mirror of
https://github.com/cluntop/tvbox.git
synced 2026-01-10 17:38:35 +01:00
228 lines
8.6 KiB
Python
Executable file
228 lines
8.6 KiB
Python
Executable file
# coding=utf-8
|
|
#!/usr/bin/python
|
|
import sys
|
|
import requests
|
|
import re
|
|
import json
|
|
from urllib.parse import urljoin, unquote
|
|
sys.path.append('..')
|
|
from base.spider import Spider
|
|
|
|
class Spider(Spider):
|
|
def init(self, extend=""):
|
|
self.host = "https://618636.xyz"
|
|
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',
|
|
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',
|
|
'Accept-Language': 'zh-CN,zh;q=0.9',
|
|
'Connection': 'keep-alive',
|
|
'Referer': self.host
|
|
}
|
|
print(f"七区: {self.host}")
|
|
|
|
def getName(self):
|
|
return "七区"
|
|
|
|
def isVideoFormat(self, url):
|
|
|
|
if not url: return False
|
|
url_lower = url.lower()
|
|
if '.html' in url_lower: return False
|
|
return '.m3u8' in url_lower or '.mp4' in url_lower
|
|
|
|
def manualVideoCheck(self):
|
|
return False
|
|
|
|
def _decrypt_title(self, encrypted_text):
|
|
try:
|
|
if not encrypted_text: return ""
|
|
decrypted_chars = []
|
|
for char in encrypted_text:
|
|
decrypted_chars.append(chr(ord(char) ^ 128))
|
|
result = ''.join(decrypted_chars)
|
|
|
|
if sum(1 for c in result if ord(c) < 32 and c not in '\t\n\r') > len(result) * 0.3:
|
|
return encrypted_text
|
|
return result
|
|
except:
|
|
return encrypted_text
|
|
|
|
def _extractVideoItems(self, html_content):
|
|
vods = []
|
|
try:
|
|
# 兼容多种列表模板
|
|
items = re.findall(r'<a[^>]*class="thumbnail"[^>]*href="([^"]+)"[^>]*>.*?<img[^>]*data-original="([^"]+)"[^>]*>.*?<span[^>]*class="title[^"]*"[^>]*>(.*?)</span>', html_content, re.S)
|
|
if not items:
|
|
items = re.findall(r'<a[^>]*href="([^"]+)"[^>]*title="([^"]*)"[^>]*>.*?<img[^>]*src="([^"]+)"', html_content, re.S)
|
|
|
|
for href, img, title_raw in items:
|
|
title = self._decrypt_title(title_raw.strip())
|
|
if not title: title = title_raw.strip()
|
|
|
|
# 补全链接
|
|
vid_url = href if href.startswith('http') else urljoin(self.host, href)
|
|
pic_url = img if img.startswith('http') else urljoin(self.host, img)
|
|
|
|
vods.append({
|
|
'vod_id': vid_url,
|
|
'vod_name': title,
|
|
'vod_pic': pic_url,
|
|
'vod_remarks': ''
|
|
})
|
|
except Exception as e:
|
|
print(f"List parsing error: {e}")
|
|
return vods
|
|
|
|
def homeContent(self, filter):
|
|
result = {}
|
|
classes = [
|
|
{'type_id': '/index.php/vod/type/id/37.html', 'type_name': '国产AV'},
|
|
{'type_id': '/index.php/vod/type/id/43.html', 'type_name': '探花AV'},
|
|
{'type_id': '/index.php/vod/type/id/40.html', 'type_name': '网黄UP主'},
|
|
{'type_id': '/index.php/vod/type/id/49.html', 'type_name': '绿帽淫妻'},
|
|
{'type_id': '/index.php/vod/type/id/44.html', 'type_name': '国产传媒'},
|
|
{'type_id': '/index.php/vod/type/id/41.html', 'type_name': '福利姬'},
|
|
{'type_id': '/index.php/vod/type/id/39.html', 'type_name': '字幕'},
|
|
{'type_id': '/index.php/vod/type/id/45.html', 'type_name': '水果派'},
|
|
{'type_id': '/index.php/vod/type/id/42.html', 'type_name': '主播直播'},
|
|
{'type_id': '/index.php/vod/type/id/38.html', 'type_name': '欧美'},
|
|
{'type_id': '/index.php/vod/type/id/66.html', 'type_name': 'FC2'},
|
|
{'type_id': '/index.php/vod/type/id/46.html', 'type_name': '性爱教学'},
|
|
{'type_id': '/index.php/vod/type/id/48.html', 'type_name': '三及片'},
|
|
{'type_id': '/index.php/vod/type/id/47.html', 'type_name': '动漫'}
|
|
]
|
|
result['class'] = classes
|
|
try:
|
|
res = requests.get(self.host, headers=self.headers, timeout=5)
|
|
result['list'] = self._extractVideoItems(res.text)
|
|
except:
|
|
result['list'] = []
|
|
return result
|
|
|
|
def homeVideoContent(self):
|
|
return self.homeContent(False)
|
|
|
|
def categoryContent(self, tid, pg, filter, extend):
|
|
result = {}
|
|
pg = int(pg) if pg else 1
|
|
url = tid if tid.startswith('http') else urljoin(self.host, tid)
|
|
if pg > 1:
|
|
url = url.replace('.html', f'/page/{pg}.html') if url.endswith('.html') else f"{url}/page/{pg}.html"
|
|
|
|
try:
|
|
res = requests.get(url, headers=self.headers, timeout=120)
|
|
res.encoding = 'utf-8'
|
|
vods = self._extractVideoItems(res.text)
|
|
result['list'] = vods
|
|
result['page'] = pg
|
|
result['pagecount'] = pg + 1 if len(vods) > 0 else pg
|
|
result['limit'] = 20
|
|
result['total'] = 9999
|
|
except:
|
|
result['list'] = []
|
|
return result
|
|
|
|
def detailContent(self, ids):
|
|
vid = ids[0]
|
|
url = vid if vid.startswith('http') else urljoin(self.host, vid)
|
|
vod = {
|
|
'vod_id': vid,
|
|
'vod_name': '',
|
|
'vod_pic': '',
|
|
'vod_remarks': '',
|
|
'vod_content': ''
|
|
}
|
|
|
|
try:
|
|
res = requests.get(url, headers=self.headers, timeout=120)
|
|
res.encoding = 'utf-8'
|
|
html = res.text
|
|
|
|
t_match = re.search(r'<h1[^>]*>(.*?)</h1>', html) or re.search(r'<title>(.*?)</title>', html)
|
|
if t_match:
|
|
raw_title = t_match.group(1).split('-')[0].strip()
|
|
decrypted = self._decrypt_title(raw_title)
|
|
vod['vod_name'] = decrypted if len(decrypted) > 1 else raw_title
|
|
|
|
img_match = re.search(r'class="(?:poster|dyimg)"[^>]*src="([^"]+)"', html)
|
|
if img_match:
|
|
vod['vod_pic'] = img_match.group(1) if img_match.group(1).startswith('http') else urljoin(self.host, img_match.group(1))
|
|
|
|
vod['vod_play_from'] = '七区线路'
|
|
vod['vod_play_url'] = f'立即播放${url}'
|
|
|
|
except Exception as e:
|
|
print(f"Detail error: {e}")
|
|
|
|
return {'list': [vod]}
|
|
|
|
def playerContent(self, flag, id, vipFlags):
|
|
|
|
url = id
|
|
headers = self.headers.copy()
|
|
headers['Referer'] = url
|
|
|
|
try:
|
|
|
|
if self.isVideoFormat(url):
|
|
return {'parse': 0, 'playUrl': '', 'url': url, 'header': headers}
|
|
|
|
res = requests.get(url, headers=headers, timeout=120, verify=False)
|
|
html = res.text
|
|
current_url = res.url
|
|
|
|
video_url = None
|
|
|
|
json_matches = re.findall(r'["\'](https?://[^"\']+\.(?:m3u8|mp4)[^"\']*)["\']', html)
|
|
for m in json_matches:
|
|
if '.html' not in m: # 再次过滤
|
|
video_url = m
|
|
break
|
|
|
|
if not video_url:
|
|
param_matches = re.findall(r'(?:v|url|src)\s*=\s*["\']([^"\']+\.(?:m3u8|mp4)[^"\']*)["\']', html)
|
|
for m in param_matches:
|
|
clean = m.replace('\\', '')
|
|
if not clean.startswith('http'):
|
|
clean = urljoin(current_url, clean)
|
|
if '.html' not in clean:
|
|
video_url = clean
|
|
break
|
|
|
|
if video_url and self.isVideoFormat(video_url):
|
|
return {
|
|
'parse': 0,
|
|
'playUrl': '',
|
|
'url': video_url,
|
|
'header': headers
|
|
}
|
|
|
|
return {
|
|
'parse': 1,
|
|
'playUrl': '',
|
|
'url': url,
|
|
'header': headers
|
|
}
|
|
|
|
except Exception as e:
|
|
print(f"Player error: {e}")
|
|
return {'parse': 1, 'playUrl': '', 'url': url, 'header': headers}
|
|
|
|
def searchContent(self, key, quick, pg="1"):
|
|
result = {'list': []}
|
|
try:
|
|
search_url = f"{self.host}/index.php/vod/search/wd/{requests.utils.quote(key)}/page/{pg}.html"
|
|
res = requests.get(search_url, headers=self.headers, timeout=120)
|
|
res.encoding = 'utf-8'
|
|
vods = self._extractVideoItems(res.text)
|
|
result['list'] = vods
|
|
result['page'] = int(pg)
|
|
result['pagecount'] = int(pg) + 1 if len(vods) > 0 else int(pg)
|
|
result['limit'] = 20
|
|
result['total'] = 9999
|
|
except:
|
|
pass
|
|
return result
|
|
|
|
def localProxy(self, params):
|
|
return None
|