Sync all projects
This commit is contained in:
@@ -0,0 +1,489 @@
|
||||
# coding=utf-8
|
||||
import re
|
||||
import json
|
||||
import time
|
||||
from urllib.parse import quote, urljoin, urlparse, parse_qs
|
||||
import sys
|
||||
# 导入外部库
|
||||
from bs4 import BeautifulSoup
|
||||
import gzip
|
||||
sys.path.append("..")
|
||||
from base.spider import Spider
|
||||
|
||||
class Spider(Spider):
|
||||
def __init__(self):
|
||||
self.name = "小鸭子看看"
|
||||
self.hosts = {
|
||||
"main": "https://xiaoyakankan.com",
|
||||
"tw": "https://tw.xiaoyakankan.com"
|
||||
}
|
||||
self.default_host = "tw"
|
||||
self.ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
||||
|
||||
# 视频格式支持
|
||||
self.VIDEO_FORMATS = ['.m3u8', '.mp4', '.flv', '.avi', '.mkv', '.mov']
|
||||
|
||||
def getName(self):
|
||||
return self.name
|
||||
|
||||
def init(self, extend=""):
|
||||
if extend:
|
||||
try:
|
||||
config = json.loads(extend)
|
||||
if config.get("host") in self.hosts:
|
||||
self.default_host = config["host"]
|
||||
self.log(f"已切换默认域名至:{self.hosts[self.default_host]}", "INFO")
|
||||
except:
|
||||
self.log("初始化参数解析失败,使用默认tw子域名", "WARNING")
|
||||
|
||||
def log(self, msg, level="INFO"):
|
||||
print(f"[{level}] [{self.name}] {time.strftime('%Y-%m-%d %H:%M:%S')} - {msg}")
|
||||
|
||||
def get_current_host(self):
|
||||
return self.hosts[self.default_host]
|
||||
|
||||
def homeContent(self, filter):
|
||||
result = {}
|
||||
result['class'] = [
|
||||
{"type_name": "电影", "type_id": "10", "land": "1", "filters": [
|
||||
{"key": "class", "name": "类型", "value": [
|
||||
{"n": "全部", "v": "10"},
|
||||
{"n": "动作片", "v": "1001"},
|
||||
{"n": "喜剧片", "v": "1002"},
|
||||
{"n": "爱情片", "v": "1003"},
|
||||
{"n": "科幻片", "v": "1004"},
|
||||
{"n": "恐怖片", "v": "1005"},
|
||||
{"n": "剧情片", "v": "1006"},
|
||||
{"n": "战争片", "v": "1007"},
|
||||
{"n": "纪录片", "v": "1008"},
|
||||
{"n": "微电影", "v": "1009"},
|
||||
{"n": "动漫电影", "v": "1010"},
|
||||
{"n": "奇幻片", "v": "1011"},
|
||||
{"n": "动画片", "v": "1013"},
|
||||
{"n": "犯罪片", "v": "1014"},
|
||||
{"n": "悬疑片", "v": "1016"},
|
||||
{"n": "欧美片", "v": "1017"},
|
||||
{"n": "邵氏电影", "v": "1019"},
|
||||
{"n": "同性片", "v": "1021"},
|
||||
{"n": "家庭片", "v": "1024"},
|
||||
{"n": "古装片", "v": "1025"},
|
||||
{"n": "历史片", "v": "1026"},
|
||||
{"n": "4K电影", "v": "1027"}
|
||||
]}
|
||||
]},
|
||||
{"type_name": "连续剧", "type_id": "11", "land": "1", "filters": [
|
||||
{"key": "class", "name": "地区类型", "value": [
|
||||
{"n": "全部", "v": "11"},
|
||||
{"n": "国产剧", "v": "1101"},
|
||||
{"n": "香港剧", "v": "1102"},
|
||||
{"n": "台湾剧", "v": "1105"},
|
||||
{"n": "韩国剧", "v": "1103"},
|
||||
{"n": "欧美剧", "v": "1104"},
|
||||
{"n": "日本剧", "v": "1106"},
|
||||
{"n": "泰国剧", "v": "1108"},
|
||||
{"n": "港台剧", "v": "1110"},
|
||||
{"n": "日韩剧", "v": "1111"},
|
||||
{"n": "东南亚剧", "v": "1112"},
|
||||
{"n": "海外剧", "v": "1107"}
|
||||
]}
|
||||
]},
|
||||
{"type_name": "综艺", "type_id": "12", "land": "1", "filters": [
|
||||
{"key": "class", "name": "地区类型", "value": [
|
||||
{"n": "全部", "v": "12"},
|
||||
{"n": "内地综艺", "v": "1201"},
|
||||
{"n": "港台综艺", "v": "1202"},
|
||||
{"n": "日韩综艺", "v": "1203"},
|
||||
{"n": "欧美综艺", "v": "1204"},
|
||||
{"n": "国外综艺", "v": "1205"}
|
||||
]}
|
||||
]},
|
||||
{"type_name": "动漫", "type_id": "13", "land": "1", "filters": [
|
||||
{"key": "class", "name": "地区类型", "value": [
|
||||
{"n": "全部", "v": "13"},
|
||||
{"n": "国产动漫", "v": "1301"},
|
||||
{"n": "日韩动漫", "v": "1302"},
|
||||
{"n": "欧美动漫", "v": "1303"},
|
||||
{"n": "海外动漫", "v": "1305"},
|
||||
{"n": "里番", "v": "1307"}
|
||||
]}
|
||||
]},
|
||||
{"type_name": "福利", "type_id": "15", "land": "1", "filters": [
|
||||
{"key": "class", "name": "地区类型", "value": [
|
||||
{"n": "全部", "v": "15"},
|
||||
{"n": "韩国情色片", "v": "1551"},
|
||||
{"n": "日本情色片", "v": "1552"},
|
||||
{"n": "大陆情色片", "v": "1555"},
|
||||
{"n": "香港情色片", "v": "1553"},
|
||||
{"n": "台湾情色片", "v": "1554"},
|
||||
{"n": "美国情色片", "v": "1556"},
|
||||
{"n": "欧洲情色片", "v": "1557"},
|
||||
{"n": "印度情色片", "v": "1558"},
|
||||
{"n": "东南亚情色片", "v": "1559"},
|
||||
{"n": "其它情色片", "v": "1550"}
|
||||
]}
|
||||
]}
|
||||
]
|
||||
|
||||
# 将所有筛选器数据添加进 result['filters'] 中
|
||||
result['filters'] = {
|
||||
"10": result['class'][0]['filters'],
|
||||
"11": result['class'][1]['filters'],
|
||||
"12": result['class'][2]['filters'],
|
||||
"13": result['class'][3]['filters'],
|
||||
"15": result['class'][4]['filters'],
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
def homeVideoContent(self):
|
||||
try:
|
||||
url = self.get_current_host()
|
||||
r = self.fetch(url, headers={"User-Agent": self.ua})
|
||||
|
||||
if r.status_code != 200:
|
||||
self.log(f"首页推荐请求失败,状态码:{r.status_code}", "ERROR")
|
||||
return {'list': []}
|
||||
|
||||
# 使用新的正则表达式来获取视频列表项
|
||||
pattern = r'<a class="link" href="(/post/[^"]+\.html)".*?<img[^>]*data-src="([^"]+)".*?alt="([^"]+)".*?(?:<div class="tag1[^>]*>([^<]+)</div>)?.*?(?:<div class="tag2">([^<]+)</div>)?'
|
||||
matches = re.findall(pattern, r.text, re.DOTALL)
|
||||
|
||||
video_list = []
|
||||
for match in matches[:12]: # 限制12个结果
|
||||
try:
|
||||
link, img_src, title, tag1, tag2 = match
|
||||
vod_id_match = re.search(r'/post/(.*?)\.html', link)
|
||||
if not vod_id_match:
|
||||
continue
|
||||
|
||||
vod_id = vod_id_match.group(1)
|
||||
|
||||
# 组合备注信息
|
||||
remarks = []
|
||||
if tag1:
|
||||
remarks.append(tag1.strip())
|
||||
if tag2:
|
||||
remarks.append(tag2.strip())
|
||||
|
||||
vod_remarks = " / ".join(remarks) if remarks else "最新"
|
||||
|
||||
# 处理图片URL
|
||||
if img_src.startswith('//'):
|
||||
img_url = 'https:' + img_src
|
||||
elif not img_src.startswith('http'):
|
||||
img_url = urljoin(self.get_current_host(), img_src)
|
||||
else:
|
||||
img_url = img_src
|
||||
|
||||
vod = {
|
||||
'vod_id': vod_id,
|
||||
'vod_name': title.strip(),
|
||||
'vod_pic': img_url,
|
||||
'vod_remarks': vod_remarks
|
||||
}
|
||||
|
||||
video_list.append(vod)
|
||||
except Exception as e:
|
||||
self.log(f"首页推荐项解析失败:{str(e)}", "ERROR")
|
||||
continue
|
||||
|
||||
self.log(f"首页推荐成功解析{len(video_list)}个项", "INFO")
|
||||
return {'list': video_list}
|
||||
except Exception as e:
|
||||
self.log(f"首页推荐内容获取失败:{str(e)}", "ERROR")
|
||||
return {'list': []}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
result = {'list': [], 'page': pg, 'pagecount': 1, 'limit': 40, 'total': 0}
|
||||
try:
|
||||
# 修复:检查 extend 参数,以支持筛选功能
|
||||
filter_tid = tid
|
||||
if extend and 'class' in extend and extend['class']:
|
||||
filter_tid = extend['class']
|
||||
|
||||
# 修复分类URL构建,使用 filter_tid
|
||||
url = f"{self.get_current_host()}/cat/{filter_tid}"
|
||||
if int(pg) > 1:
|
||||
url = f"{url}-{pg}"
|
||||
url = f"{url}.html"
|
||||
|
||||
r = self.fetch(url, headers={"User-Agent": self.ua})
|
||||
if r.status_code != 200:
|
||||
self.log(f"分类页请求失败,URL:{url},状态码:{r.status_code}", "ERROR")
|
||||
return result
|
||||
|
||||
# 修复:使用更健壮的正则来提取所有视频列表项
|
||||
items = re.findall(r'<div class="item">(.*?)<a class="title"', r.text, re.DOTALL)
|
||||
|
||||
for item in items:
|
||||
try:
|
||||
link_match = re.search(r'<a class="link" href="(/post/[^"]+\.html)"', item)
|
||||
img_match = re.search(r'<img[^>]*data-src="([^"]+)"', item)
|
||||
title_match = re.search(r'data-src="[^"]+" alt="([^"]+)"', item)
|
||||
tag1_match = re.search(r'<div class="tag1[^>]*>([^<]+)</div>', item)
|
||||
tag2_match = re.search(r'<div class="tag2">([^<]+)</div>', item)
|
||||
|
||||
if not link_match or not img_match or not title_match:
|
||||
continue
|
||||
|
||||
link = link_match.group(1)
|
||||
img_src = img_match.group(1)
|
||||
title = title_match.group(1).strip()
|
||||
vod_id = re.search(r'/post/(.*?)\.html', link).group(1)
|
||||
|
||||
remarks = []
|
||||
if tag1_match:
|
||||
remarks.append(tag1_match.group(1).strip())
|
||||
if tag2_match:
|
||||
remarks.append(tag2_match.group(1).strip())
|
||||
|
||||
vod_remarks = " / ".join(remarks) if remarks else "分类内容"
|
||||
|
||||
# 处理图片URL
|
||||
if img_src.startswith('//'):
|
||||
img_url = 'https:' + img_src
|
||||
elif not img_src.startswith('http'):
|
||||
img_url = urljoin(self.get_current_host(), img_src)
|
||||
else:
|
||||
img_url = img_src
|
||||
|
||||
vod = {
|
||||
'vod_id': vod_id,
|
||||
'vod_name': title,
|
||||
'vod_pic': img_url,
|
||||
'vod_remarks': vod_remarks
|
||||
}
|
||||
|
||||
result['list'].append(vod)
|
||||
except Exception as e:
|
||||
self.log(f"分类项解析失败:{str(e)}", "ERROR")
|
||||
continue
|
||||
|
||||
# 修复:使用更健壮的正则来提取分页信息
|
||||
page_pattern = r'/cat/\d+-(\d+)\.html'
|
||||
page_matches = re.findall(page_pattern, r.text)
|
||||
|
||||
if page_matches:
|
||||
page_nums = [int(num) for num in page_matches if num.isdigit()]
|
||||
result['pagecount'] = max(page_nums) if page_nums else 1
|
||||
else:
|
||||
result['pagecount'] = int(pg)
|
||||
|
||||
self.log(f"分类{tid}第{pg}页:解析{len(result['list'])}项", "INFO")
|
||||
return result
|
||||
except Exception as e:
|
||||
self.log(f"分类内容获取失败:{str(e)}", "ERROR")
|
||||
return result
|
||||
|
||||
def detailContent(self, ids):
|
||||
result = {"list": []}
|
||||
if not ids:
|
||||
return result
|
||||
|
||||
vod_id = ids[0]
|
||||
try:
|
||||
detail_url = f"{self.get_current_host()}/post/{vod_id}.html"
|
||||
r = self.fetch(detail_url, headers={"User-Agent": self.ua})
|
||||
if r.status_code != 200:
|
||||
self.log(f"详情页请求失败,状态码:{r.status_code}", "ERROR")
|
||||
return result
|
||||
|
||||
soup = BeautifulSoup(r.text, 'html.parser')
|
||||
|
||||
# 提取标题
|
||||
title_tag = soup.find('title')
|
||||
title = title_tag.text.replace(" - 小鴨看看", "").strip() if title_tag else "未知标题"
|
||||
|
||||
# 提取封面图
|
||||
cover_tag = soup.find('img', {'data-poster': True})
|
||||
cover_url = ""
|
||||
if cover_tag and cover_tag.get('data-poster'):
|
||||
cover_url = cover_tag['data-poster']
|
||||
if cover_url.startswith('//'):
|
||||
cover_url = 'https:' + cover_url
|
||||
elif not cover_url.startswith('http'):
|
||||
cover_url = urljoin(self.get_current_host(), cover_url)
|
||||
|
||||
# 提取描述
|
||||
desc_tag = soup.find('meta', {'name': 'description'})
|
||||
desc = desc_tag['content'].strip() if desc_tag and desc_tag.get('content') else ""
|
||||
|
||||
# 提取播放线路和剧集
|
||||
play_sources = []
|
||||
play_urls = []
|
||||
|
||||
# 从JavaScript中提取播放信息
|
||||
pp_data = None
|
||||
script_pattern = re.search(r'var pp\s*=\s*({.*?});', r.text, re.DOTALL)
|
||||
if script_pattern:
|
||||
try:
|
||||
pp_data = json.loads(script_pattern.group(1))
|
||||
except Exception as e:
|
||||
self.log(f"解析JavaScript播放信息失败:{str(e)}", "ERROR")
|
||||
|
||||
# 查找所有播放线路的容器
|
||||
source_blocks = soup.find_all('div', class_='source')
|
||||
|
||||
for idx, block in enumerate(source_blocks):
|
||||
source_name_tag = block.find('span', class_='name')
|
||||
source_name = source_name_tag.text.strip() if source_name_tag else f"线路{idx+1}"
|
||||
|
||||
resolution_tag = block.find('span', class_='res')
|
||||
resolution = resolution_tag.text.strip() if resolution_tag else ""
|
||||
|
||||
# 组合线路名称和分辨率
|
||||
full_source_name = f"{source_name} ({resolution})" if resolution else source_name
|
||||
play_sources.append(full_source_name)
|
||||
|
||||
episodes = []
|
||||
if pp_data and 'lines' in pp_data and len(pp_data['lines']) > idx:
|
||||
urls = pp_data['lines'][idx][3]
|
||||
|
||||
if isinstance(urls, list) and urls:
|
||||
for ep_idx, url in enumerate(urls):
|
||||
if isinstance(url, str) and any(url.endswith(fmt) for fmt in self.VIDEO_FORMATS):
|
||||
# 修复集数命名逻辑
|
||||
episode_name_match = re.search(r'ep-([\d\w]+)', url)
|
||||
if episode_name_match:
|
||||
episode_name = f"第{episode_name_match.group(1)}集"
|
||||
elif len(urls) == 1:
|
||||
episode_name = "全集"
|
||||
else:
|
||||
episode_name = f"第{ep_idx+1}集"
|
||||
|
||||
episodes.append(f"{episode_name}${url}")
|
||||
|
||||
if episodes:
|
||||
play_urls.append("#".join(episodes))
|
||||
else:
|
||||
play_urls.append("")
|
||||
|
||||
vod = {
|
||||
"vod_id": vod_id,
|
||||
"vod_name": title,
|
||||
"vod_pic": cover_url,
|
||||
"vod_content": desc,
|
||||
"vod_play_from": "$$$".join(play_sources) if play_sources else "",
|
||||
"vod_play_url": "$$$".join(play_urls) if play_urls else ""
|
||||
}
|
||||
|
||||
result["list"].append(vod)
|
||||
self.log(f"详情页解析成功,ID:{vod_id}", "INFO")
|
||||
return result
|
||||
except Exception as e:
|
||||
self.log(f"详情页解析失败,ID:{vod_id},错误:{str(e)}", "ERROR")
|
||||
return result
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
try:
|
||||
# 如果id已经是URL,直接返回
|
||||
if id.startswith('http'):
|
||||
return {
|
||||
"parse": 0,
|
||||
"playUrl": '',
|
||||
"url": id,
|
||||
"header": {
|
||||
"User-Agent": self.ua,
|
||||
"Referer": self.get_current_host() + "/"
|
||||
}
|
||||
}
|
||||
|
||||
# 这是一个简单的播放器URL解析,如果原始URL本身就是有效的播放地址,就直接返回
|
||||
return {
|
||||
"parse": 0,
|
||||
"playUrl": '',
|
||||
"url": id,
|
||||
"header": {
|
||||
"User-Agent": self.ua,
|
||||
"Referer": self.get_current_host() + "/"
|
||||
}
|
||||
}
|
||||
except Exception as e:
|
||||
self.log(f"播放地址解析失败:{str(e)}", "ERROR")
|
||||
return {"parse": 0, "playUrl": '', "url": id, "header": {"User-Agent": self.ua}}
|
||||
|
||||
def searchContent(self, key, quick):
|
||||
result = {"list": []}
|
||||
try:
|
||||
# 构造Google搜索URL(带站点限定)
|
||||
google_search_url = f"https://www.google.com/search?q={quote(key)}&sitesearch=xiaoyakankan.com"
|
||||
self.log(f"构造Google搜索URL: {google_search_url}")
|
||||
|
||||
# 伪装更多请求头
|
||||
headers = {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
||||
"Referer": "https://www.google.com/",
|
||||
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
|
||||
"Accept-Encoding": "gzip, deflate, br",
|
||||
"Connection": "keep-alive"
|
||||
}
|
||||
|
||||
r = self.fetch(google_search_url, headers=headers, timeout=10)
|
||||
if r.status_code != 200:
|
||||
self.log(f"Google搜索请求失败,状态码:{r.status_code},内容:{r.text[:200]}", "ERROR")
|
||||
return result
|
||||
|
||||
# 处理gzip压缩响应
|
||||
if 'gzip' in r.headers.get('Content-Encoding', ''):
|
||||
r._content = gzip.decompress(r.content)
|
||||
|
||||
try:
|
||||
soup = BeautifulSoup(r.text, 'html.parser')
|
||||
# 寻找所有包含搜索结果的a标签
|
||||
all_links = soup.find_all('a', href=re.compile(r'/url\?q='))
|
||||
|
||||
for a_tag in all_links[:15]: # 限制最多15个结果
|
||||
link = a_tag['href']
|
||||
|
||||
# 解析真实URL
|
||||
parsed_url = urlparse(link)
|
||||
query_params = parse_qs(parsed_url.query)
|
||||
|
||||
if 'q' in query_params:
|
||||
real_link = query_params['q'][0]
|
||||
|
||||
# 检查链接是否属于目标站点
|
||||
if self.get_current_host() in real_link:
|
||||
# 尝试从链接中提取影片ID
|
||||
vod_id_match = re.search(r'/post/([^/]+)\.html', real_link)
|
||||
if not vod_id_match:
|
||||
continue
|
||||
|
||||
vod_id = vod_id_match.group(1)
|
||||
|
||||
# 获取标题和图片(这里因为Google搜索结果没有图片,所以图片留空)
|
||||
title_tag = a_tag.find('h3')
|
||||
if not title_tag:
|
||||
# 有时标题在父级或其他元素中
|
||||
title_tag = a_tag.find('div', class_='g')
|
||||
|
||||
title = title_tag.text.strip() if title_tag else "未知标题"
|
||||
|
||||
vod = {
|
||||
"vod_id": vod_id,
|
||||
"vod_name": title,
|
||||
"vod_pic": "",
|
||||
"vod_remarks": "Google搜索结果"
|
||||
}
|
||||
|
||||
result["list"].append(vod)
|
||||
|
||||
except Exception as e:
|
||||
self.log(f"解析Google搜索结果失败:{str(e)}", "ERROR")
|
||||
return result
|
||||
|
||||
self.log(f"Google搜索成功解析{len(result['list'])}个项", "INFO")
|
||||
return result
|
||||
except Exception as e:
|
||||
self.log(f"搜索内容获取失败:{str(e)}", "ERROR")
|
||||
return result
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
"""判断是否为视频格式"""
|
||||
return any(url.lower().endswith(fmt) for fmt in self.VIDEO_FORMATS)
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def localProxy(self, param):
|
||||
pass
|
||||
@@ -0,0 +1,480 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 星河影视 xhkan.top - TVBox/PY Spider 兼容修复版
|
||||
# 重点:按 TVBox Py Spider 标准方法返回;分类页无 SSR 列表时自动回退首页分区数据;保留原 /api/player/resolve 播放解析。
|
||||
|
||||
import sys
|
||||
import re
|
||||
import json
|
||||
import base64
|
||||
import urllib.parse
|
||||
|
||||
sys.path.append('..')
|
||||
from base.spider import Spider
|
||||
|
||||
|
||||
class Spider(Spider):
|
||||
host = 'https://xhkan.top'
|
||||
ua = ('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 '
|
||||
'(KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36')
|
||||
|
||||
# xhkan 详情接口里的 cat 是数字;网页分类路径是 slug
|
||||
classes = [
|
||||
{'type_id': '1', 'type_name': '电影'},
|
||||
{'type_id': '2', 'type_name': '电视剧'},
|
||||
{'type_id': '3', 'type_name': '综艺'},
|
||||
{'type_id': '4', 'type_name': '动漫'},
|
||||
{'type_id': '6', 'type_name': '短剧'},
|
||||
]
|
||||
slug_map = {'1': 'movie', '2': 'tv', '3': 'variety', '4': 'anime', '6': 'short-drama'}
|
||||
cat_name_map = {'1': '电影', '2': '电视剧', '3': '综艺', '4': '动漫', '6': '短剧'}
|
||||
source_names = {'qq': '腾讯', 'qiyi': '爱奇艺', 'youku': '优酷', 'mgtv': '芒果', 'bilibili': '哔哩'}
|
||||
sites = ['qq', 'qiyi', 'youku', 'mgtv', 'bilibili']
|
||||
block_words = ('预告', '片花', 'trailer', 'teaser')
|
||||
|
||||
def init(self, extend=''):
|
||||
self.hosts = [self.host]
|
||||
try:
|
||||
if extend:
|
||||
ext = json.loads(extend) if isinstance(extend, str) and extend.strip().startswith('{') else extend
|
||||
site = ''
|
||||
if isinstance(ext, dict):
|
||||
site = ext.get('site') or ext.get('host') or ''
|
||||
elif isinstance(ext, str):
|
||||
site = ext
|
||||
if site:
|
||||
self.hosts = [i.strip().rstrip('/') for i in site.split(',') if i.strip()]
|
||||
self.host = self.hosts[0]
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def getName(self):
|
||||
return '星河影视'
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
return False
|
||||
|
||||
def manualVideoCheck(self):
|
||||
return False
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
def localProxy(self, param):
|
||||
return None
|
||||
|
||||
# ============ 标准 TVBox 方法 ============
|
||||
def homeContent(self, filter):
|
||||
filters = {}
|
||||
for c in self.classes:
|
||||
filters[c['type_id']] = [
|
||||
{'key': 'sort', 'name': '排序', 'value': [
|
||||
{'n': '最新', 'v': 'time'}, {'n': '热度', 'v': 'hits'}, {'n': '评分', 'v': 'score'}
|
||||
]},
|
||||
{'key': 'type', 'name': '类型', 'value': [
|
||||
{'n': '全部', 'v': ''}, {'n': '喜剧', 'v': '喜剧'}, {'n': '爱情', 'v': '爱情'},
|
||||
{'n': '动作', 'v': '动作'}, {'n': '剧情', 'v': '剧情'}, {'n': '悬疑', 'v': '悬疑'},
|
||||
{'n': '犯罪', 'v': '犯罪'}, {'n': '科幻', 'v': '科幻'}, {'n': '动画', 'v': '动画'},
|
||||
{'n': '其他', 'v': '其他'}
|
||||
]},
|
||||
{'key': 'area', 'name': '地区', 'value': [
|
||||
{'n': '全部', 'v': ''}, {'n': '大陆', 'v': '大陆'}, {'n': '香港', 'v': '香港'},
|
||||
{'n': '台湾', 'v': '台湾'}, {'n': '美国', 'v': '美国'}, {'n': '韩国', 'v': '韩国'},
|
||||
{'n': '日本', 'v': '日本'}, {'n': '泰国', 'v': '泰国'}, {'n': '其他', 'v': '其他'}
|
||||
]},
|
||||
{'key': 'year', 'name': '年份', 'value': [{'n': '全部', 'v': ''}] + [
|
||||
{'n': str(y), 'v': str(y)} for y in range(2026, 2014, -1)
|
||||
]}
|
||||
]
|
||||
return {'class': self.classes, 'filters': filters}
|
||||
|
||||
def homeVideoContent(self):
|
||||
html = self._get_first_text(['/'])
|
||||
return {'list': self._parse_cards(html)[:40]}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
tid = str(tid or '1')
|
||||
page = int(pg or 1)
|
||||
extend = extend or {}
|
||||
slug = self.slug_map.get(tid, tid)
|
||||
|
||||
# 1. 优先尝试可能的 JSON API,兼容站点后续改版
|
||||
videos = self._try_category_apis(tid, slug, page, extend)
|
||||
|
||||
# 2. 再尝试网页分类路径
|
||||
if not videos:
|
||||
paths = self._build_category_paths(slug, page, extend)
|
||||
for p in paths:
|
||||
html = self._get_first_text([p])
|
||||
videos = self._parse_cards(html)
|
||||
if videos:
|
||||
break
|
||||
|
||||
# 3. 当前站分类页可能只渲染筛选栏、不直接输出列表,回退首页对应分类热播数据
|
||||
if not videos:
|
||||
html = self._get_first_text(['/'])
|
||||
all_videos = self._parse_cards(html)
|
||||
videos = [v for v in all_videos if str(v.get('vod_id', '')).split('@', 1)[0] == tid]
|
||||
|
||||
return {'list': videos, 'page': page, 'pagecount': page + 1, 'limit': 30, 'total': 999999}
|
||||
|
||||
def searchContent(self, key, quick, pg='1'):
|
||||
page = int(pg or 1)
|
||||
kw = str(key or '').strip()
|
||||
if not kw:
|
||||
return {'list': [], 'page': page}
|
||||
|
||||
videos = []
|
||||
# 1. 兼容常见搜索 API / 搜索页参数
|
||||
paths = [
|
||||
'/api/search?keyword=%s&page=%s' % (urllib.parse.quote(kw), page),
|
||||
'/api/search?wd=%s&page=%s' % (urllib.parse.quote(kw), page),
|
||||
'/search?keyword=%s&page=%s' % (urllib.parse.quote(kw), page),
|
||||
'/search?q=%s&page=%s' % (urllib.parse.quote(kw), page),
|
||||
]
|
||||
for p in paths:
|
||||
txt = self._get_first_text([p])
|
||||
if not txt:
|
||||
continue
|
||||
st = txt.strip()
|
||||
if st.startswith('{') or st.startswith('['):
|
||||
try:
|
||||
videos = self._parse_json_list(json.loads(st))
|
||||
except Exception:
|
||||
videos = []
|
||||
else:
|
||||
videos = self._parse_cards(txt)
|
||||
if videos:
|
||||
break
|
||||
|
||||
# 2. 搜索 API 不可用时,至少从首页已渲染资源里本地匹配,保证壳内不空白
|
||||
if not videos:
|
||||
html = self._get_first_text(['/'])
|
||||
videos = [v for v in self._parse_cards(html) if kw.lower() in v.get('vod_name', '').lower()]
|
||||
|
||||
return {'list': videos, 'page': page, 'pagecount': page + 1, 'limit': 30, 'total': 999999}
|
||||
|
||||
def detailContent(self, ids):
|
||||
if not ids:
|
||||
return {'list': []}
|
||||
raw = str(ids[0])
|
||||
cat, vod_id = self._split_vid(raw)
|
||||
if not vod_id:
|
||||
return {'list': []}
|
||||
|
||||
detail = None
|
||||
for site in self.sites:
|
||||
try:
|
||||
api = '/api/detail?cat=%s&id=%s&site=%s' % (cat, urllib.parse.quote(vod_id), site)
|
||||
txt = self._get_first_text([api])
|
||||
data = json.loads(txt)
|
||||
if data.get('errno') == 0 and data.get('data'):
|
||||
detail = data.get('data')
|
||||
break
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
# API 失败时,解析详情页 HTML 兜底
|
||||
if not detail:
|
||||
html = self._get_first_text(['/detail/%s/%s' % (cat, urllib.parse.quote(vod_id))])
|
||||
return {'list': [self._detail_from_html(cat, vod_id, html)]}
|
||||
|
||||
title = detail.get('title') or detail.get('name') or vod_id
|
||||
if self._blocked_title(title):
|
||||
return {'list': []}
|
||||
|
||||
vod = {
|
||||
'vod_id': '%s@%s' % (cat, vod_id),
|
||||
'vod_name': title,
|
||||
'vod_pic': self._abs_img(detail.get('cover') or detail.get('pic') or detail.get('poster') or ''),
|
||||
'vod_remarks': detail.get('remarks') or detail.get('status') or '',
|
||||
'vod_year': str(detail.get('year') or detail.get('pubdate') or ''),
|
||||
'vod_area': self._join(detail.get('area') or ''),
|
||||
'vod_actor': self._join(detail.get('actors') or detail.get('actor') or ''),
|
||||
'vod_director': self._join(detail.get('directors') or detail.get('director') or ''),
|
||||
'vod_content': self._clean_text(detail.get('desc') or detail.get('intro') or detail.get('description') or ''),
|
||||
}
|
||||
|
||||
play_from, play_urls = [], []
|
||||
allep = detail.get('allepidetail') or {}
|
||||
if isinstance(allep, dict):
|
||||
for site, eps in allep.items():
|
||||
if not isinstance(eps, list) or not eps:
|
||||
continue
|
||||
urls = []
|
||||
for idx, ep in enumerate(eps, 1):
|
||||
ep_no = ep.get('playlink_num') or ep.get('episode') or idx
|
||||
name = ep.get('title') or ('第%s集' % str(ep_no).zfill(2) if str(ep_no).isdigit() else str(ep_no))
|
||||
raw_url = ep.get('url') or ep.get('play_url') or ''
|
||||
if not raw_url:
|
||||
continue
|
||||
pid = self._enc({'cat': cat, 'vod_id': vod_id, 'source': site, 'episode': int(ep_no) if str(ep_no).isdigit() else idx, 'playUrl': raw_url})
|
||||
urls.append('%s$%s' % (name, pid))
|
||||
if urls:
|
||||
play_from.append(self.source_names.get(site, site))
|
||||
play_urls.append('#'.join(urls))
|
||||
|
||||
vod['vod_play_from'] = '$$$'.join(play_from)
|
||||
vod['vod_play_url'] = '$$$'.join(play_urls)
|
||||
return {'list': [vod]}
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
try:
|
||||
obj = self._dec(str(id or ''))
|
||||
if not isinstance(obj, dict):
|
||||
return {'parse': 0, 'url': str(id or ''), 'header': {'User-Agent': self.ua, 'Referer': self.host + '/'}}
|
||||
|
||||
play_url = obj.get('playUrl') or ''
|
||||
payload = {
|
||||
'vodId': obj.get('vod_id') or obj.get('vodId') or 'direct',
|
||||
'source': obj.get('source') or 'qq',
|
||||
'episode': int(obj.get('episode') or 1),
|
||||
'category': int(obj.get('cat') or obj.get('category') or 2),
|
||||
'playUrl': play_url
|
||||
}
|
||||
self._get_first_text(['/api/player/token'])
|
||||
txt = self._post_first_json('/api/player/resolve', payload)
|
||||
data = json.loads(txt) if txt else {}
|
||||
url = data.get('url') if data.get('success') else ''
|
||||
return {'parse': 0, 'url': url or play_url or '', 'header': {'User-Agent': self.ua, 'Referer': self.host + '/'}}
|
||||
except Exception:
|
||||
return {'parse': 0, 'url': ''}
|
||||
|
||||
# ============ 分类 / API 兜底 ============
|
||||
def _build_category_paths(self, slug, page, extend):
|
||||
params = {}
|
||||
for k in ('type', 'area', 'year', 'sort'):
|
||||
if extend.get(k):
|
||||
params[k] = extend.get(k)
|
||||
if page > 1:
|
||||
params['page'] = str(page)
|
||||
q = ('?' + urllib.parse.urlencode(params)) if params else ''
|
||||
base = '/short-drama' if slug == 'short-drama' else '/category/' + slug
|
||||
paths = [base + q]
|
||||
if page > 1:
|
||||
paths.append(base + '/page/%s' % page)
|
||||
paths.append(base + '?page=%s' % page)
|
||||
return paths
|
||||
|
||||
def _try_category_apis(self, tid, slug, page, extend):
|
||||
params = {
|
||||
'cat': tid,
|
||||
'category': tid,
|
||||
'type': extend.get('type', ''),
|
||||
'area': extend.get('area', ''),
|
||||
'year': extend.get('year', ''),
|
||||
'sort': extend.get('sort', ''),
|
||||
'page': str(page),
|
||||
'limit': '30'
|
||||
}
|
||||
q1 = urllib.parse.urlencode(params)
|
||||
q2 = urllib.parse.urlencode(dict(params, cat=slug, category=slug))
|
||||
paths = [
|
||||
'/api/list?' + q1,
|
||||
'/api/vod/list?' + q1,
|
||||
'/api/category?' + q1,
|
||||
'/api/category?' + q2,
|
||||
'/api/videos?' + q1,
|
||||
'/api/search?cat=%s&page=%s' % (urllib.parse.quote(tid), page),
|
||||
'/api/search?category=%s&page=%s' % (urllib.parse.quote(slug), page),
|
||||
]
|
||||
for p in paths:
|
||||
txt = self._get_first_text([p])
|
||||
if not txt:
|
||||
continue
|
||||
try:
|
||||
obj = json.loads(txt)
|
||||
videos = self._parse_json_list(obj)
|
||||
if videos:
|
||||
return videos
|
||||
except Exception:
|
||||
continue
|
||||
return []
|
||||
|
||||
# ============ 解析工具 ============
|
||||
def _parse_cards(self, html):
|
||||
html = html or ''
|
||||
videos, seen = [], set()
|
||||
# 适配 /detail/2/xxxx,下一版如果改成完整域名也能匹配
|
||||
pattern = re.compile(r'<a[^>]+href=["\'](?:https?://[^/]+)?/detail/(\d+)/([^"\'#?]+)[^"\']*["\'][^>]*>(.*?)</a>', re.S | re.I)
|
||||
for m in pattern.finditer(html):
|
||||
cat = m.group(1)
|
||||
vid = urllib.parse.unquote(m.group(2))
|
||||
block = m.group(3)
|
||||
title = self._extract_title(block)
|
||||
if not title or self._blocked_title(title):
|
||||
continue
|
||||
pic = self._first_match(block, r'(?:data-src|src)=["\']([^"\']+)["\']') or ''
|
||||
remark = self._first_match(block, r'(全\d+集|更新至\d+集|\d{4}-\d{2}-\d{2}期|\d+期|正片)') or ''
|
||||
vod_id = '%s@%s' % (cat, vid)
|
||||
if vod_id in seen:
|
||||
continue
|
||||
seen.add(vod_id)
|
||||
videos.append({'vod_id': vod_id, 'vod_name': title, 'vod_pic': self._abs_img(pic), 'vod_remarks': remark})
|
||||
return videos
|
||||
|
||||
def _parse_json_list(self, obj):
|
||||
arr = []
|
||||
if isinstance(obj, dict):
|
||||
data = obj.get('data', obj)
|
||||
if isinstance(data, dict):
|
||||
for k in ('list', 'items', 'records', 'result', 'data'):
|
||||
if isinstance(data.get(k), list):
|
||||
arr = data.get(k)
|
||||
break
|
||||
elif isinstance(data, list):
|
||||
arr = data
|
||||
if not arr:
|
||||
for k in ('list', 'items', 'records', 'result'):
|
||||
if isinstance(obj.get(k), list):
|
||||
arr = obj.get(k)
|
||||
break
|
||||
elif isinstance(obj, list):
|
||||
arr = obj
|
||||
videos = []
|
||||
for it in arr or []:
|
||||
if not isinstance(it, dict):
|
||||
continue
|
||||
title = it.get('title') or it.get('name') or it.get('vod_name') or it.get('videoName') or ''
|
||||
if not title or self._blocked_title(title):
|
||||
continue
|
||||
cat = str(it.get('cat') or it.get('category') or it.get('type') or it.get('type_id') or it.get('cid') or '2')
|
||||
if cat in self.slug_map:
|
||||
pass
|
||||
else:
|
||||
# slug 转数字
|
||||
for k, v in self.slug_map.items():
|
||||
if str(cat) == v:
|
||||
cat = k
|
||||
break
|
||||
vid = str(it.get('id') or it.get('vod_id') or it.get('vid') or it.get('episode_id') or '')
|
||||
if not vid:
|
||||
continue
|
||||
videos.append({
|
||||
'vod_id': '%s@%s' % (cat, vid),
|
||||
'vod_name': self._clean_text(title),
|
||||
'vod_pic': self._abs_img(it.get('cover') or it.get('pic') or it.get('poster') or it.get('vod_pic') or ''),
|
||||
'vod_remarks': it.get('remarks') or it.get('status') or it.get('vod_remarks') or ''
|
||||
})
|
||||
return videos
|
||||
|
||||
def _detail_from_html(self, cat, vod_id, html):
|
||||
title = self._first_match(html, r'<h1[^>]*>(.*?)</h1>') or vod_id
|
||||
title = self._clean_text(title)
|
||||
pic = self._first_match(html, r'<img[^>]+alt=["\']%s["\'][^>]+(?:src|data-src)=["\']([^"\']+)' % re.escape(title)) or self._first_match(html, r'<img[^>]+(?:src|data-src)=["\']([^"\']+)["\']')
|
||||
content = self._first_match(html, r'###?\s*简介\s*(.*?)\s*(?:展开全部|选集|</)')
|
||||
# 播放集数从详情页 /play/cat/id/ep?s=source 提取
|
||||
eps = []
|
||||
ep_re = re.compile(r'href=["\'](?:https?://[^/]+)?/play/(\d+)/([^/"\']+)/(\d+)\?s=([^"\'&]+)[^"\']*["\'][^>]*>(.*?)</a>', re.S | re.I)
|
||||
for mm in ep_re.finditer(html or ''):
|
||||
c, vid, ep, src, name = mm.group(1), urllib.parse.unquote(mm.group(2)), mm.group(3), mm.group(4), self._clean_text(mm.group(5))
|
||||
if c != str(cat) or vid != str(vod_id):
|
||||
continue
|
||||
pid = self._enc({'cat': c, 'vod_id': vid, 'source': src, 'episode': int(ep), 'playUrl': ''})
|
||||
eps.append('%s$%s' % (name or ('第%s集' % ep), pid))
|
||||
vod = {
|
||||
'vod_id': '%s@%s' % (cat, vod_id),
|
||||
'vod_name': title,
|
||||
'vod_pic': self._abs_img(pic),
|
||||
'vod_content': self._clean_text(content),
|
||||
'vod_play_from': '星河',
|
||||
'vod_play_url': '#'.join(eps)
|
||||
}
|
||||
return vod
|
||||
|
||||
def _extract_title(self, block):
|
||||
title = self._first_match(block, r'alt=["\']([^"\']+)["\']') or self._first_match(block, r'title=["\']([^"\']+)["\']')
|
||||
if title:
|
||||
return self._clean_text(title)
|
||||
text = self._clean_text(block)
|
||||
# 首页卡片常见顺序:简介 + 状态 + 类型 + 标题,取最后一个较短片段
|
||||
parts = re.split(r'(?:全\d+集|更新至\d+集|\d{4}-\d{2}-\d{2}期|\d+期|正片|其他|剧情|喜剧|爱情|动作|悬疑|犯罪|动画|原创|都市|网剧|少儿)', text)
|
||||
cand = parts[-1].strip() if parts else text
|
||||
if not cand or len(cand) > 40:
|
||||
cand = text[-40:].strip()
|
||||
return cand
|
||||
|
||||
def _blocked_title(self, title):
|
||||
t = (title or '').lower()
|
||||
return any(w.lower() in t for w in self.block_words)
|
||||
|
||||
def _split_vid(self, raw):
|
||||
raw = str(raw)
|
||||
if '@' in raw:
|
||||
return raw.split('@', 1)
|
||||
m = re.search(r'/detail/(\d+)/([^/?#]+)', raw)
|
||||
if m:
|
||||
return m.group(1), urllib.parse.unquote(m.group(2))
|
||||
return '2', raw
|
||||
|
||||
def _headers(self):
|
||||
return {
|
||||
'User-Agent': self.ua,
|
||||
'Accept': 'application/json,text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
||||
'Referer': self.host + '/',
|
||||
}
|
||||
|
||||
def _get_first_text(self, paths, timeout=20):
|
||||
for base in getattr(self, 'hosts', [self.host]):
|
||||
base = base.rstrip('/')
|
||||
for p in paths:
|
||||
url = p if str(p).startswith('http') else base + (p if str(p).startswith('/') else '/' + str(p))
|
||||
try:
|
||||
r = self.fetch(url, headers=self._headers(), timeout=timeout, verify=False)
|
||||
if hasattr(r, 'text'):
|
||||
txt = r.text
|
||||
else:
|
||||
content = getattr(r, 'content', b'')
|
||||
txt = content.decode('utf-8', 'ignore') if isinstance(content, bytes) else str(content)
|
||||
if txt:
|
||||
return txt
|
||||
except Exception:
|
||||
continue
|
||||
return ''
|
||||
|
||||
def _post_first_json(self, path, payload, timeout=20):
|
||||
for base in getattr(self, 'hosts', [self.host]):
|
||||
url = base.rstrip('/') + path
|
||||
try:
|
||||
r = self.fetch(url, headers={**self._headers(), 'Content-Type': 'application/json'}, data=json.dumps(payload).encode('utf-8'), method='POST', timeout=timeout, verify=False)
|
||||
if hasattr(r, 'text'):
|
||||
return r.text
|
||||
content = getattr(r, 'content', b'')
|
||||
return content.decode('utf-8', 'ignore') if isinstance(content, bytes) else str(content)
|
||||
except Exception:
|
||||
continue
|
||||
return ''
|
||||
|
||||
def _enc(self, obj):
|
||||
return base64.urlsafe_b64encode(json.dumps(obj, ensure_ascii=False, separators=(',', ':')).encode('utf-8')).decode('utf-8').rstrip('=')
|
||||
|
||||
def _dec(self, s):
|
||||
try:
|
||||
return json.loads(base64.urlsafe_b64decode((s + '=' * (-len(s) % 4)).encode()).decode('utf-8'))
|
||||
except Exception:
|
||||
return s
|
||||
|
||||
def _abs_img(self, url):
|
||||
url = str(url or '').strip()
|
||||
if not url:
|
||||
return ''
|
||||
if url.startswith('//'):
|
||||
return 'https:' + url
|
||||
if url.startswith('/'):
|
||||
return self.host.rstrip('/') + url
|
||||
return url
|
||||
|
||||
def _clean_text(self, text):
|
||||
text = re.sub(r'<script[\s\S]*?</script>|<style[\s\S]*?</style>', ' ', str(text or ''), flags=re.I)
|
||||
text = re.sub(r'<[^>]+>', ' ', text)
|
||||
for a, b in {' ': ' ', '&': '&', '"': '"', ''': "'", '<': '<', '>': '>'}.items():
|
||||
text = text.replace(a, b)
|
||||
return re.sub(r'\s+', ' ', text).strip()
|
||||
|
||||
def _first_match(self, text, pattern):
|
||||
m = re.search(pattern, text or '', re.S | re.I)
|
||||
return self._clean_text(m.group(1)) if m else ''
|
||||
|
||||
def _join(self, v):
|
||||
if isinstance(v, list):
|
||||
return '/'.join([str(x.get('name') if isinstance(x, dict) else x) for x in v])
|
||||
return str(v or '')
|
||||
+4
-1
@@ -54,8 +54,9 @@ CCTV16奥林匹克1080p,https://php.jdshipin.com:2096/TVOD/iptv.php?id=cctv16
|
||||
澳门体育1080p,http://cdn6.163189.xyz/163189/amty
|
||||
纬来体育,rtmp://f13h.mine.nu/sat/tv721
|
||||
广东珠江1080p,http://m.061899.xyz/mg/gdzj
|
||||
11,https://www.fulleroticmovies.net/get_stream/4047-360.mp4
|
||||
|
||||
港台三级(140部),#genre#
|
||||
港台三级(142部),#genre#
|
||||
1色降2之血玫瑰,https://vip1.lz-cdn1.com/20220331/733_58b741b7/index.m3u8
|
||||
2色降2之萬里驅魔,https://m3u8.cdn202511.com/videos/202411/21/673e5ba03276de039d31a162/7cd5f8/index.m3u8
|
||||
3倩女幽魂,https://jkunnzyx.com/20250423/nU0wWqCw/2000kb/hls/index.m3u8
|
||||
@@ -196,6 +197,8 @@ CCTV16奥林匹克1080p,https://php.jdshipin.com:2096/TVOD/iptv.php?id=cctv16
|
||||
138贴身情人,https://v8.ppqrrs.com/wjv8/202310/12/PgMkXNrS8Q1/video/index.m3u8
|
||||
139山中艳谭,https://v8.ppqrrs.com/wjv8/202310/10/hLztMZdFui1/video/index.m3u8
|
||||
140俏表妹,https://v8.ppqrrs.com/wjv8/202310/10/ptwbuUDLQD1/video/index.m3u8
|
||||
141莲雾斩道,https://v8.ppqrrs.com/wjv8/202310/10/ggvbaajsBi1/video/index.m3u8
|
||||
142丛林快活女,https://v8.ppqrrs.com/wjv8/202310/09/6f4DrSakf31/video/index.m3u8
|
||||
|
||||
传媒系列(34部),#genre#
|
||||
1麻豆传媒MDSR-0009-1.苏语棠.艾熙.极品嫂子.夏日欲火无套乱伦.情色文学.4P,https://m3md.n6uem8.xyz/doudou/ms/dfc18a8a868268864bc36d1c8__746738/hls/1/index.m3u8
|
||||
|
||||
@@ -111,6 +111,12 @@
|
||||
"name": "🐬麻花影视.py",
|
||||
"type": 3,
|
||||
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/GHK/refs/heads/main/py/麻花影视.py"
|
||||
},
|
||||
{
|
||||
"key": "XH",
|
||||
"name": "🐬星河影视.py",
|
||||
"type": 3,
|
||||
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/GHK/refs/heads/main/py/星河影视.py"
|
||||
},
|
||||
{
|
||||
"key": "guanfeng",
|
||||
@@ -172,6 +178,12 @@
|
||||
"type": 3,
|
||||
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/GHK/refs/heads/main/py/采集聚合.py"
|
||||
},
|
||||
{
|
||||
"key": "xyzkk",
|
||||
"name": "🐬小鸭子看看.py 有🔞",
|
||||
"type": 3,
|
||||
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/GHK/refs/heads/main/py/小鸭子看看.py"
|
||||
},
|
||||
{
|
||||
"key": "kf",
|
||||
"name": "🐬咖啡体育直播.py",
|
||||
|
||||
@@ -70,6 +70,12 @@
|
||||
"name": "🐬麻花影视.py",
|
||||
"type": 3,
|
||||
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/GHK/refs/heads/main/py/麻花影视.py"
|
||||
},
|
||||
{
|
||||
"key": "XH",
|
||||
"name": "🐬星河影视.py",
|
||||
"type": 3,
|
||||
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/GHK/refs/heads/main/py/星河影视.py"
|
||||
},
|
||||
{
|
||||
"key": "guanfeng",
|
||||
@@ -124,6 +130,12 @@
|
||||
"name": "🐬七味.py(关梯)",
|
||||
"type": 3,
|
||||
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/GHK/refs/heads/main/py/七味.py"
|
||||
},
|
||||
{
|
||||
"key": "xyzkk",
|
||||
"name": "🐬小鸭子看看.py 有🔞",
|
||||
"type": 3,
|
||||
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/GHK/refs/heads/main/py/小鸭子看看.py"
|
||||
},
|
||||
{
|
||||
"key": "kf",
|
||||
|
||||
@@ -111,6 +111,12 @@
|
||||
"name": "🐬麻花影视.py",
|
||||
"type": 3,
|
||||
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/GHK/refs/heads/main/py/麻花影视.py"
|
||||
},
|
||||
{
|
||||
"key": "XH",
|
||||
"name": "🐬星河影视.py",
|
||||
"type": 3,
|
||||
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/GHK/refs/heads/main/py/星河影视.py"
|
||||
},
|
||||
{
|
||||
"key": "guanfeng",
|
||||
|
||||
+13
-1
@@ -24,7 +24,7 @@
|
||||
"api":"https://file.icve.com.cn/file_doc/249/899/3E7E0C8A023B624CEC6BDCC200F06F02.js",
|
||||
"ext":"https://cdn.waimaimingtang.com/file/images/bwc/20251023002031-6064033c16.js"
|
||||
},
|
||||
{"key":"YK",
|
||||
{"key":"YKvm",
|
||||
"name":"🐬优酷视频 海豚影视交流群 TG:@hshsjk9[追剧]",
|
||||
"type":3,
|
||||
"api":"https://file.icve.com.cn/file_doc/249/899/3E7E0C8A023B624CEC6BDCC200F06F02.js",
|
||||
@@ -142,6 +142,12 @@
|
||||
"name": "🐬麻花影视.py[追剧]",
|
||||
"type": 3,
|
||||
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/GHK/refs/heads/main/py/麻花影视.py"
|
||||
},
|
||||
{
|
||||
"key": "XH",
|
||||
"name": "🐬星河影视.py",
|
||||
"type": 3,
|
||||
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/GHK/refs/heads/main/py/星河影视.py"
|
||||
},
|
||||
{
|
||||
"key": "guanfeng",
|
||||
@@ -203,6 +209,12 @@
|
||||
"type": 3,
|
||||
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/GHK/refs/heads/main/py/采集聚合.py"
|
||||
},
|
||||
{
|
||||
"key": "xyzkk",
|
||||
"name": "🐬小鸭子看看.py 有🔞[追剧]",
|
||||
"type": 3,
|
||||
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/GHK/refs/heads/main/py/小鸭子看看.py"
|
||||
},
|
||||
{
|
||||
"key": "fY",
|
||||
"name": "🐬枫叶影院.py(关梯)[追剧]",
|
||||
|
||||
@@ -94,6 +94,12 @@
|
||||
"name": "🐬麻花影视.py[追剧]",
|
||||
"type": 3,
|
||||
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/GHK/refs/heads/main/py/麻花影视.py"
|
||||
},
|
||||
{
|
||||
"key": "XH",
|
||||
"name": "🐬星河影视.py[追剧]",
|
||||
"type": 3,
|
||||
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/GHK/refs/heads/main/py/星河影视.py"
|
||||
},
|
||||
{
|
||||
"key": "guanfeng",
|
||||
@@ -148,6 +154,12 @@
|
||||
"name": "🐬七味.py(关梯)[追剧]",
|
||||
"type": 3,
|
||||
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/GHK/refs/heads/main/py/七味.py"
|
||||
},
|
||||
{
|
||||
"key": "xyzkk",
|
||||
"name": "🐬小鸭子看看.py 有🔞[追剧]",
|
||||
"type": 3,
|
||||
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/GHK/refs/heads/main/py/小鸭子看看.py"
|
||||
},
|
||||
{
|
||||
"key": "cjjh",
|
||||
|
||||
+9
-3
@@ -24,7 +24,7 @@
|
||||
"api":"https://file.icve.com.cn/file_doc/249/899/3E7E0C8A023B624CEC6BDCC200F06F02.js",
|
||||
"ext":"https://cdn.waimaimingtang.com/file/images/bwc/20251023002031-6064033c16.js"
|
||||
},
|
||||
{"key":"YK",
|
||||
{"key":"YKvm",
|
||||
"name":"🐬优酷视频 海豚影视交流群 TG:@hshsjk9",
|
||||
"type":3,
|
||||
"api":"https://file.icve.com.cn/file_doc/249/899/3E7E0C8A023B624CEC6BDCC200F06F02.js",
|
||||
@@ -78,7 +78,7 @@
|
||||
},
|
||||
{
|
||||
"key": "sm",
|
||||
"name": "🐬熊猫直播",
|
||||
"name": "🐬韩国熊猫直播.py",
|
||||
"type": 3,
|
||||
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/GHK/refs/heads/main/py/熊猫直播.py"
|
||||
},
|
||||
@@ -103,7 +103,7 @@
|
||||
},
|
||||
{
|
||||
"key": "ww",
|
||||
"name": "🐬哇哇APP.py(关梯)[追剧]",
|
||||
"name": "🐬哇哇APP.py(关梯)",
|
||||
"type": 3,
|
||||
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/GHK/refs/heads/main/py/哇哇APP.py"
|
||||
},
|
||||
@@ -142,6 +142,12 @@
|
||||
"name": "🐬麻花影视.py",
|
||||
"type": 3,
|
||||
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/GHK/refs/heads/main/py/麻花影视.py"
|
||||
},
|
||||
{
|
||||
"key": "XH",
|
||||
"name": "🐬星河影视.py",
|
||||
"type": 3,
|
||||
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/GHK/refs/heads/main/py/星河影视.py"
|
||||
},
|
||||
{
|
||||
"key": "guanfeng",
|
||||
|
||||
Reference in New Issue
Block a user