Sync all projects

This commit is contained in:
github-actions[bot]
2026-07-17 16:00:00 +00:00
parent cca5f87a88
commit f70ad0a71c
36 changed files with 7176 additions and 6451 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+7
View File
@@ -22,6 +22,13 @@
"bz": "1",
"paichu": "1,2,3,4"
},
{
"name": "TV-悠悠资源站",
"api": "https://uuzy.me/api.php/provide/vod/",
"detail": "https://uuzy.me",
"bz": "1",
"paichu": "1,2,3,4"
},
{
"name": "TV-量子资源",
"api": "https://cj.lziapi.com/api.php/provide/vod",
+7
View File
@@ -21,6 +21,13 @@
"detail": "https://98zy.vip",
"bz": "1",
"paichu": "1,2,3,4"
},
{
"name": "TV-悠悠资源站",
"api": "https://uuzy.me/api.php/provide/vod/",
"detail": "https://uuzy.me",
"bz": "1",
"paichu": "1,2,3,4"
},
{
"name": "TV-大众资源站",
+2 -1
View File
@@ -46,7 +46,8 @@ class Spider(Spider):
's33': {'name': '🐾淘片', 'api': 'https://www.taopianzy.com/cjapi/mc/vod/json.html'},
's34': {'name': '🐾98', 'api': 'https://98zy.vip/api.php/provide/vod/'},
's35': {'name': '🐾大众', 'api': 'https://cdn.dzzyapi.com/api.php/provide/vod/'},
's36': {'name': '📺魔都', 'api': 'https://www.mdzyapi.com/api.php/provide/vod'},
's36': {'name': '🐾悠悠2', 'api': 'https://uuzy.me/api.php/provide/vod/'},
's37': {'name': '📺魔都', 'api': 'https://www.mdzyapi.com/api.php/provide/vod'},
}
headers = {
+168
View File
@@ -0,0 +1,168 @@
# -*- coding: utf-8 -*-
from base.spider import Spider
import re, sys, time, random, secrets, hashlib, urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
sys.path.append('..')
class Spider(Spider):
host, device_id = 'https://bubutv.top/', 'com.sunshine.tv_3qys_B7k7Dt56Rn'
def homeContent(self, filter):
if not self.host: return None
response = self.fetch(f'{self.host}/api.php/app/index/home', headers=self.headers(), verify=False).json()
categories = response['data']['categories']
videos, classes = [], []
for i in categories:
classes.append({'type_id': i['type_name'], 'type_name': i['type_name']})
videos.extend(self.arr2vods(i['videos']))
return {'class': classes, 'list': videos}
def categoryContent(self, tid, pg, filter, extend):
limit = 15
response = self.fetch(f'{self.host}/api.php/app/filter/vod?type_name={tid}&page={pg}&sort=hits&limit={limit}', headers=self.headers(), verify=False).json()
videos = self.arr2vods(response['data'])
page = int(pg)
# 接口返回的 pageCount/total 固定为 1/15,不可靠,按实际返回条数判断是否有下一页
pagecount = page + 1 if len(videos) == limit else page
return {
'page': page,
'pagecount': pagecount,
'limit': limit,
'total': pagecount * limit,
'list': videos
}
def searchContent(self, key, quick, pg='1'):
limit = 15
response = self.fetch(f'{self.host}/api.php/app/search/index?wd={key}&page={pg}&limit={limit}', headers=self.headers(), verify=False).json()
videos = self.arr2vods(response['data'])
page = int(pg)
# 接口返回的 pageCount/total 固定为 1/15,不可靠,按实际返回条数判断是否有下一页
pagecount = page + 1 if len(videos) == limit else page
return {
'page': page,
'pagecount': pagecount,
'limit': limit,
'total': pagecount * limit,
'list': videos
}
def detailContent(self, ids):
response = self.fetch(f'{self.host}/api.php/app/vod/get_detail?vod_id={ids[0]}', headers=self.headers(), verify=False).json()
data = response['data'][0]
shows, play_urls = [], []
raw_shows = data['vod_play_from'].split('$$$')
raw_urls_list = data['vod_play_url'].split('$$$')
for show_code, urls_str in zip(raw_shows, raw_urls_list):
need_parse, is_show, name, urls = 0, 0, show_code, []
for i in response['vodplayer']:
if i['from'] == show_code:
is_show = 1
need_parse = i['decode_status']
if show_code.casefold() != i['show'].casefold():
name = f"{i['show']}\u2005({show_code})"
break
if is_show == 1:
for url_item in urls_str.split('#'):
if '$' in url_item:
episode, url = url_item.split('$', 1)
urls.append(f"{episode}${show_code}@{int(need_parse)}@{url}")
if urls:
play_urls.append('#'.join(urls))
shows.append(name)
video = {
'vod_id': data['vod_id'],
'vod_name': data['vod_name'],
'vod_pic': data['vod_pic'],
'vod_remarks': data['vod_remarks'],
'vod_year': data['vod_year'],
'vod_area': data['vod_area'],
'vod_actor': data['vod_actor'],
'vod_director': data['vod_director'],
'vod_content': data['vod_content'],
'vod_play_from': '$$$'.join(shows),
'vod_play_url': '$$$'.join(play_urls),
'type_name': data['vod_class']
}
return {'list': [video]}
def playerContent(self, flag, vid, vip_flags):
play_from, need_parse, raw_url = vid.split('@', 2)
jx, url = 0, ''
if need_parse == '1':
try:
response = self.fetch(f'{self.host}/api.php/app/decode/url/?url={raw_url}&vodFrom={play_from}', headers=self.headers(), timeout=30, verify=False).json()
play_url = response['data']
if play_url.startswith('http'): url = play_url
except Exception:
pass
if not url:
url = raw_url
if re.search(r'(?:www\.iqiyi|v\.qq|v\.youku|www\.mgtv|www\.bilibili)\.com', raw_url):
jx = 1
return {'jx': jx, 'parse': 0, 'url': url, 'header': {'User-Agent': 'com.sunshine.tv/1.2.0 (Linux;Android 15) AndroidXMedia3/1.4.1'}}
def arr2vods(self, arr):
videos = []
for i in arr:
type_name = i.get('type_name')
if i.get('vod_class'):
type_name = f"{type_name},{i.get('vod_class', '')}"
videos.append({
'vod_id': i['vod_id'],
'vod_name': i['vod_name'],
'vod_pic': i['vod_pic'],
'vod_remarks': i['vod_remarks'],
'type_name': type_name,
'vod_year': i.get('vod_year')
})
return videos
def headers(self):
timestamp = str(int(time.time()))
nonce = ''.join(random.choice('0123456789') for _ in range(3))
ver, pkg = '3', 'com.sunshine.tv'
sign_str = f"finger=SF-C3B2B41F6EFFFF9869176CF68F6790E8F07506FC88632C94B4F5F0430D5498CA&id={pkg}&nonce={nonce}&sk=SK-thanks&time={timestamp}&v={ver}"
sign = hashlib.sha256(sign_str.encode('utf-8')).hexdigest().upper()
if not self.device_id:
device_id_cache_key = 'com.sunshine.tv_3qys_B7k7Dt56Rn'
self.device_id = self.getCache(device_id_cache_key)
if not (isinstance(self.device_id, str) and len(self.device_id) == 16):
self.device_id = ''.join(secrets.choice('0123456789abcdef') for _ in range(16))
self.setCache(device_id_cache_key, self.device_id)
return {
'User-Agent': 'okhttp/4.12.0',
'Accept': 'application/json',
'Accept-Encoding': 'gzip',
'x-aid': pkg,
'x-ave': ver,
'x-time': timestamp,
'x-nonc': nonce,
'x-sign': sign,
'x-device-id': self.device_id,
'x-device-brand': 'vivo',
'x-device-model': 'V2309A',
'x-update-id': '0245861b-2ebf-5524-389d-f983830651ec'
}
def init(self, extend=''):
pass
def homeVideoContent(self):
pass
def getName(self):
pass
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def destroy(self):
pass
def localProxy(self, param):
pass
+119
View File
@@ -0,0 +1,119 @@
# coding=utf-8
#!/usr/bin/python
# ========== 固定头部(所有TVBox爬虫通用,不可修改)==========
import sys
sys.path.append('..')
import json
import urllib.parse
import re
from lxml import etree
from urllib.parse import urljoin
from base.spider import Spider
class Spider(Spider):
def __init__(self):
# 唯一自定义:站点域名
self.host = "https://www.china-eae.com"
self.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"
}
# ==================== 固定函数:首页分类 ====================
def homeContent(self, filter):
res = self.fetch(self.host, headers=self.headers)
html = etree.HTML(res.text)
class_list = []
# 提取顶部导航分类
class_items = html.xpath('//ul[@class="stui-header__menu"]//div[@class="flickity-slider"]/li/a')
for item in class_items:
tid_name = item.xpath('./text()')[0].strip()
tid_url = urljoin(self.host, item.xpath('./@href')[0])
class_list.append({
"type_id": tid_url,
"type_name": tid_name
})
return {
'class': class_list,
'list': []
}
# ==================== 固定函数:分类分页 ====================
def categoryContent(self, tid, pg, filter, extend):
# tid是分类完整urlpg页码
page_url = f"{tid}?page={pg}"
res = self.fetch(page_url, headers=self.headers)
html = etree.HTML(res.text)
vod_items = html.xpath('//ul[@class="stui-vodlist clearfix"]/li')
vod_list = []
for li in vod_items:
vod = self.parse_vod_item(li)
vod_list.append(vod)
return {
'list': vod_list,
'page': int(pg),
'pagecount': 10
}
# ==================== 固定函数:搜索 ====================
def searchContent(self, key, quick, pg='1'):
wd = urllib.parse.quote(key)
search_url = f"{self.host}/msfw/search/-------------.html?wd={wd}&page={pg}"
res = self.fetch(search_url, headers=self.headers)
html = etree.HTML(res.text)
vod_items = html.xpath('//ul[@class="stui-vodlist clearfix"]/li')
vod_list = []
for li in vod_items:
vod = self.parse_vod_item(li)
vod_list.append(vod)
return {
'list': vod_list,
'page': int(pg),
'pagecount': 8,
'limit': 24,
'total': int(pg)*24
}
# ==================== 自定义工具:解析单条影片列表数据 ====================
def parse_vod_item(self, li_xpath):
vod_href = li_xpath.xpath('.//a[@class="stui-vodlist__thumb lazyload"]/@href')[0]
vod_id = vod_href.replace("/about/","").replace(".html","")
vod_pic = li_xpath.xpath('.//a[@class="stui-vodlist__thumb lazyload"]/@data-original')[0]
vod_name = li_xpath.xpath('.//h4[@class="title text-overflow"]/a/@title')[0]
vod_remarks = li_xpath.xpath('.//span[@class="pic-text text-right"]/text()')[0].strip()
try:
vod_actor = li_xpath.xpath('.//p[@class="text text-overflow text-muted hidden-xs"]/text()')[0].strip()
except:
vod_actor = ""
return {
"vod_id": vod_id,
"vod_name": vod_name,
"vod_pic": vod_pic,
"vod_remarks": vod_remarks,
"vod_actor": vod_actor
}
# ==================== 固定函数:详情页(播放线路,需补全播放地址解析) ====================
def detailContent(self, ids):
vod_list = []
for vid in ids:
detail_url = f"{self.host}/about/{vid}.html"
res = self.fetch(detail_url, headers=self.headers)
html = etree.HTML(res.text)
vod_name = html.xpath('//h1/@title')[0]
# 此处需自行解析页面内m3u8/mp4播放线路,示例占位
play_url = "需解析详情页script内播放地址"
vod_list.append({
"vod_id": vid,
"vod_name": vod_name,
"vod_play_url": play_url
})
return {"list": vod_list}
# ==================== 固定函数:播放器解析 ====================
def playerContent(self, flag, url, headers):
return {
'parse': 0,
'url': url,
'header': headers,
'playType': 1
}