TV-BOX/tvbox/江湖/py/py_nivod_cc.py
yuanwangokk-1 7fb1457e88 main
2025-10-14 22:54:33 +08:00

304 lines
No EOL
15 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- coding: utf-8 -*-
# @Author : 老王叔叔 for 泥視頻.CC with Multi-Source Support
# @Time : 2025/04/06
import sys
import requests
from lxml import etree
import json
import re
from urllib.parse import urlencode
sys.path.append('..')
from base.spider import Spider
class Spider(Spider):
def __init__(self):
self.home_url = 'https://www.nivod.cc'
self.headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36",
"Referer": "https://www.nivod.cc/",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
"Accept-Language": "zh-CN,zh;q=0.9",
"Connection": "keep-alive",
}
self.placeholder_pic = 'https://image.tmdb.org/t/p/w600_and_h900_bestv2/placeholder.jpg'
def init(self, extend=""):
pass
def getName(self):
return "泥視頻.CC"
def getDependence(self):
return []
def isVideoFormat(self, url):
return url.endswith('.m3u8') or url.endswith('.mp4')
def manualVideoCheck(self):
return False
def homeContent(self, filter):
categories = "電影$movie#電視劇$tv#綜藝$show#動漫$anime"
class_list = [{'type_id': v.split('$')[1], 'type_name': v.split('$')[0]} for v in categories.split('#')]
filters = {
'movie': [
{'key': 'class', 'name': '剧情', 'value': [{'n': v.split('$')[0], 'v': v.split('$')[1]} for v in "冒险$mao-xian#剧情$ju-qing#动作$dong-zuo#同性$tong-xing#喜剧$xi-ju#奇幻$qi-huan#恐怖$kong-bu#悬疑$xuan-yi#惊悚$jing-song#战争$zhan-zheng#歌舞$ge-wu#灾难$zai-nan#爱情$ai-qing#犯罪$fan-zui#科幻$ke-huan".split('#')]},
{'key': 'area', 'name': '地区', 'value': [{'n': v.split('$')[0], 'v': v.split('$')[1]} for v in "大陆$cn#香港$hk#台湾$tw#欧美$west#泰国$th#新马$sg-my#其他$other".split('#')]},
{'key': 'year', 'name': '年份', 'value': [{'n': v, 'v': v} for v in ["2025", "2024", "2023", "2022", "2021", "2020", "2019-2010", "2009-2000", "90年代", "80年代", "更早"]]}
],
'tv': [
{'key': 'class', 'name': '剧情', 'value': [{'n': v.split('$')[0], 'v': v.split('$')[1]} for v in "剧情$ju-qing#动作$dong-zuo#历史$li-shi#历险$mao-xian#古装$gu-zhuang#同性$tong-xing#喜剧$xi-ju#奇幻$qi-huan#家庭$jia-ting#悬疑$xuan-yi#惊悚$jing-song#战争$zhan-zheng#武侠$wu-xia#爱情$ai-qing#科幻$ke-huan#罪案$zui-an".split('#')]},
{'key': 'area', 'name': '地区', 'value': [{'n': v.split('$')[0], 'v': v.split('$')[1]} for v in "大陆$cn#香港$hk#台湾$tw#日本$jp#韩国$kr#欧美$west#泰国$th#新马$sg-my".split('#')]},
{'key': 'year', 'name': '年份', 'value': [{'n': v, 'v': v} for v in ["2025", "2024", "2023", "2022", "2021", "2020", "2019-2015", "2014-2010", "2009-2000", "90年代", "80年代", "更早"]]}
],
'show': [
{'key': 'class', 'name': '剧情', 'value': [{'n': v.split('$')[0], 'v': v.split('$')[1]} for v in "搞笑$gao-xiao#音乐$yin-yue#真人秀$zhen-ren-xiu#脱口秀$tuo-kou-xiu".split('#')]},
{'key': 'area', 'name': '地区', 'value': [{'n': v.split('$')[0], 'v': v.split('$')[1]} for v in "大陆$cn#韩国$kr#欧美$west#其它$other".split('#')]},
{'key': 'year', 'name': '年份', 'value': [{'n': v, 'v': v} for v in ["2025", "2024", "2023", "2022", "2021", "2020", "2019-2015", "2014-2010", "2009-2000", "90年代", "80年代", "更早"]]}
],
'anime': [
{'key': 'class', 'name': '剧情', 'value': [{'n': v.split('$')[0], 'v': v.split('$')[1]} for v in "冒险$mao-xian#动画电影$movie#推理$tui-li#校园$xiao-yuan#治愈$zhi-yu#泡面$pao-mian#热血$re-xue#科幻$ke-huan#魔幻$mo-huan".split('#')]},
{'key': 'area', 'name': '地区', 'value': [{'n': v.split('$')[0], 'v': v.split('$')[1]} for v in "大陆$cn#日本$jp#欧美$west".split('#')]},
{'key': 'year', 'name': '年份', 'value': [{'n': v, 'v': v} for v in ["2025", "2024", "2023", "2022", "2021", "2020", "2019-2015", "2014-2010", "2009-2000", "90年代", "80年代", "更早"]]}
]
}
return {'class': class_list, 'filters': filters if filter else {}}
def homeVideoContent(self):
result = {'list': []}
try:
res = requests.get(self.home_url, headers=self.headers)
res.encoding = 'utf-8'
root = etree.HTML(res.text)
data_list = root.xpath('//div[contains(@class, "qy-mod-link-wrap")]/a')
for i in data_list:
name_nodes = i.xpath('.//picture[@class="video-item-preview-img"]/img/@alt')
vod_name = name_nodes[0].strip() if name_nodes else None
if not vod_name:
vod_name = i.xpath('./@title')
vod_name = vod_name[0].strip() if vod_name else "未知"
vod_id = i.get('href', '')
pic_nodes = i.xpath('.//picture[@class="video-item-preview-img"]/img/@src')
vod_pic = pic_nodes[0] if pic_nodes else self.placeholder_pic
if vod_pic.startswith('/'):
vod_pic = self.home_url + vod_pic
remark_nodes = i.xpath('.//span[contains(@class, "qy-mod-label")]/text()')
vod_remarks = remark_nodes[0].strip() if remark_nodes else ''
result['list'].append({
'vod_id': vod_id,
'vod_name': vod_name,
'vod_pic': vod_pic,
'vod_remarks': vod_remarks
})
result['list'] = result['list'][:10]
except Exception as e:
print(f"Error in homeVideoContent: {e}")
return result
def categoryContent(self, tid, pg, filter, ext):
result = {'list': []}
_year = ext.get('year', '')
_class = ext.get('class', '')
_area = ext.get('area', '')
params = {
'channel': tid,
'region': _area,
'class': _class,
'year': _year,
'page': pg
}
url = f"{self.home_url}/filter.html?{urlencode(params)}"
# 如果頁碼超過 5直接返回空結果
if int(pg) > 5:
result['page'] = int(pg)
result['pagecount'] = 5
result['limit'] = 0
result['total'] = 240
return result
try:
res = requests.get(url, headers=self.headers)
res.encoding = 'utf-8'
print(f"categoryContent URL: {url}")
print(f"categoryContent Response Status: {res.status_code}")
print(f"categoryContent HTML length: {len(res.text)}")
root = etree.HTML(res.text)
data_list = root.xpath('//li[contains(@class, "qy-mod-li")]')
for i in data_list:
vod_id = i.xpath('.//a/@href')[0] if i.xpath('.//a/@href') else ''
name_nodes = i.xpath('.//a/@title')
vod_name = name_nodes[0].strip() if name_nodes else "未知"
pic_nodes = i.xpath('.//div[@class="qy-mod-cover"]/@style')
vod_pic = None
if pic_nodes:
style = pic_nodes[0]
match = re.search(r'url\((.*?)\)', style)
vod_pic = match.group(1).strip() if match else None
if not vod_pic:
vod_pic = self.placeholder_pic
if vod_pic.startswith('/'):
vod_pic = self.home_url + vod_pic
remark_nodes = i.xpath('.//span[@class="qy-mod-label"]/text()')
vod_remarks = remark_nodes[0].strip() if remark_nodes else ''
result['list'].append({
'vod_id': vod_id,
'vod_name': vod_name,
'vod_pic': vod_pic,
'vod_remarks': vod_remarks
})
# 假設每頁最多 48 個項目,網站分頁上限為 5 頁
current_items = len(data_list)
total_pages = 5 # 網站分頁上限為 5
if current_items < 48: # 如果當前頁項目少於 48假設是最後一頁
total_pages = int(pg)
total_items = (int(pg) - 1) * 48 + current_items if total_pages == int(pg) else total_pages * 48
result['page'] = int(pg)
result['pagecount'] = total_pages
result['limit'] = current_items
result['total'] = total_items
except Exception as e:
print(f"Error in categoryContent: {e}")
return result
def detailContent(self, array):
result = {'list': []}
ids = array[0]
detail_url = f"{self.home_url}{ids}"
try:
res = requests.get(detail_url, headers=self.headers)
res.encoding = 'utf-8'
root = etree.HTML(res.text)
vod_name = root.xpath('//div[@class="right-title"]/text()')[0].strip() if root.xpath('//div[@class="right-title"]') else "未知"
vod_year = root.xpath('//div[@id="postYear"]/text()')[0].strip() if root.xpath('//div[@id="postYear"]') else ""
vod_area = root.xpath('//div[@id="region"]/text()')[0].strip() if root.xpath('//div[@id="region"]') else ""
vod_content = root.xpath('//div[@id="show-desc"]/text()')[0].strip() if root.xpath('//div[@id="show-desc"]') else ""
vod_remarks = root.xpath('//div[@id="updateTxt"]/text()')[0].strip() if root.xpath('//div[@id="updateTxt"]') else ""
vod_actor = root.xpath('//div[@id="actors"]/text()')[0].strip() if root.xpath('//div[@id="actors"]') else ""
vod_director = root.xpath('//div[@id="director"]/text()')[0].strip() if root.xpath('//div[@id="director"]') else ""
vod_pic = root.xpath('//img[@class="left-img"]/@src')[0] if root.xpath('//img[@class="left-img"]') else self.placeholder_pic
if vod_pic.startswith('/'):
vod_pic = self.home_url + vod_pic
episodes = root.xpath('//div[@id="list-jj"]/a')
if not episodes:
vod = {
'vod_id': ids,
'vod_name': vod_name,
'vod_pic': vod_pic,
'type_name': '',
'vod_year': vod_year,
'vod_area': vod_area,
'vod_remarks': vod_remarks,
'vod_actor': vod_actor,
'vod_director': vod_director,
'vod_content': vod_content,
'vod_play_from': '泥視頻',
'vod_play_url': '第1集$https://www.nivod.cc/vodplay/202552243/ep1'
}
else:
play_from = set()
play_urls = {}
for ep in episodes[::-1]:
ep_name = ep.xpath('.//div[@class="item"]/text()')[0].strip() if ep.xpath('.//div[@class="item"]') else "未知"
ep_url = self.home_url + ep.get('href', '')
vod_id = ids.split('/')[2]
ep_id = ep_url.split('/')[-1]
xhr_url = f"{self.home_url}/xhr_playinfo/{vod_id}-{ep_id}"
res = requests.get(xhr_url, headers=self.headers)
res.encoding = 'utf-8'
data = res.json()
if 'pdatas' in data and data['pdatas']:
for source in data['pdatas']:
source_name = source['from']
play_from.add(source_name)
if source_name not in play_urls:
play_urls[source_name] = []
play_urls[source_name].append(f"{ep_name}${source['playurl']}")
vod_play_from = '$$$'.join(play_from)
vod_play_url = '$$$'.join(['#'.join(play_urls[source]) for source in play_from])
vod = {
'vod_id': ids,
'vod_name': vod_name,
'vod_pic': vod_pic,
'type_name': '',
'vod_year': vod_year,
'vod_area': vod_area,
'vod_remarks': vod_remarks,
'vod_actor': vod_actor,
'vod_director': vod_director,
'vod_content': vod_content,
'vod_play_from': vod_play_from,
'vod_play_url': vod_play_url
}
result['list'].append(vod)
except Exception as e:
print(f"Error in detailContent: {e}")
result['list'].append({
'vod_id': ids,
'vod_name': '未知',
'vod_pic': self.placeholder_pic,
'vod_play_from': '泥視頻',
'vod_play_url': ''
})
return result
def searchContent(self, key, quick, pg='1'):
result = {'list': []}
try:
search_url = f"{self.home_url}/search_x.html?keyword={key}&page={pg}"
res = requests.get(search_url, headers=self.headers)
res.encoding = 'utf-8'
root = etree.HTML(res.text)
data_list = root.xpath('//a[contains(@class, "qy-mod-link")]')
for item in data_list:
name_nodes = item.xpath('.//picture[@class="video-item-preview-img"]/img/@alt')
vod_name = name_nodes[0].strip() if name_nodes else None
if not vod_name:
vod_name = item.xpath('./@title')
vod_name = vod_name[0].strip() if vod_name else "未知"
vod_id = item.get('href', '')
pic_nodes = item.xpath('.//picture[@class="video-item-preview-img"]/img/@src')
vod_pic = pic_nodes[0] if pic_nodes else self.placeholder_pic
if vod_pic.startswith('/'):
vod_pic = self.home_url + vod_pic
vod_remarks = ''
result['list'].append({
'vod_id': vod_id,
'vod_name': vod_name,
'vod_pic': vod_pic,
'vod_remarks': vod_remarks
})
except Exception as e:
print(f"Error in searchContent: {e}")
return result
def playerContent(self, flag, id, vipFlags):
result = {}
try:
play_url = id.split('$')[1] if '$' in id else id
result = {
'url': play_url,
'header': json.dumps(self.headers),
'parse': 0,
'playUrl': ''
}
except Exception as e:
print(f"Error in playerContent: {e}")
result = {'url': '', 'parse': 0}
return result
def localProxy(self, param):
return [200, "video/MP2T", {}, b""]
def destroy(self):
pass