Sync all projects
This commit is contained in:
@@ -0,0 +1,404 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 🌈 Love
|
||||
import json
|
||||
import random
|
||||
import re
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
from base64 import b64decode, b64encode
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import requests
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Util.Padding import unpad
|
||||
from pyquery import PyQuery as pq
|
||||
sys.path.append('..')
|
||||
from base.spider import Spider
|
||||
|
||||
|
||||
class Spider(Spider):
|
||||
|
||||
def init(self, extend=""):
|
||||
try:self.proxies = json.loads(extend)
|
||||
except:self.proxies = {}
|
||||
self.headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.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,application/signed-exchange;v=b3;q=0.7',
|
||||
'Accept-Language': 'zh-CN,zh;q=0.9',
|
||||
'Connection': 'keep-alive',
|
||||
'Cache-Control': 'no-cache',
|
||||
}
|
||||
# Use working dynamic URLs directly
|
||||
self.host = self.get_working_host()
|
||||
self.headers.update({'Origin': self.host, 'Referer': f"{self.host}/"})
|
||||
self.log(f"使用站点: {self.host}")
|
||||
print(f"使用站点: {self.host}")
|
||||
pass
|
||||
|
||||
def getName(self):
|
||||
return "🌈 51吸瓜"
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
# Treat direct media formats as playable without parsing
|
||||
return any(ext in (url or '') for ext in ['.m3u8', '.mp4', '.ts'])
|
||||
|
||||
def manualVideoCheck(self):
|
||||
return False
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
def homeContent(self, filter):
|
||||
try:
|
||||
response = requests.get(self.host, headers=self.headers, proxies=self.proxies, timeout=15)
|
||||
if response.status_code != 200:
|
||||
return {'class': [], 'list': []}
|
||||
|
||||
data = self.getpq(response.text)
|
||||
result = {}
|
||||
classes = []
|
||||
|
||||
# Try to get categories from different possible locations
|
||||
category_selectors = [
|
||||
'.category-list ul li',
|
||||
'.nav-menu li',
|
||||
'.menu li',
|
||||
'nav ul li'
|
||||
]
|
||||
|
||||
for selector in category_selectors:
|
||||
for k in data(selector).items():
|
||||
link = k('a')
|
||||
href = (link.attr('href') or '').strip()
|
||||
name = (link.text() or '').strip()
|
||||
# Skip placeholder or invalid entries
|
||||
if not href or href == '#' or not name:
|
||||
continue
|
||||
classes.append({
|
||||
'type_name': name,
|
||||
'type_id': href
|
||||
})
|
||||
if classes:
|
||||
break
|
||||
|
||||
# If no categories found, create some default ones
|
||||
if not classes:
|
||||
classes = [
|
||||
{'type_name': '首页', 'type_id': '/'},
|
||||
{'type_name': '最新', 'type_id': '/latest/'},
|
||||
{'type_name': '热门', 'type_id': '/hot/'}
|
||||
]
|
||||
|
||||
result['class'] = classes
|
||||
result['list'] = self.getlist(data('#index article a'))
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
print(f"homeContent error: {e}")
|
||||
return {'class': [], 'list': []}
|
||||
|
||||
def homeVideoContent(self):
|
||||
try:
|
||||
response = requests.get(self.host, headers=self.headers, proxies=self.proxies, timeout=15)
|
||||
if response.status_code != 200:
|
||||
return {'list': []}
|
||||
data = self.getpq(response.text)
|
||||
return {'list': self.getlist(data('#index article a, #archive article a'))}
|
||||
except Exception as e:
|
||||
print(f"homeVideoContent error: {e}")
|
||||
return {'list': []}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
try:
|
||||
if '@folder' in tid:
|
||||
id = tid.replace('@folder', '')
|
||||
videos = self.getfod(id)
|
||||
else:
|
||||
# Build URL properly
|
||||
if tid.startswith('/'):
|
||||
if pg and pg != '1':
|
||||
url = f"{self.host}{tid}page/{pg}/"
|
||||
else:
|
||||
url = f"{self.host}{tid}"
|
||||
else:
|
||||
url = f"{self.host}/{tid}"
|
||||
|
||||
response = requests.get(url, headers=self.headers, proxies=self.proxies, timeout=15)
|
||||
if response.status_code != 200:
|
||||
return {'list': [], 'page': pg, 'pagecount': 1, 'limit': 90, 'total': 0}
|
||||
|
||||
data = self.getpq(response.text)
|
||||
videos = self.getlist(data('#archive article a, #index article a'), tid)
|
||||
|
||||
result = {}
|
||||
result['list'] = videos
|
||||
result['page'] = pg
|
||||
result['pagecount'] = 1 if '@folder' in tid else 99999
|
||||
result['limit'] = 90
|
||||
result['total'] = 999999
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
print(f"categoryContent error: {e}")
|
||||
return {'list': [], 'page': pg, 'pagecount': 1, 'limit': 90, 'total': 0}
|
||||
|
||||
def detailContent(self, ids):
|
||||
try:
|
||||
url = f"{self.host}{ids[0]}" if not ids[0].startswith('http') else ids[0]
|
||||
response = requests.get(url, headers=self.headers, proxies=self.proxies, timeout=15)
|
||||
|
||||
if response.status_code != 200:
|
||||
return {'list': [{'vod_play_from': '51吸瓜', 'vod_play_url': f'页面加载失败${url}'}]}
|
||||
|
||||
data = self.getpq(response.text)
|
||||
vod = {'vod_play_from': '51吸瓜'}
|
||||
|
||||
# Get content/description
|
||||
try:
|
||||
clist = []
|
||||
if data('.tags .keywords a'):
|
||||
for k in data('.tags .keywords a').items():
|
||||
title = k.text()
|
||||
href = k.attr('href')
|
||||
if title and href:
|
||||
clist.append('[a=cr:' + json.dumps({'id': href, 'name': title}) + '/]' + title + '[/a]')
|
||||
vod['vod_content'] = ' '.join(clist) if clist else data('.post-title').text()
|
||||
except:
|
||||
vod['vod_content'] = data('.post-title').text() or '51吸瓜视频'
|
||||
|
||||
# Get video URLs (build episode list when multiple players exist)
|
||||
try:
|
||||
plist = []
|
||||
used_names = set()
|
||||
if data('.dplayer'):
|
||||
for c, k in enumerate(data('.dplayer').items(), start=1):
|
||||
config_attr = k.attr('data-config')
|
||||
if config_attr:
|
||||
try:
|
||||
config = json.loads(config_attr)
|
||||
video_url = config.get('video', {}).get('url', '')
|
||||
# Determine a readable episode name from nearby headings if present
|
||||
ep_name = ''
|
||||
try:
|
||||
parent = k.parents().eq(0)
|
||||
# search up to a few ancestors for a heading text
|
||||
for _ in range(3):
|
||||
if not parent: break
|
||||
heading = parent.find('h2, h3, h4').eq(0).text() or ''
|
||||
heading = heading.strip()
|
||||
if heading:
|
||||
ep_name = heading
|
||||
break
|
||||
parent = parent.parents().eq(0)
|
||||
except Exception:
|
||||
ep_name = ''
|
||||
base_name = ep_name if ep_name else f"视频{c}"
|
||||
name = base_name
|
||||
count = 2
|
||||
# Ensure the name is unique
|
||||
while name in used_names:
|
||||
name = f"{base_name} {count}"
|
||||
count += 1
|
||||
used_names.add(name)
|
||||
if video_url:
|
||||
self.log(f"解析到视频: {name} -> {video_url}")
|
||||
print(f"解析到视频: {name} -> {video_url}")
|
||||
plist.append(f"{name}${video_url}")
|
||||
except:
|
||||
continue
|
||||
|
||||
if plist:
|
||||
self.log(f"拼装播放列表,共{len(plist)}个")
|
||||
print(f"拼装播放列表,共{len(plist)}个")
|
||||
vod['vod_play_url'] = '#'.join(plist)
|
||||
else:
|
||||
vod['vod_play_url'] = f"未找到视频源${url}"
|
||||
|
||||
except Exception as e:
|
||||
vod['vod_play_url'] = f"视频解析失败${url}"
|
||||
|
||||
return {'list': [vod]}
|
||||
|
||||
except Exception as e:
|
||||
print(f"detailContent error: {e}")
|
||||
return {'list': [{'vod_play_from': '51吸瓜', 'vod_play_url': f'详情页加载失败${ids[0] if ids else ""}'}]}
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
try:
|
||||
url = f"{self.host}/search/{key}/{pg}" if pg != "1" else f"{self.host}/search/{key}/"
|
||||
response = requests.get(url, headers=self.headers, proxies=self.proxies, timeout=15)
|
||||
|
||||
if response.status_code != 200:
|
||||
return {'list': [], 'page': pg}
|
||||
|
||||
data = self.getpq(response.text)
|
||||
videos = self.getlist(data('#archive article a, #index article a'))
|
||||
return {'list': videos, 'page': pg}
|
||||
|
||||
except Exception as e:
|
||||
print(f"searchContent error: {e}")
|
||||
return {'list': [], 'page': pg}
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
url = id
|
||||
p = 1
|
||||
if self.isVideoFormat(url):
|
||||
# m3u8/mp4 direct play; when using proxy setting, wrap to proxy for m3u8
|
||||
if '.m3u8' in url:
|
||||
url = self.proxy(url)
|
||||
p = 0
|
||||
self.log(f"播放请求: parse={p}, url={url}")
|
||||
print(f"播放请求: parse={p}, url={url}")
|
||||
return {'parse': p, 'url': url, 'header': self.headers}
|
||||
|
||||
def localProxy(self, param):
|
||||
if param.get('type') == 'img':
|
||||
res=requests.get(param['url'], headers=self.headers, proxies=self.proxies, timeout=10)
|
||||
return [200,res.headers.get('Content-Type'),self.aesimg(res.content)]
|
||||
elif param.get('type') == 'm3u8':return self.m3Proxy(param['url'])
|
||||
else:return self.tsProxy(param['url'])
|
||||
|
||||
def proxy(self, data, type='m3u8'):
|
||||
if data and len(self.proxies):return f"{self.getProxyUrl()}&url={self.e64(data)}&type={type}"
|
||||
else:return data
|
||||
|
||||
def m3Proxy(self, url):
|
||||
url=self.d64(url)
|
||||
ydata = requests.get(url, headers=self.headers, proxies=self.proxies, allow_redirects=False)
|
||||
data = ydata.content.decode('utf-8')
|
||||
if ydata.headers.get('Location'):
|
||||
url = ydata.headers['Location']
|
||||
data = requests.get(url, headers=self.headers, proxies=self.proxies).content.decode('utf-8')
|
||||
lines = data.strip().split('\n')
|
||||
last_r = url[:url.rfind('/')]
|
||||
parsed_url = urlparse(url)
|
||||
durl = parsed_url.scheme + "://" + parsed_url.netloc
|
||||
iskey=True
|
||||
for index, string in enumerate(lines):
|
||||
if iskey and 'URI' in string:
|
||||
pattern = r'URI="([^"]*)"'
|
||||
match = re.search(pattern, string)
|
||||
if match:
|
||||
lines[index] = re.sub(pattern, f'URI="{self.proxy(match.group(1), "mkey")}"', string)
|
||||
iskey=False
|
||||
continue
|
||||
if '#EXT' not in string:
|
||||
if 'http' not in string:
|
||||
domain = last_r if string.count('/') < 2 else durl
|
||||
string = domain + ('' if string.startswith('/') else '/') + string
|
||||
lines[index] = self.proxy(string, string.split('.')[-1].split('?')[0])
|
||||
data = '\n'.join(lines)
|
||||
return [200, "application/vnd.apple.mpegur", data]
|
||||
|
||||
def tsProxy(self, url):
|
||||
url = self.d64(url)
|
||||
data = requests.get(url, headers=self.headers, proxies=self.proxies, stream=True)
|
||||
return [200, data.headers['Content-Type'], data.content]
|
||||
|
||||
def e64(self, text):
|
||||
try:
|
||||
text_bytes = text.encode('utf-8')
|
||||
encoded_bytes = b64encode(text_bytes)
|
||||
return encoded_bytes.decode('utf-8')
|
||||
except Exception as e:
|
||||
print(f"Base64编码错误: {str(e)}")
|
||||
return ""
|
||||
|
||||
def d64(self, encoded_text):
|
||||
try:
|
||||
encoded_bytes = encoded_text.encode('utf-8')
|
||||
decoded_bytes = b64decode(encoded_bytes)
|
||||
return decoded_bytes.decode('utf-8')
|
||||
except Exception as e:
|
||||
print(f"Base64解码错误: {str(e)}")
|
||||
return ""
|
||||
|
||||
def get_working_host(self):
|
||||
"""Get working host from known dynamic URLs"""
|
||||
# Known working URLs from the dynamic gateway
|
||||
dynamic_urls = [
|
||||
'https://artist.vgwtswi.xyz',
|
||||
'https://ability.vgwtswi.xyz',
|
||||
'https://am.vgwtswi.xyz'
|
||||
]
|
||||
|
||||
# Test each URL to find a working one
|
||||
for url in dynamic_urls:
|
||||
try:
|
||||
response = requests.get(url, headers=self.headers, proxies=self.proxies, timeout=10)
|
||||
if response.status_code == 200:
|
||||
# Verify it has the expected content structure
|
||||
data = self.getpq(response.text)
|
||||
articles = data('#index article a')
|
||||
if len(articles) > 0:
|
||||
self.log(f"选用可用站点: {url}")
|
||||
print(f"选用可用站点: {url}")
|
||||
return url
|
||||
except Exception as e:
|
||||
continue
|
||||
|
||||
# Fallback to first URL if none work (better than crashing)
|
||||
self.log(f"未检测到可用站点,回退: {dynamic_urls[0]}")
|
||||
print(f"未检测到可用站点,回退: {dynamic_urls[0]}")
|
||||
return dynamic_urls[0]
|
||||
|
||||
|
||||
def getlist(self, data, tid=''):
|
||||
videos = []
|
||||
l = '/mrdg' in tid
|
||||
for k in data.items():
|
||||
a = k.attr('href')
|
||||
b = k('h2').text()
|
||||
# Some pages might not include datePublished; use a fallback
|
||||
c = k('span[itemprop="datePublished"]').text() or k('.post-meta, .entry-meta, time').text()
|
||||
if a and b:
|
||||
videos.append({
|
||||
'vod_id': f"{a}{'@folder' if l else ''}",
|
||||
'vod_name': b.replace('\n', ' '),
|
||||
'vod_pic': self.getimg(k('script').text()),
|
||||
'vod_remarks': c or '',
|
||||
'vod_tag': 'folder' if l else '',
|
||||
'style': {"type": "rect", "ratio": 1.33}
|
||||
})
|
||||
return videos
|
||||
|
||||
def getfod(self, id):
|
||||
url = f"{self.host}{id}"
|
||||
data = self.getpq(requests.get(url, headers=self.headers, proxies=self.proxies).text)
|
||||
vdata=data('.post-content[itemprop="articleBody"]')
|
||||
r=['.txt-apps','.line','blockquote','.tags','.content-tabs']
|
||||
for i in r:vdata.remove(i)
|
||||
p=vdata('p')
|
||||
videos=[]
|
||||
for i,x in enumerate(vdata('h2').items()):
|
||||
c=i*2
|
||||
videos.append({
|
||||
'vod_id': p.eq(c)('a').attr('href'),
|
||||
'vod_name': p.eq(c).text(),
|
||||
'vod_pic': f"{self.getProxyUrl()}&url={p.eq(c+1)('img').attr('data-xkrkllgl')}&type=img",
|
||||
'vod_remarks':x.text()
|
||||
})
|
||||
return videos
|
||||
|
||||
def getimg(self, text):
|
||||
match = re.search(r"loadBannerDirect\('([^']+)'", text)
|
||||
if match:
|
||||
url = match.group(1)
|
||||
return f"{self.getProxyUrl()}&url={url}&type=img"
|
||||
else:
|
||||
return ''
|
||||
|
||||
def aesimg(self, word):
|
||||
key = b'f5d965df75336270'
|
||||
iv = b'97b60394abc2fbe1'
|
||||
cipher = AES.new(key, AES.MODE_CBC, iv)
|
||||
decrypted = unpad(cipher.decrypt(word), AES.block_size)
|
||||
return decrypted
|
||||
|
||||
def getpq(self, data):
|
||||
try:
|
||||
return pq(data)
|
||||
except Exception as e:
|
||||
print(f"{str(e)}")
|
||||
return pq(data.encode('utf-8'))
|
||||
@@ -0,0 +1,404 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 🌈 Love
|
||||
import json
|
||||
import random
|
||||
import re
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
from base64 import b64decode, b64encode
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import requests
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Util.Padding import unpad
|
||||
from pyquery import PyQuery as pq
|
||||
sys.path.append('..')
|
||||
from base.spider import Spider
|
||||
|
||||
|
||||
class Spider(Spider):
|
||||
|
||||
def init(self, extend=""):
|
||||
try:self.proxies = json.loads(extend)
|
||||
except:self.proxies = {}
|
||||
self.headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.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,application/signed-exchange;v=b3;q=0.7',
|
||||
'Accept-Language': 'zh-CN,zh;q=0.9',
|
||||
'Connection': 'keep-alive',
|
||||
'Cache-Control': 'no-cache',
|
||||
}
|
||||
# Use working dynamic URLs directly
|
||||
self.host = self.get_working_host()
|
||||
self.headers.update({'Origin': self.host, 'Referer': f"{self.host}/"})
|
||||
self.log(f"使用站点: {self.host}")
|
||||
print(f"使用站点: {self.host}")
|
||||
pass
|
||||
|
||||
def getName(self):
|
||||
return "🌈 51吸瓜"
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
# Treat direct media formats as playable without parsing
|
||||
return any(ext in (url or '') for ext in ['.m3u8', '.mp4', '.ts'])
|
||||
|
||||
def manualVideoCheck(self):
|
||||
return False
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
def homeContent(self, filter):
|
||||
try:
|
||||
response = requests.get(self.host, headers=self.headers, proxies=self.proxies, timeout=15)
|
||||
if response.status_code != 200:
|
||||
return {'class': [], 'list': []}
|
||||
|
||||
data = self.getpq(response.text)
|
||||
result = {}
|
||||
classes = []
|
||||
|
||||
# Try to get categories from different possible locations
|
||||
category_selectors = [
|
||||
'.category-list ul li',
|
||||
'.nav-menu li',
|
||||
'.menu li',
|
||||
'nav ul li'
|
||||
]
|
||||
|
||||
for selector in category_selectors:
|
||||
for k in data(selector).items():
|
||||
link = k('a')
|
||||
href = (link.attr('href') or '').strip()
|
||||
name = (link.text() or '').strip()
|
||||
# Skip placeholder or invalid entries
|
||||
if not href or href == '#' or not name:
|
||||
continue
|
||||
classes.append({
|
||||
'type_name': name,
|
||||
'type_id': href
|
||||
})
|
||||
if classes:
|
||||
break
|
||||
|
||||
# If no categories found, create some default ones
|
||||
if not classes:
|
||||
classes = [
|
||||
{'type_name': '首页', 'type_id': '/'},
|
||||
{'type_name': '最新', 'type_id': '/latest/'},
|
||||
{'type_name': '热门', 'type_id': '/hot/'}
|
||||
]
|
||||
|
||||
result['class'] = classes
|
||||
result['list'] = self.getlist(data('#index article a'))
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
print(f"homeContent error: {e}")
|
||||
return {'class': [], 'list': []}
|
||||
|
||||
def homeVideoContent(self):
|
||||
try:
|
||||
response = requests.get(self.host, headers=self.headers, proxies=self.proxies, timeout=15)
|
||||
if response.status_code != 200:
|
||||
return {'list': []}
|
||||
data = self.getpq(response.text)
|
||||
return {'list': self.getlist(data('#index article a, #archive article a'))}
|
||||
except Exception as e:
|
||||
print(f"homeVideoContent error: {e}")
|
||||
return {'list': []}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
try:
|
||||
if '@folder' in tid:
|
||||
id = tid.replace('@folder', '')
|
||||
videos = self.getfod(id)
|
||||
else:
|
||||
# Build URL properly
|
||||
if tid.startswith('/'):
|
||||
if pg and pg != '1':
|
||||
url = f"{self.host}{tid}page/{pg}/"
|
||||
else:
|
||||
url = f"{self.host}{tid}"
|
||||
else:
|
||||
url = f"{self.host}/{tid}"
|
||||
|
||||
response = requests.get(url, headers=self.headers, proxies=self.proxies, timeout=15)
|
||||
if response.status_code != 200:
|
||||
return {'list': [], 'page': pg, 'pagecount': 1, 'limit': 90, 'total': 0}
|
||||
|
||||
data = self.getpq(response.text)
|
||||
videos = self.getlist(data('#archive article a, #index article a'), tid)
|
||||
|
||||
result = {}
|
||||
result['list'] = videos
|
||||
result['page'] = pg
|
||||
result['pagecount'] = 1 if '@folder' in tid else 99999
|
||||
result['limit'] = 90
|
||||
result['total'] = 999999
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
print(f"categoryContent error: {e}")
|
||||
return {'list': [], 'page': pg, 'pagecount': 1, 'limit': 90, 'total': 0}
|
||||
|
||||
def detailContent(self, ids):
|
||||
try:
|
||||
url = f"{self.host}{ids[0]}" if not ids[0].startswith('http') else ids[0]
|
||||
response = requests.get(url, headers=self.headers, proxies=self.proxies, timeout=15)
|
||||
|
||||
if response.status_code != 200:
|
||||
return {'list': [{'vod_play_from': '51吸瓜', 'vod_play_url': f'页面加载失败${url}'}]}
|
||||
|
||||
data = self.getpq(response.text)
|
||||
vod = {'vod_play_from': '51吸瓜'}
|
||||
|
||||
# Get content/description
|
||||
try:
|
||||
clist = []
|
||||
if data('.tags .keywords a'):
|
||||
for k in data('.tags .keywords a').items():
|
||||
title = k.text()
|
||||
href = k.attr('href')
|
||||
if title and href:
|
||||
clist.append('[a=cr:' + json.dumps({'id': href, 'name': title}) + '/]' + title + '[/a]')
|
||||
vod['vod_content'] = ' '.join(clist) if clist else data('.post-title').text()
|
||||
except:
|
||||
vod['vod_content'] = data('.post-title').text() or '51吸瓜视频'
|
||||
|
||||
# Get video URLs (build episode list when multiple players exist)
|
||||
try:
|
||||
plist = []
|
||||
used_names = set()
|
||||
if data('.dplayer'):
|
||||
for c, k in enumerate(data('.dplayer').items(), start=1):
|
||||
config_attr = k.attr('data-config')
|
||||
if config_attr:
|
||||
try:
|
||||
config = json.loads(config_attr)
|
||||
video_url = config.get('video', {}).get('url', '')
|
||||
# Determine a readable episode name from nearby headings if present
|
||||
ep_name = ''
|
||||
try:
|
||||
parent = k.parents().eq(0)
|
||||
# search up to a few ancestors for a heading text
|
||||
for _ in range(3):
|
||||
if not parent: break
|
||||
heading = parent.find('h2, h3, h4').eq(0).text() or ''
|
||||
heading = heading.strip()
|
||||
if heading:
|
||||
ep_name = heading
|
||||
break
|
||||
parent = parent.parents().eq(0)
|
||||
except Exception:
|
||||
ep_name = ''
|
||||
base_name = ep_name if ep_name else f"视频{c}"
|
||||
name = base_name
|
||||
count = 2
|
||||
# Ensure the name is unique
|
||||
while name in used_names:
|
||||
name = f"{base_name} {count}"
|
||||
count += 1
|
||||
used_names.add(name)
|
||||
if video_url:
|
||||
self.log(f"解析到视频: {name} -> {video_url}")
|
||||
print(f"解析到视频: {name} -> {video_url}")
|
||||
plist.append(f"{name}${video_url}")
|
||||
except:
|
||||
continue
|
||||
|
||||
if plist:
|
||||
self.log(f"拼装播放列表,共{len(plist)}个")
|
||||
print(f"拼装播放列表,共{len(plist)}个")
|
||||
vod['vod_play_url'] = '#'.join(plist)
|
||||
else:
|
||||
vod['vod_play_url'] = f"未找到视频源${url}"
|
||||
|
||||
except Exception as e:
|
||||
vod['vod_play_url'] = f"视频解析失败${url}"
|
||||
|
||||
return {'list': [vod]}
|
||||
|
||||
except Exception as e:
|
||||
print(f"detailContent error: {e}")
|
||||
return {'list': [{'vod_play_from': '51吸瓜', 'vod_play_url': f'详情页加载失败${ids[0] if ids else ""}'}]}
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
try:
|
||||
url = f"{self.host}/search/{key}/{pg}" if pg != "1" else f"{self.host}/search/{key}/"
|
||||
response = requests.get(url, headers=self.headers, proxies=self.proxies, timeout=15)
|
||||
|
||||
if response.status_code != 200:
|
||||
return {'list': [], 'page': pg}
|
||||
|
||||
data = self.getpq(response.text)
|
||||
videos = self.getlist(data('#archive article a, #index article a'))
|
||||
return {'list': videos, 'page': pg}
|
||||
|
||||
except Exception as e:
|
||||
print(f"searchContent error: {e}")
|
||||
return {'list': [], 'page': pg}
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
url = id
|
||||
p = 1
|
||||
if self.isVideoFormat(url):
|
||||
# m3u8/mp4 direct play; when using proxy setting, wrap to proxy for m3u8
|
||||
if '.m3u8' in url:
|
||||
url = self.proxy(url)
|
||||
p = 0
|
||||
self.log(f"播放请求: parse={p}, url={url}")
|
||||
print(f"播放请求: parse={p}, url={url}")
|
||||
return {'parse': p, 'url': url, 'header': self.headers}
|
||||
|
||||
def localProxy(self, param):
|
||||
if param.get('type') == 'img':
|
||||
res=requests.get(param['url'], headers=self.headers, proxies=self.proxies, timeout=10)
|
||||
return [200,res.headers.get('Content-Type'),self.aesimg(res.content)]
|
||||
elif param.get('type') == 'm3u8':return self.m3Proxy(param['url'])
|
||||
else:return self.tsProxy(param['url'])
|
||||
|
||||
def proxy(self, data, type='m3u8'):
|
||||
if data and len(self.proxies):return f"{self.getProxyUrl()}&url={self.e64(data)}&type={type}"
|
||||
else:return data
|
||||
|
||||
def m3Proxy(self, url):
|
||||
url=self.d64(url)
|
||||
ydata = requests.get(url, headers=self.headers, proxies=self.proxies, allow_redirects=False)
|
||||
data = ydata.content.decode('utf-8')
|
||||
if ydata.headers.get('Location'):
|
||||
url = ydata.headers['Location']
|
||||
data = requests.get(url, headers=self.headers, proxies=self.proxies).content.decode('utf-8')
|
||||
lines = data.strip().split('\n')
|
||||
last_r = url[:url.rfind('/')]
|
||||
parsed_url = urlparse(url)
|
||||
durl = parsed_url.scheme + "://" + parsed_url.netloc
|
||||
iskey=True
|
||||
for index, string in enumerate(lines):
|
||||
if iskey and 'URI' in string:
|
||||
pattern = r'URI="([^"]*)"'
|
||||
match = re.search(pattern, string)
|
||||
if match:
|
||||
lines[index] = re.sub(pattern, f'URI="{self.proxy(match.group(1), "mkey")}"', string)
|
||||
iskey=False
|
||||
continue
|
||||
if '#EXT' not in string:
|
||||
if 'http' not in string:
|
||||
domain = last_r if string.count('/') < 2 else durl
|
||||
string = domain + ('' if string.startswith('/') else '/') + string
|
||||
lines[index] = self.proxy(string, string.split('.')[-1].split('?')[0])
|
||||
data = '\n'.join(lines)
|
||||
return [200, "application/vnd.apple.mpegur", data]
|
||||
|
||||
def tsProxy(self, url):
|
||||
url = self.d64(url)
|
||||
data = requests.get(url, headers=self.headers, proxies=self.proxies, stream=True)
|
||||
return [200, data.headers['Content-Type'], data.content]
|
||||
|
||||
def e64(self, text):
|
||||
try:
|
||||
text_bytes = text.encode('utf-8')
|
||||
encoded_bytes = b64encode(text_bytes)
|
||||
return encoded_bytes.decode('utf-8')
|
||||
except Exception as e:
|
||||
print(f"Base64编码错误: {str(e)}")
|
||||
return ""
|
||||
|
||||
def d64(self, encoded_text):
|
||||
try:
|
||||
encoded_bytes = encoded_text.encode('utf-8')
|
||||
decoded_bytes = b64decode(encoded_bytes)
|
||||
return decoded_bytes.decode('utf-8')
|
||||
except Exception as e:
|
||||
print(f"Base64解码错误: {str(e)}")
|
||||
return ""
|
||||
|
||||
def get_working_host(self):
|
||||
"""Get working host from known dynamic URLs"""
|
||||
# Known working URLs from the dynamic gateway
|
||||
dynamic_urls = [
|
||||
'https://artist.vgwtswi.xyz',
|
||||
'https://ability.vgwtswi.xyz',
|
||||
'https://am.vgwtswi.xyz'
|
||||
]
|
||||
|
||||
# Test each URL to find a working one
|
||||
for url in dynamic_urls:
|
||||
try:
|
||||
response = requests.get(url, headers=self.headers, proxies=self.proxies, timeout=10)
|
||||
if response.status_code == 200:
|
||||
# Verify it has the expected content structure
|
||||
data = self.getpq(response.text)
|
||||
articles = data('#index article a')
|
||||
if len(articles) > 0:
|
||||
self.log(f"选用可用站点: {url}")
|
||||
print(f"选用可用站点: {url}")
|
||||
return url
|
||||
except Exception as e:
|
||||
continue
|
||||
|
||||
# Fallback to first URL if none work (better than crashing)
|
||||
self.log(f"未检测到可用站点,回退: {dynamic_urls[0]}")
|
||||
print(f"未检测到可用站点,回退: {dynamic_urls[0]}")
|
||||
return dynamic_urls[0]
|
||||
|
||||
|
||||
def getlist(self, data, tid=''):
|
||||
videos = []
|
||||
l = '/mrdg' in tid
|
||||
for k in data.items():
|
||||
a = k.attr('href')
|
||||
b = k('h2').text()
|
||||
# Some pages might not include datePublished; use a fallback
|
||||
c = k('span[itemprop="datePublished"]').text() or k('.post-meta, .entry-meta, time').text()
|
||||
if a and b:
|
||||
videos.append({
|
||||
'vod_id': f"{a}{'@folder' if l else ''}",
|
||||
'vod_name': b.replace('\n', ' '),
|
||||
'vod_pic': self.getimg(k('script').text()),
|
||||
'vod_remarks': c or '',
|
||||
'vod_tag': 'folder' if l else '',
|
||||
'style': {"type": "rect", "ratio": 1.33}
|
||||
})
|
||||
return videos
|
||||
|
||||
def getfod(self, id):
|
||||
url = f"{self.host}{id}"
|
||||
data = self.getpq(requests.get(url, headers=self.headers, proxies=self.proxies).text)
|
||||
vdata=data('.post-content[itemprop="articleBody"]')
|
||||
r=['.txt-apps','.line','blockquote','.tags','.content-tabs']
|
||||
for i in r:vdata.remove(i)
|
||||
p=vdata('p')
|
||||
videos=[]
|
||||
for i,x in enumerate(vdata('h2').items()):
|
||||
c=i*2
|
||||
videos.append({
|
||||
'vod_id': p.eq(c)('a').attr('href'),
|
||||
'vod_name': p.eq(c).text(),
|
||||
'vod_pic': f"{self.getProxyUrl()}&url={p.eq(c+1)('img').attr('data-xkrkllgl')}&type=img",
|
||||
'vod_remarks':x.text()
|
||||
})
|
||||
return videos
|
||||
|
||||
def getimg(self, text):
|
||||
match = re.search(r"loadBannerDirect\('([^']+)'", text)
|
||||
if match:
|
||||
url = match.group(1)
|
||||
return f"{self.getProxyUrl()}&url={url}&type=img"
|
||||
else:
|
||||
return ''
|
||||
|
||||
def aesimg(self, word):
|
||||
key = b'f5d965df75336270'
|
||||
iv = b'97b60394abc2fbe1'
|
||||
cipher = AES.new(key, AES.MODE_CBC, iv)
|
||||
decrypted = unpad(cipher.decrypt(word), AES.block_size)
|
||||
return decrypted
|
||||
|
||||
def getpq(self, data):
|
||||
try:
|
||||
return pq(data)
|
||||
except Exception as e:
|
||||
print(f"{str(e)}")
|
||||
return pq(data.encode('utf-8'))
|
||||
@@ -0,0 +1,181 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import json
|
||||
import sys
|
||||
import re
|
||||
import html as html_parser
|
||||
from urllib.parse import quote
|
||||
|
||||
sys.path.append('..')
|
||||
from base.spider import Spider
|
||||
|
||||
class Spider(Spider):
|
||||
def init(self, extend=""):
|
||||
# 建议使用原站或稳定的镜像地址
|
||||
self.host = "https://www.hanime1.nl"
|
||||
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',
|
||||
'Referer': f'{self.host}/',
|
||||
}
|
||||
|
||||
def getName(self):
|
||||
return "Hanime"
|
||||
|
||||
def homeContent(self, filter):
|
||||
classes = [
|
||||
{'type_name': '最新上市', 'type_id': 'latest_rank'},
|
||||
{'type_name': '裏番', 'type_id': '裏番'},
|
||||
{'type_name': '泡麵番', 'type_id': '泡麵番'},
|
||||
{'type_name': 'Motion Anime', 'type_id': 'Motion Anime'},
|
||||
{'type_name': '3DCG', 'type_id': '3DCG'},
|
||||
{'type_name': '2D動畫', 'type_id': '2D動畫'},
|
||||
{'type_name': 'AI生成', 'type_id': 'AI生成'},
|
||||
{'type_name': 'MMD', 'type_id': 'MMD'},
|
||||
{'type_name': 'Cosplay', 'type_id': 'Cosplay'},
|
||||
{'type_name': '本日排行', 'type_id': 'daily_rank'},
|
||||
{'type_name': '本週排行', 'type_id': 'weekly_rank'},
|
||||
{'type_name': '本月排行', 'type_id': 'monthly_rank'}
|
||||
]
|
||||
sort_options = [
|
||||
{"n": "最新上市", "v": "最新上市"},
|
||||
{"n": "最新上傳", "v": "最新上傳"},
|
||||
{"n": "本日排行", "v": "本日排行"},
|
||||
{"n": "本週排行", "v": "本週排行"},
|
||||
{"n": "本月排行", "v": "本月排行"},
|
||||
{"n": "觀看次數", "v": "觀看次數"}
|
||||
]
|
||||
filters = {}
|
||||
for item in classes:
|
||||
filters[item['type_id']] = [{"key": "sort", "name": "排序", "value": sort_options}]
|
||||
return {'class': classes, 'filters': filters}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
page = int(pg)
|
||||
|
||||
# 映射表:将 type_id 映射为网站识别的 sort 字符串
|
||||
rank_map = {
|
||||
'latest_rank': '最新上市',
|
||||
'daily_rank': '本日排行',
|
||||
'weekly_rank': '本週排行',
|
||||
'monthly_rank': '本月排行'
|
||||
}
|
||||
|
||||
# 核心修复逻辑:
|
||||
# 1. 如果 extend 中有用户选下的 sort,则优先使用
|
||||
# 2. 如果没有,则尝试从 rank_map 匹配 tid 的默认排序
|
||||
# 3. 最后保底使用 '最新上市'
|
||||
sort = extend.get('sort')
|
||||
if not sort:
|
||||
sort = rank_map.get(tid, '最新上市')
|
||||
|
||||
# 构造请求 URL
|
||||
if tid in rank_map:
|
||||
# 排行类标签通常不需要 genre 参数
|
||||
url = f"{self.host}/search?sort={quote(sort)}&page={page}"
|
||||
else:
|
||||
# 普通分类标签(如:裏番)需要 genre 和 sort 同时存在
|
||||
url = f"{self.host}/search?genre={quote(tid)}&sort={quote(sort)}&page={page}"
|
||||
|
||||
try:
|
||||
content = self.fetch(url, headers=self.headers).text
|
||||
vods = self.parse_vod_list(content)
|
||||
|
||||
# 提取总页数:寻找类似 "第 1 / 100 页" 的结构
|
||||
pc_match = re.search(r'\/ (\d+)', content)
|
||||
pagecount = int(pc_match.group(1)) if pc_match else page + 1
|
||||
|
||||
return {'list': vods, 'page': page, 'pagecount': pagecount}
|
||||
except Exception:
|
||||
return {'list': []}
|
||||
|
||||
def parse_vod_list(self, html):
|
||||
vods = []
|
||||
seen = set()
|
||||
|
||||
# 模式1:搜索结果卡片布局
|
||||
p1 = re.compile(r'class="video-item-container".*?href="[^"]*v=(\d+)".*?src="([^"]+)".*?class="duration">(.*?)<.*?class="title">(.*?)<', re.S)
|
||||
|
||||
# 模式2:首页行列表布局
|
||||
p2 = re.compile(r'href="[^"]*watch\?v=(\d+)".*?src="([^"]+)".*?class="home-rows-videos-title"[^>]*>(.*?)</div>', re.S)
|
||||
|
||||
# 匹配卡片
|
||||
for vid, pic, dur, title in p1.findall(html):
|
||||
if vid not in seen:
|
||||
seen.add(vid)
|
||||
vods.append({
|
||||
"vod_id": vid,
|
||||
"vod_name": html_parser.unescape(title).strip(),
|
||||
"vod_pic": pic,
|
||||
"vod_remarks": dur.strip()
|
||||
})
|
||||
|
||||
# 匹配行
|
||||
if not vods:
|
||||
for vid, pic, title in p2.findall(html):
|
||||
if vid not in seen:
|
||||
seen.add(vid)
|
||||
vods.append({
|
||||
"vod_id": vid,
|
||||
"vod_name": html_parser.unescape(title).strip(),
|
||||
"vod_pic": pic,
|
||||
"vod_remarks": ""
|
||||
})
|
||||
return vods
|
||||
|
||||
def detailContent(self, ids):
|
||||
vid = ids[0]
|
||||
url = f"{self.host}/watch?v={vid}"
|
||||
try:
|
||||
html = self.fetch(url, headers=self.headers).text
|
||||
title_match = re.search(r'<meta property="og:title" content="(.*?)"', html)
|
||||
pic_match = re.search(r'<meta property="og:image" content="(.*?)"', html)
|
||||
|
||||
title = title_match.group(1) if title_match else "未知标题"
|
||||
pic = pic_match.group(1) if pic_match else ""
|
||||
|
||||
# 解析播放源
|
||||
sources = re.findall(r'<source[^>]+src="([^"]+)"', html)
|
||||
if not sources:
|
||||
sources = re.findall(r'https?://[^\s"\'<>]+?\.mp4[^\s"\'<>]*', html)
|
||||
|
||||
play_parts = []
|
||||
seen_urls = set()
|
||||
for s_url in sources:
|
||||
s_url = html_parser.unescape(s_url).replace('&', '&')
|
||||
if s_url in seen_urls: continue
|
||||
seen_urls.add(s_url)
|
||||
|
||||
if '1080' in s_url: tag = "1080P"
|
||||
elif '720' in s_url: tag = "720P"
|
||||
else: tag = "标清"
|
||||
|
||||
play_parts.append(f"{tag}${s_url}")
|
||||
|
||||
# 排序:高清在前
|
||||
play_parts.sort(key=lambda x: 0 if "1080" in x else (1 if "720" in x else 2))
|
||||
|
||||
return {'list': [{
|
||||
"vod_id": vid,
|
||||
"vod_name": title,
|
||||
"vod_pic": pic,
|
||||
"vod_play_from": "Hanime",
|
||||
"vod_play_url": "#".join(play_parts)
|
||||
}]}
|
||||
except Exception:
|
||||
return {'list': []}
|
||||
|
||||
def searchContent(self, key, quick, pg="1", extend=None):
|
||||
url = f"{self.host}/search?query={quote(key)}&page={pg}"
|
||||
try:
|
||||
html = self.fetch(url, headers=self.headers).text
|
||||
return {'list': self.parse_vod_list(html), 'page': pg}
|
||||
except Exception:
|
||||
return {'list': []}
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
# 必须伪造原站 Referer 绕过防盗链
|
||||
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.hanime1.nl/',
|
||||
'Connection': 'keep-alive'
|
||||
}
|
||||
return {'parse': 0, 'url': id, 'header': headers}
|
||||
Binary file not shown.
@@ -0,0 +1,392 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import json
|
||||
import re
|
||||
import requests
|
||||
from pyquery import PyQuery as pq
|
||||
import sys
|
||||
sys.path.append('..')
|
||||
from base.spider import Spider
|
||||
|
||||
|
||||
class Spider(Spider):
|
||||
host = 'https://cn.avjoy.me'
|
||||
headers = {
|
||||
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/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': 'https://cn.avjoy.me/',
|
||||
'origin': 'https://cn.avjoy.me',
|
||||
}
|
||||
|
||||
def init(self, extend=''):
|
||||
self.proxies = json.loads(extend).get('proxy', {}) if extend else {}
|
||||
self.session = requests.Session()
|
||||
self.session.headers.update(self.headers)
|
||||
|
||||
def getName(self):
|
||||
return "hohoj"
|
||||
|
||||
def fetch(self, url, params=None):
|
||||
try:
|
||||
resp = self.session.get(url, headers=self.session.headers, params=params,
|
||||
proxies=self.proxies, timeout=10, allow_redirects=True)
|
||||
return resp.text
|
||||
except:
|
||||
return ''
|
||||
|
||||
def fetch_resp(self, url, params=None, extra_headers=None, stream=False):
|
||||
try:
|
||||
hdrs = self.session.headers.copy()
|
||||
if extra_headers:
|
||||
hdrs.update(extra_headers)
|
||||
return self.session.get(url, headers=hdrs, params=params,
|
||||
proxies=self.proxies, timeout=10,
|
||||
allow_redirects=True, stream=stream)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def homeContent(self, filter):
|
||||
html = self.fetch(self.host)
|
||||
return {
|
||||
'class': [
|
||||
{'type_name': '最新上传视频', 'type_id': 'videos'},
|
||||
{'type_name': '视频', 'type_id': 'videos'},
|
||||
{'type_name': '类别', 'type_id': 'categories'},
|
||||
{'type_name': '标签', 'type_id': 'tags'}
|
||||
],
|
||||
'filters': self.get_filters(),
|
||||
'list': self.parse_videos_from_list_html(pq(html))
|
||||
}
|
||||
|
||||
def get_filters(self):
|
||||
return {}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
norm = tid.lstrip('/') if not tid.startswith('http') else tid
|
||||
if '?' in norm and not norm.startswith('http'):
|
||||
norm = norm.split('?', 1)[0]
|
||||
url = f"{self.host}/{norm}" if not norm.startswith('http') else norm
|
||||
params = (extend or {}).copy()
|
||||
try:
|
||||
if int(pg) > 1:
|
||||
params['page'] = pg
|
||||
except:
|
||||
pass
|
||||
params.pop('o', None)
|
||||
html = self.fetch(url, params)
|
||||
doc = pq(html)
|
||||
m_cur = re.search(r"current_url\s*=\s*\"([^\"]+)\"", html)
|
||||
if m_cur:
|
||||
base_path = m_cur.group(1)
|
||||
if base_path.startswith('/videos/') or base_path.startswith('/search/videos/'):
|
||||
url = f"{self.host}{base_path}"
|
||||
html = self.fetch(url, params)
|
||||
doc = pq(html)
|
||||
|
||||
def uniq_append(items, entry):
|
||||
key = (entry.get('vod_id'), entry.get('vod_name'))
|
||||
if key and key not in {(i.get('vod_id'), i.get('vod_name')) for i in items}:
|
||||
items.append(entry)
|
||||
if tid == 'categories':
|
||||
items = []
|
||||
for card in doc('div.content-left .row.content-row > div').items():
|
||||
a = card.find('a').eq(0)
|
||||
href = (a.attr('href') or '').strip()
|
||||
name = card.find('.category-title .title-truncate').text().strip()
|
||||
pic = (card.find('.thumb-overlay img').attr('src') or '').strip()
|
||||
if href and name and href.startswith('/videos/'):
|
||||
cat_id = href.lstrip('/')
|
||||
if pic and pic.startswith('/'):
|
||||
pic = f"{self.host}{pic}"
|
||||
uniq_append(items, {
|
||||
'vod_id': cat_id,
|
||||
'vod_name': name,
|
||||
'vod_pic': pic,
|
||||
'vod_tag': 'folder',
|
||||
'style': {"type": "rect", "ratio": 1.1}
|
||||
})
|
||||
for a in doc('.dropdown-menu.multi-column-dropdown a').items():
|
||||
href = (a.attr('href') or '').strip()
|
||||
name = a.text().strip()
|
||||
if href.startswith('/videos/') and name:
|
||||
uniq_append(items, {
|
||||
'vod_id': href.lstrip('/'),
|
||||
'vod_name': name,
|
||||
'vod_pic': '',
|
||||
'vod_tag': 'folder',
|
||||
'style': {"type": "rect", "ratio": 1.1}
|
||||
})
|
||||
return {
|
||||
'list': items,
|
||||
'page': '1',
|
||||
'pagecount': 1,
|
||||
'limit': 90,
|
||||
'total': len(items)
|
||||
}
|
||||
if tid == 'tags':
|
||||
items = []
|
||||
for a in doc('.popular-tag a').items():
|
||||
name = a.text().strip()
|
||||
href = (a.attr('href') or '').strip()
|
||||
if href.startswith('/search/videos/') and name:
|
||||
uniq_append(items, {
|
||||
'vod_id': href.lstrip('/'),
|
||||
'vod_name': name,
|
||||
'vod_tag': 'folder',
|
||||
'style': {"type": "rect", "ratio": 1.0}
|
||||
})
|
||||
for a in doc('.trending-searches a').items():
|
||||
name = a.text().strip()
|
||||
href = (a.attr('href') or '').strip()
|
||||
if href.startswith('/search/videos/') and name:
|
||||
uniq_append(items, {
|
||||
'vod_id': href.lstrip('/'),
|
||||
'vod_name': name,
|
||||
'vod_tag': 'folder',
|
||||
'style': {"type": "rect", "ratio": 1.0}
|
||||
})
|
||||
return {
|
||||
'list': items,
|
||||
'page': '1',
|
||||
'pagecount': 1,
|
||||
'limit': 90,
|
||||
'total': len(items)
|
||||
}
|
||||
videos = self.parse_videos_from_list_html(doc)
|
||||
if not videos:
|
||||
fallback = []
|
||||
for a in doc('a[href^="/video/"]').items():
|
||||
href = a.attr('href')
|
||||
title = a.text().strip()
|
||||
img = a.parents().find('img').eq(0).attr('src')
|
||||
if href and title:
|
||||
uniq_append(fallback, {
|
||||
'vod_id': href,
|
||||
'vod_name': title,
|
||||
'vod_pic': img,
|
||||
'style': {"type": "rect", "ratio": 1.5}
|
||||
})
|
||||
videos = fallback
|
||||
pagecount = 1
|
||||
try:
|
||||
pagecount = doc('.pagination a').length or 1
|
||||
except:
|
||||
pagecount = 1
|
||||
return {
|
||||
'list': videos,
|
||||
'page': pg,
|
||||
'pagecount': pagecount,
|
||||
'limit': 90,
|
||||
'total': 999999
|
||||
}
|
||||
|
||||
def detailContent(self, ids):
|
||||
vid = ids[0]
|
||||
url = f"{self.host}{vid}" if vid.startswith('/') else f"{self.host}/{vid}"
|
||||
html = self.fetch(url)
|
||||
data = pq(html)
|
||||
title = data('h1').text() or data('title').text() or ''
|
||||
title = re.sub(r'\s*HoHoJ.*$', '', title)
|
||||
title = re.sub(r'\s*\|.*$', '', title)
|
||||
title = title.strip()
|
||||
poster = data('video#video').attr('poster') or data('meta[property="og:image"]').attr('content')
|
||||
vod_year = data('.info span').eq(-1).text()
|
||||
m_vid = re.search(r"video_id\s*=\s*\"(\d+)\"", html)
|
||||
video_id = m_vid.group(1) if m_vid else ''
|
||||
if not video_id:
|
||||
m_url_id = re.search(r"/video/(\d+)", url) or re.search(r"/video/(\d+)", html)
|
||||
video_id = m_url_id.group(1) if m_url_id else ''
|
||||
m_vkey = re.search(r"/embed/([a-zA-Z0-9]+)", html)
|
||||
vkey = m_vkey.group(1) if m_vkey else ''
|
||||
play_id = video_id or vkey
|
||||
|
||||
vod = {
|
||||
'vod_id': vid,
|
||||
'vod_name': title,
|
||||
'vod_play_from': '撸出血',
|
||||
'vod_play_url': f"{title}${play_id or ''}",
|
||||
'vod_pic': poster,
|
||||
'vod_year': vod_year,
|
||||
}
|
||||
tags = []
|
||||
for tag in data('a.tag').items():
|
||||
name = tag.text().strip()
|
||||
href = tag.attr('href')
|
||||
if name and href:
|
||||
tags.append(f'[a=cr:{json.dumps({"id": href, "name": name})}/]{name}[/a]')
|
||||
if tags:
|
||||
vod['vod_content'] = ' '.join(tags)
|
||||
director_name = data('a[href^="/user/"]').text().strip()
|
||||
if director_name:
|
||||
try:
|
||||
from urllib.parse import quote
|
||||
director_href = f"/search/videos/{quote(director_name)}"
|
||||
except:
|
||||
director_href = f"/search/videos/{director_name}"
|
||||
director_link = f"[a=cr:{json.dumps({'id': director_href, 'name': director_name})}/]{director_name}[/a]"
|
||||
vod['vod_content'] = (vod.get('vod_content', '') + ('\n' if vod.get('vod_content') else '') + '导演:' + director_link)
|
||||
intro = (data('section.video-description').text() or '').strip()
|
||||
if not intro:
|
||||
intro = (data('meta[name="description"]').attr('content') or '').strip()
|
||||
if intro:
|
||||
vod['vod_content'] = (vod.get('vod_content', '') + ('\n' if vod.get('vod_content') else '') + '影片介绍:' + intro)
|
||||
|
||||
return {'list': [vod]}
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
params = {}
|
||||
try:
|
||||
if int(pg) > 1:
|
||||
params['page'] = pg
|
||||
except:
|
||||
pass
|
||||
url = f"{self.host}/search/videos/{requests.utils.quote(key)}"
|
||||
html = self.fetch(url, params)
|
||||
if not html:
|
||||
html = self.fetch(f"{self.host}/search", {'text': key, **params})
|
||||
return {'list': self.parse_videos_from_list_html(pq(html)), 'page': pg}
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
def pick_best_source(html_text):
|
||||
sources = []
|
||||
for m in re.finditer(r"<source[^>]+src=\"([^\"]+)\"[^>]*>", html_text):
|
||||
frag = html_text[m.start():m.end()]
|
||||
src = m.group(1)
|
||||
res_m = re.search(r"res=\'?(\d+)\'?", frag)
|
||||
label_m = re.search(r"label=\'([^\']+)\'", frag)
|
||||
res = int(res_m.group(1)) if res_m else 0
|
||||
label = label_m.group(1) if label_m else ''
|
||||
sources.append((res, label, src))
|
||||
if sources:
|
||||
sources.sort(reverse=True)
|
||||
return sources[0][2]
|
||||
mv = re.search(r"<video[^>]+src=\"([^\"]+)\"", html_text)
|
||||
if mv:
|
||||
return mv.group(1)
|
||||
mv2 = re.search(r"var\s+videoSrc\s*=\s*[\"']([^\"']+)[\"']", html_text)
|
||||
if mv2:
|
||||
return mv2.group(1)
|
||||
doc = pq(html_text)
|
||||
return doc('video source').attr('src') or doc('video').attr('src') or ''
|
||||
|
||||
raw = str(id).strip()
|
||||
if re.match(r'^https?://', raw) and self.isVideoFormat(raw):
|
||||
return {
|
||||
'parse': 0,
|
||||
'url': raw,
|
||||
'header': {
|
||||
'user-agent': self.headers['user-agent'],
|
||||
'referer': self.host,
|
||||
'origin': self.host,
|
||||
}
|
||||
}
|
||||
|
||||
m = re.search(r"/video/(\d+)", raw) or re.search(r"id=(\d+)", raw)
|
||||
if m:
|
||||
raw = m.group(1)
|
||||
is_numeric = re.match(r"^\d+$", raw) is not None
|
||||
|
||||
video_url = ''
|
||||
referer_used = ''
|
||||
if is_numeric:
|
||||
for path in [f"{self.host}/video/{raw}", f"{self.host}/video/{raw}/"]:
|
||||
self.session.headers['referer'] = path
|
||||
play_html = self.fetch(path)
|
||||
video_url = pick_best_source(play_html)
|
||||
if video_url:
|
||||
referer_used = path
|
||||
break
|
||||
m_dl = re.search(r"href=\"(/download\\.php\\?id=\\d+[^\"]*label=1080p)\"", play_html)
|
||||
if not m_dl:
|
||||
m_dl = re.search(r"href=\"(/download\\.php\\?id=\\d+[^\"]*)\"", play_html)
|
||||
if m_dl:
|
||||
dl_url = f"{self.host}{m_dl.group(1)}"
|
||||
resp = self.fetch_resp(dl_url, extra_headers={'referer': path}, stream=True)
|
||||
if resp and resp.ok:
|
||||
resp.close()
|
||||
video_url = resp.url
|
||||
referer_used = path
|
||||
break
|
||||
if not video_url:
|
||||
embed_url = f"{self.host}/embed/{raw}" if not is_numeric else f"{self.host}/embed?id={raw}"
|
||||
self.session.headers['referer'] = embed_url
|
||||
html = self.fetch(embed_url)
|
||||
v2 = pick_best_source(html)
|
||||
if v2:
|
||||
video_url = v2
|
||||
referer_used = embed_url
|
||||
|
||||
return {
|
||||
'parse': 0,
|
||||
'url': video_url or '',
|
||||
'header': {
|
||||
'user-agent': self.headers['user-agent'],
|
||||
'referer': referer_used or self.host,
|
||||
'origin': self.host,
|
||||
}
|
||||
}
|
||||
def parse_videos_from_list_html(self, doc: pq):
|
||||
videos = []
|
||||
for item in doc('.row.content-row > div').items():
|
||||
link = item.find('a').eq(0).attr('href')
|
||||
img = item.find('.thumb-overlay img').eq(0).attr('src')
|
||||
info = item.find('.content-info').eq(0)
|
||||
title = info.find('.content-title').text().strip()
|
||||
duration = (item.find('.video-duration, .thumb-overlay .duration, .content-duration, .duration').eq(0).text() or '').strip()
|
||||
overlay_text = (item.find('.thumb-overlay').text() or '').strip()
|
||||
hd_flag = bool(item.find('.hd, .icon-hd, .hd-icon, .badge-hd, .label-hd').length) or ('HD' in overlay_text)
|
||||
if not link or not title:
|
||||
continue
|
||||
parts = []
|
||||
if hd_flag:
|
||||
parts.append('HD')
|
||||
if duration:
|
||||
parts.append(duration)
|
||||
remarks = ' • '.join(parts)
|
||||
videos.append({
|
||||
'vod_id': link,
|
||||
'vod_name': re.sub(r'\s*\|.*$', '', re.sub(r'\s*HoHoJ.*$', '', title)).strip(),
|
||||
'vod_pic': img,
|
||||
'vod_remarks': remarks or '',
|
||||
'vod_tag': '',
|
||||
'style': {"type": "rect", "ratio": 1.5}
|
||||
})
|
||||
if not videos:
|
||||
for info in doc('.content-info').items():
|
||||
a = info('a').eq(0)
|
||||
link = a.attr('href')
|
||||
title = info('.content-title').text().strip()
|
||||
if not link or not title:
|
||||
continue
|
||||
img = info.prev('a').find('img').attr('src') or info.prevAll('a').eq(0).find('img').attr('src')
|
||||
duration = (info.parents().find('.video-duration, .thumb-overlay .duration, .content-duration, .duration').eq(0).text() or '').strip()
|
||||
overlay_text = (info.parents().find('.thumb-overlay').text() or '').strip()
|
||||
hd_flag = bool(info.parents().find('.hd, .icon-hd, .hd-icon, .badge-hd, .label-hd').length) or ('HD' in overlay_text)
|
||||
parts = []
|
||||
if hd_flag:
|
||||
parts.append('HD')
|
||||
if duration:
|
||||
parts.append(duration)
|
||||
remarks = ' • '.join(parts)
|
||||
videos.append({
|
||||
'vod_id': link,
|
||||
'vod_name': re.sub(r'\s*\|.*$', '', re.sub(r'\s*HoHoJ.*$', '', title)).strip(),
|
||||
'vod_pic': img,
|
||||
'vod_remarks': remarks or '',
|
||||
'vod_tag': '',
|
||||
'style': {"type": "rect", "ratio": 1.5}
|
||||
})
|
||||
return videos
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
return bool(url) and (url.lower().endswith('.mp4') or url.lower().endswith('.m3u8'))
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
def destroy(self):
|
||||
pass
|
||||
def homeVideoContent(self):
|
||||
pass
|
||||
def localProxy(self, param):
|
||||
pass
|
||||
def liveContent(self, url):
|
||||
pass
|
||||
@@ -0,0 +1,315 @@
|
||||
# coding = utf-8
|
||||
# !/usr/bin/python
|
||||
# 新时代青年 2025.06.25 getApp第三版
|
||||
import re,sys,uuid,json,base64,urllib3
|
||||
from Crypto.Cipher import AES
|
||||
from base.spider import Spider
|
||||
from Crypto.Util.Padding import pad,unpad
|
||||
sys.path.append('..')
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
|
||||
class Spider(Spider):
|
||||
xurl,key,iv,init_data,search_verify = '','','','',''
|
||||
headerx = {
|
||||
'User-Agent': 'okhttp/3.10.0' # okhttp/3.14.9
|
||||
}
|
||||
|
||||
def getName(self):
|
||||
return "首页"
|
||||
|
||||
def init(self, extend):
|
||||
js1=json.loads(extend)
|
||||
host = js1['host']
|
||||
if not re.match(r'^https?:\/\/[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*(:\d+)?(\/)?$',host):
|
||||
host = self.fetch(host, headers=self.headerx, timeout=10, verify=False).text.rstrip('/')
|
||||
api = js1.get('api','/api.php/getappapi')
|
||||
if str(api) == '2':
|
||||
api = '/api.php/qijiappapi'
|
||||
self.xurl = host + api
|
||||
self.key = js1['datakey']
|
||||
self.iv = js1.get('dataiv',self.key)
|
||||
res = self.fetch(self.xurl + '.index/initV119', headers=self.headerx, verify=False).json()
|
||||
encrypted_data = res['data']
|
||||
response = self.decrypt(encrypted_data)
|
||||
init_data = json.loads(response)
|
||||
self.init_data = init_data
|
||||
self.search_verify = init_data['config'].get('system_search_verify_status',False)
|
||||
|
||||
def homeContent(self, filter):
|
||||
kjson = self.init_data
|
||||
result = {"class": [], "filters": {}}
|
||||
for i in kjson['type_list']:
|
||||
if not(i['type_name'] in {'全部', 'QQ', 'juo.one'} or '企鹅群' in i['type_name']):
|
||||
result['class'].append({
|
||||
"type_id": i['type_id'],
|
||||
"type_name": i['type_name']
|
||||
})
|
||||
name_mapping = {'class': '类型', 'area': '地区', 'lang': '语言', 'year': '年份', 'sort': '排序'}
|
||||
filter_items = []
|
||||
for filter_type in i.get('filter_type_list', []):
|
||||
filter_name = filter_type.get('name')
|
||||
values = filter_type.get('list', [])
|
||||
if not values:
|
||||
continue
|
||||
value_list = [{"n": value, "v": value} for value in values]
|
||||
display_name = name_mapping.get(filter_name, filter_name)
|
||||
key = 'by' if filter_name == 'sort' else filter_name
|
||||
filter_items.append({
|
||||
"key": key,
|
||||
"name": display_name,
|
||||
"value": value_list
|
||||
})
|
||||
type_id = i.get('type_id')
|
||||
if filter_items:
|
||||
result["filters"][str(type_id)] = filter_items
|
||||
return result
|
||||
|
||||
def homeVideoContent(self):
|
||||
videos = []
|
||||
kjson = self.init_data
|
||||
for i in kjson['type_list']:
|
||||
for item in i['recommend_list']:
|
||||
vod_id = item['vod_id']
|
||||
name = item['vod_name']
|
||||
pic = item['vod_pic']
|
||||
remarks = item['vod_remarks']
|
||||
video = {
|
||||
"vod_id": vod_id,
|
||||
"vod_name": name,
|
||||
"vod_pic": pic,
|
||||
"vod_remarks": remarks
|
||||
}
|
||||
videos.append(video)
|
||||
return {'list': videos}
|
||||
|
||||
def categoryContent(self, cid, pg, filter, ext):
|
||||
videos = []
|
||||
payload = {
|
||||
'area': ext.get('area','全部'),
|
||||
'year': ext.get('year','全部'),
|
||||
'type_id': cid,
|
||||
'page': str(pg),
|
||||
'sort': ext.get('sort','最新'),
|
||||
'lang': ext.get('lang','全部'),
|
||||
'class': ext.get('class','全部')
|
||||
}
|
||||
url = f'{self.xurl}.index/typeFilterVodList'
|
||||
res = self.post(url=url, headers=self.headerx,data=payload, verify=False).json()
|
||||
encrypted_data = res['data']
|
||||
kjson = self.decrypt(encrypted_data)
|
||||
kjson1 = json.loads(kjson)
|
||||
for i in kjson1['recommend_list']:
|
||||
id = i['vod_id']
|
||||
name = i['vod_name']
|
||||
pic = i['vod_pic']
|
||||
remarks = i['vod_remarks']
|
||||
video = {
|
||||
"vod_id": id,
|
||||
"vod_name": name,
|
||||
"vod_pic": pic,
|
||||
"vod_remarks": remarks
|
||||
}
|
||||
videos.append(video)
|
||||
return {'list': videos, 'page': pg, 'pagecount': 9999, 'limit': 90, 'total': 999999}
|
||||
|
||||
def detailContent(self, ids):
|
||||
did = ids[0]
|
||||
payload = {
|
||||
'vod_id': did,
|
||||
}
|
||||
api_endpoints = ['vodDetail', 'vodDetail2']
|
||||
|
||||
for endpoint in api_endpoints:
|
||||
url = f'{self.xurl}.index/{endpoint}'
|
||||
response = self.post(url=url, headers=self.headerx, data=payload, verify=False)
|
||||
|
||||
if response.status_code == 200:
|
||||
response_data = response.json()
|
||||
encrypted_data = response_data['data']
|
||||
kjson1 = self.decrypt(encrypted_data)
|
||||
kjson = json.loads(kjson1)
|
||||
break
|
||||
videos = []
|
||||
play_form = ''
|
||||
play_url = ''
|
||||
lineid = 1
|
||||
name_count = {}
|
||||
for line in kjson['vod_play_list']:
|
||||
keywords = {'防走丢', '群', '防失群', '官网'}
|
||||
player_show = line['player_info']['show']
|
||||
if any(keyword in player_show for keyword in keywords):
|
||||
player_show = f'{lineid}线'
|
||||
line['player_info']['show'] = player_show
|
||||
count = name_count.get(player_show, 0) + 1
|
||||
name_count[player_show] = count
|
||||
if count > 1:
|
||||
line['player_info']['show'] = f"{player_show}{count}"
|
||||
play_form += line['player_info']['show'] + '$$$'
|
||||
parse = line['player_info']['parse']
|
||||
parse_type = line['player_info']['parse_type']
|
||||
player_parse_type = line['player_info']['player_parse_type']
|
||||
kurls = ""
|
||||
for vod in line['urls']:
|
||||
token = 'token+' + vod['token']
|
||||
kurls += f"{str(vod['name'])}${parse},{vod['url']},{token},{player_parse_type},{parse_type}#"
|
||||
kurls = kurls.rstrip('#')
|
||||
play_url += kurls + '$$$'
|
||||
lineid += 1
|
||||
play_form = play_form.rstrip('$$$')
|
||||
play_url = play_url.rstrip('$$$')
|
||||
videos.append({
|
||||
"vod_id": did,
|
||||
"vod_name": kjson['vod']['vod_name'],
|
||||
"vod_actor": kjson['vod']['vod_actor'].replace('演员', ''),
|
||||
"vod_director": kjson['vod'].get('vod_director', '').replace('导演', ''),
|
||||
"vod_content": kjson['vod']['vod_content'],
|
||||
"vod_remarks": kjson['vod']['vod_remarks'],
|
||||
"vod_year": kjson['vod']['vod_year'] + '年',
|
||||
"vod_area": kjson['vod']['vod_area'],
|
||||
"vod_play_from": play_form,
|
||||
"vod_play_url": play_url
|
||||
})
|
||||
return {'list': videos}
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
url = ''
|
||||
aid = id.split(',')
|
||||
uid = aid[0]
|
||||
kurl = aid[1]
|
||||
token = aid[2].replace('token+', '')
|
||||
player_parse_type = aid[3]
|
||||
parse_type = aid[4]
|
||||
if parse_type == '0':
|
||||
res = {"parse": 0, "url": kurl, "header": {'User-Agent': 'Dalvik/2.1.0 (Linux; U; Android 14; 23113RK12C Build/SKQ1.231004.001)'}}
|
||||
elif parse_type == '2':
|
||||
res = {"parse": 1, "url": uid+kurl, "header": {'User-Agent': 'Dalvik/2.1.0 (Linux; U; Android 14; 23113RK12C Build/SKQ1.231004.001)'}}
|
||||
elif player_parse_type == '2':
|
||||
response = self.fetch(url=f'{uid}{kurl}',verify=False)
|
||||
if response.status_code == 200:
|
||||
kjson1 = response.json()
|
||||
res = {"parse": 0, "url": kjson1['url'], "header": {'User-Agent': 'Dalvik/2.1.0 (Linux; U; Android 14; 23113RK12C Build/SKQ1.231004.001)'}}
|
||||
else:
|
||||
id1 = self.encrypt(kurl)
|
||||
payload = {
|
||||
'parse_api': uid,
|
||||
'url': id1,
|
||||
'player_parse_type': player_parse_type,
|
||||
'token': token
|
||||
}
|
||||
url1 = f"{self.xurl}.index/vodParse"
|
||||
response = self.post(url=url1, headers=self.headerx, data=payload, verify=False)
|
||||
if response.status_code == 200:
|
||||
response_data = response.json()
|
||||
encrypted_data = response_data['data']
|
||||
kjson = self.decrypt(encrypted_data)
|
||||
kjson1 = json.loads(kjson)
|
||||
kjson2 = kjson1['json']
|
||||
kjson3 = json.loads(kjson2)
|
||||
url = kjson3['url']
|
||||
res = {"parse": 0, "playUrl": '', "url": url, "header": {'User-Agent': 'Dalvik/2.1.0 (Linux; U; Android 14; 23113RK12C Build/SKQ1.231004.001)'}}
|
||||
return res
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
videos = []
|
||||
if 'xiaohys.com' in self.xurl:
|
||||
host = self.xurl.split('api.php')[0]
|
||||
data = self.fetch(f'{host}index.php/ajax/suggest?mid=1&wd={key}').json()
|
||||
for i in data['list']:
|
||||
videos.append({
|
||||
"vod_id": i['id'],
|
||||
"vod_name": i['name'],
|
||||
"vod_pic": i.get('pic')
|
||||
})
|
||||
else:
|
||||
payload = {
|
||||
'keywords': key,
|
||||
'type_id': "0",
|
||||
'page': str(pg)
|
||||
}
|
||||
if self.search_verify:
|
||||
verifi = self.verification()
|
||||
if verifi is None:
|
||||
return {'list':[]}
|
||||
payload['code'] = verifi['code']
|
||||
payload['key'] = verifi['uuid']
|
||||
url = f'{self.xurl}.index/searchList'
|
||||
res = self.post(url=url, data=payload, headers=self.headerx, verify=False).json()
|
||||
if not res.get('data'):
|
||||
return {'list':[] ,'msg': res.get('msg')}
|
||||
encrypted_data = res['data']
|
||||
kjson = self.decrypt(encrypted_data)
|
||||
kjson1 = json.loads(kjson)
|
||||
for i in kjson1['search_list']:
|
||||
id = i['vod_id']
|
||||
name = i['vod_name']
|
||||
pic = i['vod_pic']
|
||||
remarks = i['vod_year'] + ' ' + i['vod_class']
|
||||
videos.append({
|
||||
"vod_id": id,
|
||||
"vod_name": name,
|
||||
"vod_pic": pic,
|
||||
"vod_remarks": remarks
|
||||
})
|
||||
return {'list': videos, 'page': pg, 'pagecount': 9999, 'limit': 90, 'total': 999999}
|
||||
|
||||
def localProxy(self, params):
|
||||
if params['type'] == "m3u8":
|
||||
return self.proxyM3u8(params)
|
||||
elif params['type'] == "media":
|
||||
return self.proxyMedia(params)
|
||||
elif params['type'] == "ts":
|
||||
return self.proxyTs(params)
|
||||
return None
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def decrypt(self, encrypted_data_b64):
|
||||
key_bytes = self.key.encode('utf-8')
|
||||
iv_bytes = self.iv.encode('utf-8')
|
||||
encrypted_data = base64.b64decode(encrypted_data_b64)
|
||||
cipher = AES.new(key_bytes, AES.MODE_CBC, iv_bytes)
|
||||
decrypted_padded = cipher.decrypt(encrypted_data)
|
||||
decrypted = unpad(decrypted_padded, AES.block_size)
|
||||
return decrypted.decode('utf-8')
|
||||
|
||||
def encrypt(self, sencrypted_data):
|
||||
key_bytes = self.key.encode('utf-8')
|
||||
iv_bytes = self.iv.encode('utf-8')
|
||||
data_bytes = sencrypted_data.encode('utf-8')
|
||||
padded_data = pad(data_bytes, AES.block_size)
|
||||
cipher = AES.new(key_bytes, AES.MODE_CBC, iv_bytes)
|
||||
encrypted_bytes = cipher.encrypt(padded_data)
|
||||
encrypted_data_b64 = base64.b64encode(encrypted_bytes).decode('utf-8')
|
||||
return encrypted_data_b64
|
||||
|
||||
def ocr(self, base64img):
|
||||
dat2 = self.post("https://api.nn.ci/ocr/b64/text", data=base64img, headers=self.headerx, verify=False).text
|
||||
if dat2:
|
||||
return dat2
|
||||
else:
|
||||
return None
|
||||
|
||||
def verification(self):
|
||||
random_uuid = str(uuid.uuid4())
|
||||
dat = self.fetch(f'{self.xurl}.verify/create?key={random_uuid}',headers=self.headerx, verify=False).content
|
||||
base64_img = base64.b64encode(dat).decode('utf-8')
|
||||
if not dat:
|
||||
return None
|
||||
code = self.ocr(base64_img)
|
||||
if not code:
|
||||
return None
|
||||
code = self.replace_code(code)
|
||||
if not (len(code) == 4 and code.isdigit()):
|
||||
return None
|
||||
return {'uuid': random_uuid, 'code': code}
|
||||
|
||||
def replace_code(self, text):
|
||||
replacements = {'y': '9', '口': '0', 'q': '0', 'u': '0', 'o': '0', '>': '1', 'd': '0', 'b': '8', '已': '2','D': '0', '五': '5'}
|
||||
if len(text) == 3:
|
||||
text = text.replace('566', '5066')
|
||||
text = text.replace('066', '1666')
|
||||
return ''.join(replacements.get(c, c) for c in text)
|
||||
@@ -0,0 +1,354 @@
|
||||
var iil = 'jsjiami.com.v6',
|
||||
iil_ = ['iil'],
|
||||
lIIIl1ll = [iil, '\x6f\x6b\x68\x74\x74\x70\x2f\x33\x2e\x31\x35', '\x69\x6e\x64\x65\x78\x4f\x66', '\x24\x24\x24', '\x73\x70\x6c\x69\x74', '\x74\x72\x69\x6d', '\x26\x26\x26', '\x3a\x2f\x2f', '\x6c\x6f\x67', '\x70\x69\x63\x55\x72\x6c\x3a\x20', '\x72\x65\x70\x6c\x61\x63\x65', '\x70\x75\x73\x68', '\x2f\x66\x69\x6c\x65\x2f\x6c\x69\x76\x65\x73\x6f\x75\x72\x63\x65\x6c\x69\x73\x74', '\x2f\x6c\x69\x76\x65\x73\x6f\x75\x72\x63\x65\x6c\x69\x73\x74', '\x47\x45\x54', '\x70\x61\x72\x73\x65', '\x63\x6f\x6e\x74\x65\x6e\x74', '\x73\x75\x62\x73\x74\x72\x69\x6e\x67', '\x6c\x61\x73\x74\x49\x6e\x64\x65\x78\x4f\x66', '\x6e\x61\x6d\x65', '\x75\x72\x6c', '\x3d\x3d\x3d\x3d\x20\x3e\x3e\x3e\x20', '\x73\x74\x72\x69\x6e\x67\x69\x66\x79', '\x65\x78\x65\x63', '\x74\x65\x73\x74', '\x6d\x61\x74\x63\x68', '\x63\x68\x61\x6e\x6e\x65\x6c', '\x2c\x23\x67\x65\x6e\x72\x65\x23\x0a', '\x75\x72\x6c\x73', '\x64\x61\x74\x61', '\x64\x61\x74\x61\x6c\x69\x73\x74', '\x70\x72\x6f\x76', '\x6c\x69\x73\x74', '\x2d\x2d\x2d', '\x6c\x69\x6e\x65', '\x77\x65\x62\x50\x69\x63\x55\x72\x6c\x3a\x20', '\x23\x45\x58\x54\x4d\x33\x55', '\x22\x63\x68\x61\x6e\x6e\x65\x6c\x22', '\x22\x75\x72\x6c\x73\x22', '\x22\x64\x61\x74\x61\x6c\x69\x73\x74\x22', '\x6c\x65\x6e\x67\x74\x68', '\x23\x67\x65\x6e\x72\x65\x23', '\x7b\x6e\x61\x6d\x65\x7d', '\x7b\x63\x61\x74\x65\x7d', '\u76f4\u64ad\u5217\u8868', '\x6e\x75\x6c\x6c', '\x74\x79\x70\x65\x5f\x69\x64', '\x76\x6f\x64\x5f\x70\x6c\x61\x79\x5f\x75\x72\x6c', '\x68\x61\x73\x4f\x77\x6e\x50\x72\x6f\x70\x65\x72\x74\x79', '\x6a\x6f\x69\x6e', '\x76\x6f\x64\x5f\x70\x6c\x61\x79\x5f\x66\x72\x6f\x6d', '\x6a\x43\x50\x73\x4e\x6a\x77\x69\x4a\x61\x51\x6d\x69\x2e\x63\x6f\x4e\x4f\x6d\x47\x72\x2e\x76\x56\x36\x41\x46\x67\x79\x74\x74\x3d\x3d'];
|
||||
|
||||
function Ii1l1III(_0x3b13df, _0x346a54) {
|
||||
_0x3b13df = ~~'0x' ['concat'](_0x3b13df['slice'](0x0));
|
||||
var _0x4db44b = lIIIl1ll[_0x3b13df];
|
||||
return _0x4db44b;
|
||||
};
|
||||
(function(_0x209161, _0x5eaa4a) {
|
||||
var _0x42ecdf = 0x0;
|
||||
for (_0x5eaa4a = _0x209161['shift'](_0x42ecdf >> 0x2); _0x5eaa4a && _0x5eaa4a !== (_0x209161['pop'](_0x42ecdf >> 0x3) + '')['replace'](/[CPNwJQNOGrVAFgytt=]/g, ''); _0x42ecdf++) {
|
||||
_0x42ecdf = _0x42ecdf ^ 0x127efd;
|
||||
}
|
||||
}(lIIIl1ll, Ii1l1III));
|
||||
let headers = {
|
||||
'User-Agent': Ii1l1III('0')
|
||||
};
|
||||
let classes = [];
|
||||
let cates = {};
|
||||
let picUrl = '';
|
||||
let webPaths = {};
|
||||
|
||||
function init(IiIIi1i) {
|
||||
let i1Ii11I1 = '';
|
||||
if (IiIIi1i[Ii1l1III('1')](Ii1l1III('2')) > 0x0) {
|
||||
i1Ii11I1 = IiIIi1i[Ii1l1III('3')](Ii1l1III('2'))[0x0][Ii1l1III('4')]();
|
||||
IiIIi1i = IiIIi1i[Ii1l1III('3')](Ii1l1III('2'))[0x1][Ii1l1III('4')]();
|
||||
}
|
||||
if (IiIIi1i[Ii1l1III('1')](Ii1l1III('5')) > 0x0) {
|
||||
picUrl = IiIIi1i[Ii1l1III('3')](Ii1l1III('5'))[0x1][Ii1l1III('4')]();
|
||||
if (picUrl[Ii1l1III('1')](Ii1l1III('6')) < 0x0) {
|
||||
picUrl = i1Ii11I1 + picUrl;
|
||||
}
|
||||
IiIIi1i = IiIIi1i[Ii1l1III('3')](Ii1l1III('5'))[0x0][Ii1l1III('4')]();
|
||||
}
|
||||
console[Ii1l1III('7')](Ii1l1III('8') + picUrl);
|
||||
let IIlIlI1I = IiIIi1i[Ii1l1III('3')]('\x23');
|
||||
for (const IlII1I1 of IIlIlI1I) {
|
||||
if (IlII1I1[Ii1l1III('1')]('\x24') > 0x0) {
|
||||
let illIl111 = IlII1I1;
|
||||
let Ill1iIi = IlII1I1[Ii1l1III('3')]('\x24')[0x0];
|
||||
if (illIl111[Ii1l1III('1')](Ii1l1III('6')) < 0x0) {
|
||||
illIl111 = illIl111[Ii1l1III('9')]('\x24', '\x24' + i1Ii11I1);
|
||||
}
|
||||
classes[Ii1l1III('a')]({
|
||||
'type_id': illIl111,
|
||||
'type_name': Ill1iIi[Ii1l1III('9')]('\x21\x21', '')
|
||||
});
|
||||
} else {
|
||||
let II1lIlli = IlII1I1;
|
||||
if (II1lIlli[Ii1l1III('1')](Ii1l1III('6')) < 0x0) {
|
||||
II1lIlli = i1Ii11I1 + II1lIlli;
|
||||
}
|
||||
II1lIlli = II1lIlli[Ii1l1III('9')](Ii1l1III('b'), Ii1l1III('c'));
|
||||
let Illi11ll = req(II1lIlli, {
|
||||
'\x6d\x65\x74\x68\x6f\x64': Ii1l1III('d'),
|
||||
'\x68\x65\x61\x64\x65\x72\x73': headers
|
||||
});
|
||||
try {
|
||||
let l1lIiill = JSON[Ii1l1III('e')](Illi11ll[Ii1l1III('f')]);
|
||||
let lillI11l = II1lIlli[Ii1l1III('10')](0x0, II1lIlli[Ii1l1III('11')]('\x2f') + 0x1);
|
||||
for (const i1iilII1 of l1lIiill) {
|
||||
let Iillil = i1iilII1[Ii1l1III('12')];
|
||||
let lI1iIl = i1iilII1[Ii1l1III('13')];
|
||||
let illIl111 = Iillil + '\x24' + (lI1iIl[Ii1l1III('1')](Ii1l1III('6')) < 0x0 ? lillI11l : '') + lI1iIl;
|
||||
classes[Ii1l1III('a')]({
|
||||
'type_id': illIl111,
|
||||
'type_name': Iillil[Ii1l1III('9')]('\x21\x21', '')
|
||||
});
|
||||
webPaths[illIl111] = lillI11l;
|
||||
}
|
||||
} catch (Ii1Ii11) {
|
||||
console[Ii1l1III('7')](Ii1l1III('14') + Ii1Ii11);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function home(I1iiIiIl) {
|
||||
return JSON[Ii1l1III('15')]({
|
||||
'class': classes,
|
||||
'filters': null
|
||||
});
|
||||
}
|
||||
|
||||
function parseM3u(iIi1Ii1I, I1IlIIIi) {
|
||||
let iI1iiIii = {};
|
||||
let iiI11111 = /(#EXTINF:.+?),([^,]+?)\s*\n(.+?)\s*\n/g;
|
||||
let ii1iilil = null;
|
||||
while ((ii1iilil = iiI11111[Ii1l1III('16')](iIi1Ii1I)) != null) {
|
||||
let lllli1iI = ii1iilil[0x1];
|
||||
let il1Ili1I = ii1iilil[0x2];
|
||||
let liIlll1l = ii1iilil[0x3];
|
||||
if (il1Ili1I == null || liIlll1l == null || il1Ili1I == '' || liIlll1l == '') {
|
||||
continue;
|
||||
}
|
||||
il1Ili1I = il1Ili1I[Ii1l1III('4')]();
|
||||
liIlll1l = liIlll1l[Ii1l1III('4')]();
|
||||
let IiI1lI1l = I1IlIIIi;
|
||||
let ilIl1i1i = /group-title="(.*?)"/;
|
||||
if (ilIl1i1i[Ii1l1III('17')](lllli1iI)) {
|
||||
IiI1lI1l = lllli1iI[Ii1l1III('18')](ilIl1i1i)[0x1];
|
||||
}
|
||||
if (!iI1iiIii[IiI1lI1l]) {
|
||||
iI1iiIii[IiI1lI1l] = [];
|
||||
}
|
||||
iI1iiIii[IiI1lI1l][Ii1l1III('a')](il1Ili1I + '\x2c' + liIlll1l);
|
||||
}
|
||||
let ll11III1 = '';
|
||||
for (const li1Ili in iI1iiIii) {
|
||||
ll11III1 += li1Ili + '\x0a';
|
||||
let IlIil1ll = iI1iiIii[li1Ili];
|
||||
for (const li1iI11 of IlIil1ll) {
|
||||
ll11III1 += li1iI11 + '\x0a';
|
||||
}
|
||||
}
|
||||
return ll11III1;
|
||||
}
|
||||
|
||||
function parseFm(IliiIl1I) {
|
||||
let lliiI1i1 = '';
|
||||
let Iii1ll = JSON[Ii1l1III('e')](IliiIl1I);
|
||||
for (const i1lIlli1 of Iii1ll) {
|
||||
let I111Il1l = i1lIlli1[Ii1l1III('12')];
|
||||
let ilI11li = i1lIlli1[Ii1l1III('19')];
|
||||
lliiI1i1 += I111Il1l + Ii1l1III('1a');
|
||||
for (const iiilI1iI of ilI11li) {
|
||||
let I11111l1 = iiilI1iI[Ii1l1III('12')];
|
||||
let IlI1l1I1 = iiilI1iI[Ii1l1III('1b')];
|
||||
for (const l1II1lll of IlI1l1I1) {
|
||||
lliiI1i1 += I11111l1 + '\x2c' + l1II1lll + '\x0a';
|
||||
}
|
||||
}
|
||||
}
|
||||
return lliiI1i1;
|
||||
}
|
||||
|
||||
function parseLu(iIliI1lI) {
|
||||
let IIlilI1i = '';
|
||||
let I11ilI1i = JSON[Ii1l1III('e')](iIliI1lI)[Ii1l1III('1c')];
|
||||
for (const i1Ii1l1 of I11ilI1i[Ii1l1III('1d')]) {
|
||||
let I11111l = i1Ii1l1[Ii1l1III('1e')];
|
||||
let IiIiii1l = i1Ii1l1[Ii1l1III('1f')];
|
||||
IIlilI1i += I11111l + Ii1l1III('1a');
|
||||
for (const l1111lI of IiIiii1l) {
|
||||
let lIlI1iI = l1111lI[Ii1l1III('12')];
|
||||
let ll11i1II = l1111lI[Ii1l1III('1b')];
|
||||
for (const Iliilii of ll11i1II) {
|
||||
IIlilI1i += lIlI1iI + Ii1l1III('20') + Iliilii[Ii1l1III('21')] + '\x2c' + Iliilii[Ii1l1III('13')] + '\x0a';
|
||||
}
|
||||
}
|
||||
}
|
||||
return IIlilI1i;
|
||||
}
|
||||
|
||||
function getCateData(IliI1i) {
|
||||
let iI1I1I1I = picUrl;
|
||||
if (IliI1i[Ii1l1III('1')](Ii1l1III('5')) > 0x0) {
|
||||
iI1I1I1I = IliI1i[Ii1l1III('3')](Ii1l1III('5'))[0x1][Ii1l1III('4')]();
|
||||
if (iI1I1I1I[Ii1l1III('1')](Ii1l1III('6')) < 0x0 && webPaths[IliI1i]) {
|
||||
iI1I1I1I = webPaths[IliI1i] + iI1I1I1I;
|
||||
}
|
||||
IliI1i = IliI1i[Ii1l1III('3')](Ii1l1III('5'))[0x0][Ii1l1III('4')]();
|
||||
}
|
||||
console[Ii1l1III('7')](Ii1l1III('22') + iI1I1I1I);
|
||||
let ll1iIiiI = IliI1i[Ii1l1III('3')]('\x24')[0x1];
|
||||
let i1I1l1i = IliI1i[Ii1l1III('3')]('\x24')[0x0];
|
||||
if (!cates[IliI1i]) {
|
||||
cates[IliI1i] = [];
|
||||
let iIl11Iii = headers;
|
||||
if (ll1iIiiI[Ii1l1III('1')]('\x7c') > 0x0) {
|
||||
let ii111I1I = decodeURIComponent(ll1iIiiI[Ii1l1III('3')]('\x7c')[0x1]);
|
||||
ll1iIiiI = ll1iIiiI[Ii1l1III('3')]('\x7c')[0x0];
|
||||
for (const II1Ii1l of ii111I1I[Ii1l1III('3')]('\x26')) {
|
||||
if (II1Ii1l[Ii1l1III('1')]('\x3d') > 0x0) {
|
||||
let lI1lliii = II1Ii1l[Ii1l1III('3')]('\x3d')[0x0];
|
||||
let I11Iii1i = II1Ii1l[Ii1l1III('3')]('\x3d')[0x1];
|
||||
iIl11Iii[lI1lliii] = I11Iii1i;
|
||||
}
|
||||
}
|
||||
}
|
||||
let I111lilI = req(ll1iIiiI, {
|
||||
'\x6d\x65\x74\x68\x6f\x64': Ii1l1III('d'),
|
||||
'\x68\x65\x61\x64\x65\x72\x73': iIl11Iii
|
||||
});
|
||||
I111lilI = I111lilI[Ii1l1III('f')][Ii1l1III('4')]();
|
||||
if (I111lilI[Ii1l1III('1')](Ii1l1III('23')) >= 0x0) {
|
||||
I111lilI = parseM3u(I111lilI, i1I1l1i);
|
||||
} else if (I111lilI[Ii1l1III('1')](Ii1l1III('24')) > 0x0 && I111lilI[Ii1l1III('1')](Ii1l1III('25')) > 0x0) {
|
||||
I111lilI = parseFm(I111lilI);
|
||||
} else if (I111lilI[Ii1l1III('1')](Ii1l1III('26')) > 0x0 && I111lilI[Ii1l1III('1')](Ii1l1III('25')) > 0x0) {
|
||||
I111lilI = parseLu(I111lilI);
|
||||
}
|
||||
let li1IiiII = (i1I1l1i + '\x0a' + I111lilI[Ii1l1III('9')]('\x0d', ''))[Ii1l1III('3')]('\x0a');
|
||||
let lli11iI = i1I1l1i;
|
||||
let IiiIIiIi = null;
|
||||
let iiiI1l = '';
|
||||
for (let i1ii1IIl = 0x0; i1ii1IIl < li1IiiII[Ii1l1III('27')]; i1ii1IIl++) {
|
||||
let lIliIii = li1IiiII[i1ii1IIl][Ii1l1III('9')](/\s+/g, '');
|
||||
if (lIliIii != '' && lIliIii[Ii1l1III('1')](Ii1l1III('6')) < 0x0 && (lIliIii[Ii1l1III('1')]('\x2c') < 0x0 || lIliIii[Ii1l1III('1')](Ii1l1III('28')) > 0x0)) {
|
||||
if (iiiI1l != '') {
|
||||
let ilIIIl = iI1I1I1I[Ii1l1III('9')](Ii1l1III('29'), encodeURIComponent(lli11iI))[Ii1l1III('9')](Ii1l1III('2a'), encodeURIComponent(i1I1l1i));
|
||||
let ilI1ilI = ilIIIl[Ii1l1III('1')]('\x3c');
|
||||
let iili1I1i = ilIIIl[Ii1l1III('11')]('\x3e');
|
||||
if (ilI1ilI > -0x1 && iili1I1i > ilI1ilI) {
|
||||
let I11Ilili = ilIIIl[Ii1l1III('10')](ilI1ilI, iili1I1i + 0x1);
|
||||
let I1liliII = new RegExp(I11Ilili[Ii1l1III('9')](/<|>/g, ''));
|
||||
let lii11liI = lli11iI[Ii1l1III('9')](I1liliII, function(Ili1lIi1, iiliII1l) {
|
||||
return iiliII1l;
|
||||
});
|
||||
ilIIIl = ilIIIl[Ii1l1III('9')](I11Ilili, lii11liI);
|
||||
console[Ii1l1III('7')](lli11iI + '\x2c\x20' + ilIIIl);
|
||||
}
|
||||
let IiiIIiIi = {
|
||||
'vod_id': IliI1i + Ii1l1III('2') + cates[IliI1i][Ii1l1III('27')],
|
||||
'vod_name': lli11iI,
|
||||
'vod_pic': ilIIIl,
|
||||
'vod_remarks': '',
|
||||
'type_name': Ii1l1III('2b'),
|
||||
'vod_year': '',
|
||||
'vod_area': '',
|
||||
'vod_actor': '',
|
||||
'vod_director': '',
|
||||
'vod_content': '',
|
||||
'vod_play_from': i1I1l1i,
|
||||
'vod_play_url': iiiI1l
|
||||
};
|
||||
cates[IliI1i][Ii1l1III('a')](IiiIIiIi);
|
||||
}
|
||||
lli11iI = lIliIii[Ii1l1III('3')]('\x2c')[0x0][Ii1l1III('4')]();
|
||||
iiiI1l = '';
|
||||
} else if (lIliIii[Ii1l1III('1')]('\x2c') > 0x0 && /http|rtmp|rtsp|rsp/ [Ii1l1III('17')](lIliIii)) {
|
||||
let l1iiI1ii = lIliIii[Ii1l1III('3')]('\x2c');
|
||||
if (iiiI1l != '') {
|
||||
iiiI1l += '\x23';
|
||||
}
|
||||
iiiI1l += l1iiI1ii[0x0][Ii1l1III('4')]() + '\x24' + l1iiI1ii[0x1][Ii1l1III('4')]();
|
||||
}
|
||||
}
|
||||
if (iiiI1l != '') {
|
||||
let II1Iliil = iI1I1I1I[Ii1l1III('9')](Ii1l1III('29'), encodeURIComponent(lli11iI))[Ii1l1III('9')](Ii1l1III('2a'), encodeURIComponent(i1I1l1i));
|
||||
let ilI1ilI = II1Iliil[Ii1l1III('1')]('\x3c');
|
||||
let iili1I1i = II1Iliil[Ii1l1III('11')]('\x3e');
|
||||
if (ilI1ilI > -0x1 && iili1I1i > ilI1ilI) {
|
||||
let I11Ilili = II1Iliil[Ii1l1III('10')](ilI1ilI, iili1I1i + 0x1);
|
||||
let I1liliII = new RegExp(I11Ilili[Ii1l1III('9')](/<|>/g, ''));
|
||||
let lii11liI = I1liliII[Ii1l1III('17')](lli11iI) ? lli11iI[Ii1l1III('18')](I1liliII)[0x1] : Ii1l1III('2c');
|
||||
II1Iliil = II1Iliil[Ii1l1III('9')](I11Ilili, lii11liI);
|
||||
}
|
||||
let IiiIIiIi = {
|
||||
'vod_id': IliI1i + Ii1l1III('2') + cates[IliI1i][Ii1l1III('27')],
|
||||
'vod_name': lli11iI,
|
||||
'vod_pic': II1Iliil,
|
||||
'vod_remarks': '',
|
||||
'type_name': Ii1l1III('2b'),
|
||||
'vod_year': '',
|
||||
'vod_area': '',
|
||||
'vod_actor': '',
|
||||
'vod_director': '',
|
||||
'vod_content': '',
|
||||
'vod_play_from': i1I1l1i,
|
||||
'vod_play_url': iiiI1l
|
||||
};
|
||||
cates[IliI1i][Ii1l1III('a')](IiiIIiIi);
|
||||
}
|
||||
}
|
||||
return cates[IliI1i];
|
||||
}
|
||||
|
||||
function homeVod(liIIlIl1) {
|
||||
let iIl1IIii = getCateData(classes[0x0][Ii1l1III('2d')]);
|
||||
let I1l1iil = JSON[Ii1l1III('15')]({
|
||||
'list': iIl1IIii
|
||||
});
|
||||
return I1l1iil;
|
||||
}
|
||||
|
||||
function category(I1l1i1Ii, l1IiiIli, IIi1Illi, lilIliIl) {
|
||||
let IIi1i1ll = [];
|
||||
if (l1IiiIli == 0x1) {
|
||||
IIi1i1ll = getCateData(I1l1i1Ii);
|
||||
}
|
||||
let iIiiIi1i = JSON[Ii1l1III('15')]({
|
||||
'list': IIi1i1ll
|
||||
});
|
||||
return iIiiIi1i;
|
||||
}
|
||||
|
||||
function detail(lIl11iii) {
|
||||
let I1IIIil = lIl11iii[Ii1l1III('3')](Ii1l1III('2'));
|
||||
let liiiil1i = I1IIIil[0x0];
|
||||
let l1l111II = liiiil1i[Ii1l1III('3')]('\x24')[0x0];
|
||||
let Il1li11i = parseInt(I1IIIil[0x1]);
|
||||
let Iill11Ii = getCateData(liiiil1i)[Il1li11i];
|
||||
console[Ii1l1III('7')](JSON[Ii1l1III('15')](Iill11Ii));
|
||||
if (l1l111II[Ii1l1III('1')]('\x21\x21') >= 0x0) {
|
||||
l1l111II = l1l111II[Ii1l1III('9')]('\x21\x21', '');
|
||||
const ii1l1iil = Iill11Ii[Ii1l1III('2e')][Ii1l1III('3')]('\x23');
|
||||
console[Ii1l1III('7')](JSON[Ii1l1III('15')](ii1l1iil));
|
||||
let i1Ili1I = {};
|
||||
let IIIllli1 = {};
|
||||
for (const i1IiIlIl of ii1l1iil) {
|
||||
let Ill1iii1 = i1IiIlIl[Ii1l1III('3')]('\x24')[0x0];
|
||||
let IIiIII11 = l1l111II;
|
||||
if (Ill1iii1[Ii1l1III('1')](Ii1l1III('20')) > 0x0) {
|
||||
IIiIII11 = Ill1iii1[Ii1l1III('3')](Ii1l1III('20'))[0x1];
|
||||
Ill1iii1 = Ill1iii1[Ii1l1III('3')](Ii1l1III('20'))[0x0];
|
||||
}
|
||||
if (!i1Ili1I[Ii1l1III('2f')](Ill1iii1)) {
|
||||
i1Ili1I[Ill1iii1] = 0x1;
|
||||
} else {
|
||||
i1Ili1I[Ill1iii1]++;
|
||||
}
|
||||
IIiIII11 = l1l111II + (i1Ili1I[Ill1iii1] > 0x1 ? '\x20' + i1Ili1I[Ill1iii1] : '');
|
||||
if (!IIIllli1[Ii1l1III('2f')](IIiIII11)) {
|
||||
IIIllli1[IIiIII11] = [];
|
||||
}
|
||||
IIIllli1[IIiIII11][Ii1l1III('a')](Ill1iii1 + '\x24' + i1IiIlIl[Ii1l1III('3')]('\x24')[0x1]);
|
||||
}
|
||||
let III1i1ii = [];
|
||||
let iii1lIIi = [];
|
||||
for (let iliI1I1i in IIIllli1) {
|
||||
III1i1ii[Ii1l1III('a')](iliI1I1i);
|
||||
iii1lIIi[Ii1l1III('a')](IIIllli1[iliI1I1i][Ii1l1III('30')]('\x23'));
|
||||
}
|
||||
Iill11Ii[Ii1l1III('31')] = III1i1ii[Ii1l1III('30')](Ii1l1III('2'));
|
||||
Iill11Ii[Ii1l1III('2e')] = iii1lIIi[Ii1l1III('30')](Ii1l1III('2'));
|
||||
}
|
||||
return JSON[Ii1l1III('15')]({
|
||||
'list': [Iill11Ii]
|
||||
});
|
||||
}
|
||||
|
||||
function play(l1llIIii, illiiIII, lIIIiIiI) {
|
||||
return JSON[Ii1l1III('15')]({
|
||||
'parse': 0x0,
|
||||
'url': illiiIII
|
||||
});
|
||||
}
|
||||
|
||||
function search(I1lll, lI1iiIII) {
|
||||
return null;
|
||||
}
|
||||
__JS_SPIDER__ = {
|
||||
'\x69\x6e\x69\x74': init,
|
||||
'\x68\x6f\x6d\x65': home,
|
||||
'\x68\x6f\x6d\x65\x56\x6f\x64': homeVod,
|
||||
'\x63\x61\x74\x65\x67\x6f\x72\x79': category,
|
||||
'\x64\x65\x74\x61\x69\x6c': detail,
|
||||
'\x70\x6c\x61\x79': play,
|
||||
'\x73\x65\x61\x72\x63\x68': search
|
||||
};;
|
||||
iil = 'jsjiami.com.v6';
|
||||
@@ -0,0 +1,192 @@
|
||||
#coding=utf-8
|
||||
#!/usr/bin/python
|
||||
import sys
|
||||
sys.path.append('..')
|
||||
from base.spider import Spider
|
||||
import json
|
||||
import time
|
||||
import base64
|
||||
import re
|
||||
|
||||
class Spider(Spider): # 元类 默认的元类 type
|
||||
def getName(self):
|
||||
return "央视片库"
|
||||
def init(self,extend=""):
|
||||
print("============{0}============".format(extend))
|
||||
pass
|
||||
def isVideoFormat(self,url):
|
||||
pass
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
def homeContent(self,filter):
|
||||
result = {}
|
||||
cateManual = {
|
||||
|
||||
"动画片": "动画片",
|
||||
|
||||
#"特别节目": "特别节目"
|
||||
}
|
||||
classes = []
|
||||
for k in cateManual:
|
||||
classes.append({
|
||||
'type_name':k,
|
||||
'type_id':cateManual[k]
|
||||
})
|
||||
result['class'] = classes
|
||||
if(filter):
|
||||
result['filters'] = self.config['filter']
|
||||
return result
|
||||
def homeVideoContent(self):
|
||||
result = {
|
||||
'list':[]
|
||||
}
|
||||
return result
|
||||
def categoryContent(self,tid,pg,filter,extend):
|
||||
result = {}
|
||||
month = ""
|
||||
year = ""
|
||||
if 'month' in extend.keys():
|
||||
month = extend['month']
|
||||
if 'year' in extend.keys():
|
||||
year = extend['year']
|
||||
if year == '':
|
||||
month = ''
|
||||
prefix = year + month
|
||||
|
||||
url="https://api.cntv.cn/list/getVideoAlbumList?channelid=CHAL1460955899450127&area=&sc=&fc=%E5%8A%A8%E7%94%BB%E7%89%87&letter=&p={0}&n=24&serviceId=tvcctv&topv=1&t=json"
|
||||
if tid=="电视剧":
|
||||
url="https://api.cntv.cn/list/getVideoAlbumList?channelid=CHAL1460955853485115&area=&sc=&fc=%E7%94%B5%E8%A7%86%E5%89%A7&year=&letter=&p={0}&n=24&serviceId=tvcctv&topv=1&t=json"
|
||||
elif tid=="纪录片":
|
||||
url="https://api.cntv.cn/list/getVideoAlbumList?channelid=CHAL1460955924871139&fc=%E7%BA%AA%E5%BD%95%E7%89%87&channel=&sc=&year=&letter=&p={0}&n=24&serviceId=tvcctv&topv=1&t=json"
|
||||
elif tid=="4":
|
||||
url="https://api.cntv.cn/list/getVideoAlbumList?channelid=CHAL1460955953877151&channel=&sc=&fc=%E7%89%B9%E5%88%AB%E8%8A%82%E7%9B%AE&bigday=&letter=&p={0}&n=24&serviceId=tvcctv&topv=1&t=json"
|
||||
suffix = ""
|
||||
jo = self.fetch(url.format(pg),headers=self.header).json()
|
||||
vodList=jo["data"]["list"]
|
||||
videos = []
|
||||
for vod in vodList:
|
||||
lastVideo =vod['url']
|
||||
brief=vod['brief']
|
||||
if len(brief) == 0:
|
||||
brief = ' '
|
||||
if len(lastVideo) == 0:
|
||||
lastVideo = '_'
|
||||
guid = tid+'###'+vod["title"]+'###'+lastVideo+'###'+vod['image']+'###'+brief
|
||||
title = vod["title"]
|
||||
img = vod['image']
|
||||
videos.append({
|
||||
"vod_id":guid,
|
||||
"vod_name":title,
|
||||
"vod_pic":img,
|
||||
"vod_remarks":''
|
||||
})
|
||||
result['list'] = videos
|
||||
result['page'] = pg
|
||||
result['pagecount'] = 9999
|
||||
result['limit'] = 90
|
||||
result['total'] = 999999
|
||||
return result
|
||||
def detailContent(self,array):
|
||||
aid = array[0].split('###')
|
||||
if aid[2].find("http")<0:
|
||||
return {}
|
||||
tid = aid[0]
|
||||
logo = aid[3]
|
||||
lastVideo = aid[2]
|
||||
title = aid[1]
|
||||
date = aid[0]
|
||||
if lastVideo == '_':
|
||||
return {}
|
||||
rsp = self.fetch(lastVideo)
|
||||
htmlTxt=rsp.text
|
||||
column_id = ""
|
||||
videoList = []
|
||||
patternTxt=r"'title':\s*'(.+?)',\n{0,1}\s*'img':\s*'(.+?)',\n{0,1}\s*'brief':\s*'(.+?)',\n{0,1}\s*'url':\s*'(.+?)'"
|
||||
titleIndex=0
|
||||
UrlIndex=3
|
||||
if tid=="电视剧" or tid=="纪录片":
|
||||
patternTxt=r"'title':\s*'(.+?)',\n{0,1}\s*'brief':\s*'(.+?)',\n{0,1}\s*'img':\s*'(.+?)',\n{0,1}\s*'url':\s*'(.+?)'"
|
||||
titleIndex=0
|
||||
UrlIndex=3
|
||||
elif tid=="特别节目":
|
||||
patternTxt=r'class="tp1"><a\s*href="(https://.+?)"\s*target="_blank"\s*title="(.+?)"></a></div>'
|
||||
titleIndex=1
|
||||
UrlIndex=0
|
||||
#https://api.cntv.cn/NewVideo/getVideoListByAlbumIdNew?id=VIDA3YcIusJ9mh4c9mw5XHyx230113&serviceId=tvcctv//由于方式不同暂时不做
|
||||
pattern = re.compile(patternTxt)
|
||||
ListRe=pattern.findall(htmlTxt)
|
||||
for value in ListRe:
|
||||
videoList.append(value[titleIndex]+"$"+value[UrlIndex])
|
||||
if len(videoList) == 0:
|
||||
return {}
|
||||
vod = {
|
||||
"vod_id":array[0],
|
||||
"vod_name":title,
|
||||
"vod_pic":logo,
|
||||
"type_name":tid,
|
||||
"vod_year":date,
|
||||
"vod_area":"",
|
||||
"vod_remarks":date,
|
||||
"vod_actor":"",
|
||||
"vod_director":column_id,
|
||||
"vod_content":aid[4]
|
||||
}
|
||||
vod['vod_play_from'] = 'CCTV'
|
||||
vod['vod_play_url'] = "#".join(videoList)
|
||||
result = {
|
||||
'list':[
|
||||
vod
|
||||
]
|
||||
}
|
||||
return result
|
||||
|
||||
def searchContent(self,key,quick):
|
||||
result = {
|
||||
'list':[]
|
||||
}
|
||||
return result
|
||||
def playerContent(self,flag,id,vipFlags):
|
||||
result = {}
|
||||
rsp = self.fetch(id)
|
||||
htmlTxt=rsp.text
|
||||
pattern = re.compile(r'var\sguid\s*=\s*"(.+?)";')
|
||||
ListRe=pattern.findall(htmlTxt)
|
||||
if ListRe==[]:
|
||||
return result
|
||||
url = "https://vdn.apps.cntv.cn/api/getHttpVideoInfo.do?pid={0}".format(ListRe[0])
|
||||
jo = self.fetch(url,headers=self.header).json()
|
||||
link = jo['hls_url'].strip()
|
||||
rsp = self.fetch(link,headers=self.header)
|
||||
content = rsp.text.strip()
|
||||
arr = content.split('\n')
|
||||
urlPrefix = self.regStr(link,'(http[s]?://[a-zA-z0-9.]+)/')
|
||||
|
||||
subUrl = arr[-1].split('/')
|
||||
subUrl[3] = '1200'
|
||||
subUrl[-1] = '1200.m3u8'
|
||||
hdUrl = urlPrefix + '/'.join(subUrl)
|
||||
|
||||
url = urlPrefix + arr[-1]
|
||||
|
||||
hdRsp = self.fetch(hdUrl,headers=self.header)
|
||||
if hdRsp.status_code == 200:
|
||||
url = hdUrl
|
||||
|
||||
result["parse"] = 0
|
||||
result["playUrl"] = ''
|
||||
result["url"] = url
|
||||
result["header"] = ''
|
||||
return result
|
||||
|
||||
config = {
|
||||
"player": {},
|
||||
"filter": {"CCTV":[{"key":"cid","name":"频道","value":[{"n":"全部","v":""},{"n":"CCTV-1综合","v":"EPGC1386744804340101"},{"n":"CCTV-2财经","v":"EPGC1386744804340102"},{"n":"CCTV-3综艺","v":"EPGC1386744804340103"},{"n":"CCTV-4中文国际","v":"EPGC1386744804340104"},{"n":"CCTV-5体育","v":"EPGC1386744804340107"},{"n":"CCTV-6电影","v":"EPGC1386744804340108"},{"n":"CCTV-7国防军事","v":"EPGC1386744804340109"},{"n":"CCTV-8电视剧","v":"EPGC1386744804340110"},{"n":"CCTV-9纪录","v":"EPGC1386744804340112"},{"n":"CCTV-10科教","v":"EPGC1386744804340113"},{"n":"CCTV-11戏曲","v":"EPGC1386744804340114"},{"n":"CCTV-12社会与法","v":"EPGC1386744804340115"},{"n":"CCTV-13新闻","v":"EPGC1386744804340116"},{"n":"CCTV-14少儿","v":"EPGC1386744804340117"},{"n":"CCTV-15音乐","v":"EPGC1386744804340118"},{"n":"CCTV-16奥林匹克","v":"EPGC1634630207058998"},{"n":"CCTV-17农业农村","v":"EPGC1563932742616872"},{"n":"CCTV-5+体育赛事","v":"EPGC1468294755566101"}]},{"key":"fc","name":"分类","value":[{"n":"全部","v":""},{"n":"新闻","v":"新闻"},{"n":"体育","v":"体育"},{"n":"综艺","v":"综艺"},{"n":"健康","v":"健康"},{"n":"生活","v":"生活"},{"n":"科教","v":"科教"},{"n":"经济","v":"经济"},{"n":"农业","v":"农业"},{"n":"法治","v":"法治"},{"n":"军事","v":"军事"},{"n":"少儿","v":"少儿"},{"n":"动画","v":"动画"},{"n":"纪实","v":"纪实"},{"n":"戏曲","v":"戏曲"},{"n":"音乐","v":"音乐"},{"n":"影视","v":"影视"}]},{"key":"fl","name":"字母","value":[{"n":"全部","v":""},{"n":"A","v":"A"},{"n":"B","v":"B"},{"n":"C","v":"C"},{"n":"D","v":"D"},{"n":"E","v":"E"},{"n":"F","v":"F"},{"n":"G","v":"G"},{"n":"H","v":"H"},{"n":"I","v":"I"},{"n":"J","v":"J"},{"n":"K","v":"K"},{"n":"L","v":"L"},{"n":"M","v":"M"},{"n":"N","v":"N"},{"n":"O","v":"O"},{"n":"P","v":"P"},{"n":"Q","v":"Q"},{"n":"R","v":"R"},{"n":"S","v":"S"},{"n":"T","v":"T"},{"n":"U","v":"U"},{"n":"V","v":"V"},{"n":"W","v":"W"},{"n":"X","v":"X"},{"n":"Y","v":"Y"},{"n":"Z","v":"Z"}]},{"key":"year","name":"年份","value":[{"n":"全部","v":""},{"n":"2022","v":"2022"},{"n":"2021","v":"2021"},{"n":"2020","v":"2020"},{"n":"2019","v":"2019"},{"n":"2018","v":"2018"},{"n":"2017","v":"2017"},{"n":"2016","v":"2016"},{"n":"2015","v":"2015"},{"n":"2014","v":"2014"},{"n":"2013","v":"2013"},{"n":"2012","v":"2012"},{"n":"2011","v":"2011"},{"n":"2010","v":"2010"},{"n":"2009","v":"2009"},{"n":"2008","v":"2008"},{"n":"2007","v":"2007"},{"n":"2006","v":"2006"},{"n":"2005","v":"2005"},{"n":"2004","v":"2004"},{"n":"2003","v":"2003"},{"n":"2002","v":"2002"},{"n":"2001","v":"2001"},{"n":"2000","v":"2000"}]},{"key":"month","name":"月份","value":[{"n":"全部","v":""},{"n":"12","v":"12"},{"n":"11","v":"11"},{"n":"10","v":"10"},{"n":"09","v":"09"},{"n":"08","v":"08"},{"n":"07","v":"07"},{"n":"06","v":"06"},{"n":"05","v":"05"},{"n":"04","v":"04"},{"n":"03","v":"03"},{"n":"02","v":"02"},{"n":"01","v":"01"}]}]}
|
||||
}
|
||||
header = {
|
||||
"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.54 Safari/537.36",
|
||||
"Origin": "https://tv.cctv.com",
|
||||
"Referer": "https://tv.cctv.com/"
|
||||
}
|
||||
|
||||
def localProxy(self,param):
|
||||
return [200, "video/MP2T", action, ""]
|
||||
@@ -0,0 +1,213 @@
|
||||
# coding=utf-8
|
||||
# !/usr/bin/python
|
||||
# 嗷呜
|
||||
import sys
|
||||
from base64 import b64encode, b64decode
|
||||
from Crypto.Hash import MD5, SHA256
|
||||
sys.path.append("..")
|
||||
from base.spider import Spider
|
||||
from Crypto.Cipher import AES
|
||||
import json
|
||||
import time
|
||||
|
||||
|
||||
class Spider(Spider):
|
||||
|
||||
def getName(self):
|
||||
return "lav"
|
||||
|
||||
def init(self, extend=""):
|
||||
self.id = self.ms(self.t)[:16]
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def action(self, action):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
host = "http://sir_new.tiansexyl.tv"
|
||||
t = str(int(time.time() * 1000))
|
||||
headers = {'User-Agent': 'okhttp-okgo/jeasonlzy', 'Connection': 'Keep-Alive',
|
||||
'Content-Type': 'application/x-www-form-urlencoded'}
|
||||
|
||||
def homeContent(self, filter):
|
||||
cateManual = {"演员": "actor", "分类": "avsearch", }
|
||||
classes = []
|
||||
for k in cateManual:
|
||||
classes.append({'type_name': k, 'type_id': cateManual[k]})
|
||||
j = {'code': 'homePage', 'mod': 'down', 'channel': 'self', 'via': 'agent', 'bundleId': 'com.tvlutv',
|
||||
'app_type': 'rn', 'os_version': '12.0.5', 'version': '3.2.3', 'oauth_type': 'android_rn',
|
||||
'oauth_id': self.id}
|
||||
|
||||
body = self.aes(j)
|
||||
data = self.post(f'{self.host}/api.php?t={self.t}', data=body, headers=self.headers).json()['data']
|
||||
data1 = self.aes(data, False)['data']
|
||||
self.r = data1['r']
|
||||
for i, d in enumerate(data1['avTag']):
|
||||
# if i == 4:
|
||||
# break
|
||||
classes.append({'type_name': d['name'], 'type_id': d['tag']})
|
||||
resutl = {}
|
||||
resutl["class"] = classes
|
||||
return resutl
|
||||
|
||||
def homeVideoContent(self):
|
||||
pass
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
id = tid.split("@@")
|
||||
result = {}
|
||||
result["page"] = pg
|
||||
result["pagecount"] = 9999
|
||||
result["limit"] = 90
|
||||
result["total"] = 999999
|
||||
if id[0] == 'avsearch':
|
||||
if pg == '1':
|
||||
j = {'code': 'avsearch', 'mod': 'search', 'channel': 'self', 'via': 'agent', 'bundleId': 'com.tvlutv',
|
||||
'app_type': 'rn', 'os_version': '12.0.5', 'version': '3.2.3', 'oauth_type': 'android_rn',
|
||||
'oauth_id': self.id}
|
||||
if len(id) > 1:
|
||||
j = {'code': 'find', 'mod': 'tag', 'channel': 'self', 'via': 'agent', 'bundleId': 'com.tvlutv',
|
||||
'app_type': 'rn', 'os_version': '12.0.5', 'version': '3.2.3', 'oauth_type': 'android_rn',
|
||||
'oauth_id': self.id, 'type': 'av', 'dis': 'new', 'page': str(pg), 'tag': id[1]}
|
||||
elif id[0] == 'actor':
|
||||
j = {'mod': 'actor', 'channel': 'self', 'via': 'agent', 'bundleId': 'com.tvlutv', 'app_type': 'rn',
|
||||
'os_version': '12.0.5', 'version': '3.2.3', 'oauth_type': 'android_rn', 'oauth_id': self.id,
|
||||
'page': str(pg), 'filter': ''}
|
||||
if len(id) > 1:
|
||||
j = {'code': 'eq', 'mod': 'actor', 'channel': 'self', 'via': 'agent', 'bundleId': 'com.tvlutv',
|
||||
'app_type': 'rn', 'os_version': '12.0.5', 'version': '3.2.3', 'oauth_type': 'android_rn',
|
||||
'oauth_id': self.id, 'page': str(pg), 'id': id[1], 'actor': id[2]}
|
||||
else:
|
||||
j = {'code': 'search', 'mod': 'av', 'channel': 'self', 'via': 'agent', 'bundleId': 'com.tvlutv',
|
||||
'app_type': 'rn', 'os_version': '12.0.5', 'version': '3.2.3', 'oauth_type': 'android_rn',
|
||||
'oauth_id': self.id, 'page': str(pg), 'tag': id[0]}
|
||||
|
||||
body = self.aes(j)
|
||||
data = self.post(f'{self.host}/api.php?t={self.t}', data=body, headers=self.headers).json()['data']
|
||||
data1 = self.aes(data, False)['data']
|
||||
videos = []
|
||||
if tid == 'avsearch' and len(id) == 1:
|
||||
for item in data1:
|
||||
videos.append({"vod_id": id[0] + "@@" + str(item.get('tags')), 'vod_name': item.get('name'),
|
||||
'vod_pic': self.imgs(item.get('ico')), 'vod_tag': 'folder',
|
||||
'style': {"type": "rect", "ratio": 1.33}})
|
||||
elif tid == 'actor' and len(id) == 1:
|
||||
for item in data1:
|
||||
videos.append({"vod_id": id[0] + "@@" + str(item.get('id')) + "@@" + item.get('name'),
|
||||
'vod_name': item.get('name'), 'vod_pic': self.imgs(item.get('cover')),
|
||||
'vod_tag': 'folder', 'style': {"type": "oval"}})
|
||||
else:
|
||||
for item in data1:
|
||||
if item.get('_id'):
|
||||
videos.append({"vod_id": str(item.get('id')), 'vod_name': item.get('title'),
|
||||
'vod_pic': self.imgs(item.get('cover_thumb') or item.get('cover_full')),
|
||||
'vod_remarks': item.get('good'), 'style': {"type": "rect", "ratio": 1.33}})
|
||||
result["list"] = videos
|
||||
return result
|
||||
|
||||
def detailContent(self, ids):
|
||||
id = ids[0]
|
||||
j = {'code': 'detail', 'mod': 'av', 'channel': 'self', 'via': 'agent', 'bundleId': 'com.tvlutv',
|
||||
'app_type': 'rn', 'os_version': '12.0.5', 'version': '3.2.3', 'oauth_type': 'android_rn',
|
||||
'oauth_id': self.id, 'id': id}
|
||||
body = self.aes(j)
|
||||
data = self.post(f'{self.host}/api.php?t={self.t}', data=body, headers=self.headers).json()['data']
|
||||
data1 = self.aes(data, False)['line']
|
||||
vod = {}
|
||||
play = []
|
||||
for itt in data1:
|
||||
a = itt['line'].get('s720')
|
||||
if a:
|
||||
b = a.split('.')
|
||||
b[0] = 'https://m3u8'
|
||||
a = '.'.join(b)
|
||||
play.append(itt['info']['tips'] + "$" + a)
|
||||
break
|
||||
vod["vod_play_from"] = 'LAV'
|
||||
vod["vod_play_url"] = "#".join(play)
|
||||
result = {"list": [vod]}
|
||||
return result
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
pass
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
url = self.getProxyUrl() + "&url=" + b64encode(id.encode('utf-8')).decode('utf-8') + "&type=m3u8"
|
||||
self.hh = {'User-Agent': 'dd', 'Connection': 'Keep-Alive', 'Referer': self.r}
|
||||
result = {}
|
||||
result["parse"] = 0
|
||||
result["url"] = url
|
||||
result["header"] = self.hh
|
||||
return result
|
||||
|
||||
def localProxy(self, param):
|
||||
url = param["url"]
|
||||
if param.get('type') == "m3u8":
|
||||
return self.vod(b64decode(url).decode('utf-8'))
|
||||
else:
|
||||
return self.img(url)
|
||||
|
||||
def vod(self, url):
|
||||
data = self.fetch(url, headers=self.hh).text
|
||||
key = bytes.fromhex("13d47399bda541b85e55830528d4e66f1791585b2d2216f23215c4c63ebace31")
|
||||
iv = bytes.fromhex(data[:32])
|
||||
data = data[32:]
|
||||
cipher = AES.new(key, AES.MODE_CFB, iv, segment_size=128)
|
||||
data_bytes = bytes.fromhex(data)
|
||||
decrypted = cipher.decrypt(data_bytes)
|
||||
encoded = decrypted.decode("utf-8").replace("\x08", "")
|
||||
return [200, "application/vnd.apple.mpegur", encoded]
|
||||
|
||||
def imgs(self, url):
|
||||
return self.getProxyUrl() + '&url=' + url
|
||||
|
||||
def img(self, url):
|
||||
type = url.split('.')[-1]
|
||||
data = self.fetch(url).text
|
||||
key = bytes.fromhex("ba78f184208d775e1553550f2037f4af22cdcf1d263a65b4d5c74536f084a4b2")
|
||||
iv = bytes.fromhex(data[:32])
|
||||
data = data[32:]
|
||||
cipher = AES.new(key, AES.MODE_CFB, iv, segment_size=128)
|
||||
data_bytes = bytes.fromhex(data)
|
||||
decrypted = cipher.decrypt(data_bytes)
|
||||
return [200, f"image/{type}", decrypted]
|
||||
|
||||
def ms(self, data, m=False):
|
||||
h = MD5.new()
|
||||
if m:
|
||||
h = SHA256.new()
|
||||
h.update(data.encode('utf-8'))
|
||||
return h.hexdigest()
|
||||
|
||||
def aes(self, data, operation=True):
|
||||
key = bytes.fromhex("620f15cfdb5c79c34b3940537b21eda072e22f5d7151456dec3932d7a2b22c53")
|
||||
t = str(int(time.time()))
|
||||
ivt = self.ms(t)
|
||||
if operation:
|
||||
data = json.dumps(data, separators=(',', ':'))
|
||||
iv = bytes.fromhex(ivt)
|
||||
else:
|
||||
iv = bytes.fromhex(data[:32])
|
||||
data = data[32:]
|
||||
cipher = AES.new(key, AES.MODE_CFB, iv, segment_size=128)
|
||||
if operation:
|
||||
data_bytes = data.encode('utf-8')
|
||||
encrypted = cipher.encrypt(data_bytes)
|
||||
ep = f'{ivt}{encrypted.hex()}'
|
||||
edata = f"data={ep}×tamp={t}0d27dfacef1338483561a46b246bf36d"
|
||||
sign = self.ms(self.ms(edata, True))
|
||||
edata = f"timestamp={t}&data={ep}&sign={sign}"
|
||||
return edata
|
||||
else:
|
||||
data_bytes = bytes.fromhex(data)
|
||||
decrypted = cipher.decrypt(data_bytes)
|
||||
return json.loads(decrypted.decode('utf-8'))
|
||||
|
||||
@@ -0,0 +1,349 @@
|
||||
# coding=utf-8
|
||||
# !/usr/bin/python
|
||||
|
||||
"""
|
||||
|
||||
作者 丢丢喵 内容均从互联网收集而来 仅供交流学习使用 严禁用于商业用途 请于24小时内删除
|
||||
====================Diudiumiao====================
|
||||
|
||||
"""
|
||||
|
||||
from Crypto.Util.Padding import unpad
|
||||
from Crypto.Util.Padding import pad
|
||||
from urllib.parse import urlparse
|
||||
from urllib.parse import unquote
|
||||
from Crypto.Cipher import ARC4
|
||||
from urllib.parse import quote
|
||||
from base.spider import Spider
|
||||
from Crypto.Cipher import AES
|
||||
from datetime import datetime
|
||||
from bs4 import BeautifulSoup
|
||||
from base64 import b64decode
|
||||
import concurrent.futures
|
||||
import urllib.request
|
||||
import urllib.parse
|
||||
import datetime
|
||||
import binascii
|
||||
import requests
|
||||
import base64
|
||||
import zlib
|
||||
import json
|
||||
import time
|
||||
import sys
|
||||
import re
|
||||
import os
|
||||
|
||||
sys.path.append('..')
|
||||
|
||||
xurl = "https://yhecfhhm.top:2549" # http://104.255.229.161:6688/?r=aHR0cDovL2hoZTQ5LmNvbS8=
|
||||
|
||||
headerx = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.87 Safari/537.36'
|
||||
}
|
||||
|
||||
class Spider(Spider):
|
||||
|
||||
def getName(self):
|
||||
return "丢丢喵"
|
||||
|
||||
def init(self, extend):
|
||||
pass
|
||||
|
||||
def searchContentPage(self, key, quick, pg):
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def homeVideoContent(self):
|
||||
pass
|
||||
|
||||
def extract_middle_text(self, text, start_str, end_str, pl, start_index1: str = '', end_index2: str = ''):
|
||||
if pl == 3:
|
||||
plx = []
|
||||
while True:
|
||||
start_index = text.find(start_str)
|
||||
if start_index == -1:
|
||||
break
|
||||
end_index = text.find(end_str, start_index + len(start_str))
|
||||
if end_index == -1:
|
||||
break
|
||||
middle_text = text[start_index + len(start_str):end_index]
|
||||
plx.append(middle_text)
|
||||
text = text.replace(start_str + middle_text + end_str, '')
|
||||
if len(plx) > 0:
|
||||
purl = ''
|
||||
for i in range(len(plx)):
|
||||
matches = re.findall(start_index1, plx[i])
|
||||
output = ""
|
||||
for match in matches:
|
||||
match3 = re.search(r'(?:^|[^0-9])(\d+)(?:[^0-9]|$)', match[1])
|
||||
if match3:
|
||||
number = match3.group(1)
|
||||
else:
|
||||
number = 0
|
||||
if 'http' not in match[0]:
|
||||
output += f"#{match[1]}${number}{xurl}{match[0]}"
|
||||
else:
|
||||
output += f"#{match[1]}${number}{match[0]}"
|
||||
output = output[1:]
|
||||
purl = purl + output + "$$$"
|
||||
purl = purl[:-3]
|
||||
return purl
|
||||
else:
|
||||
return ""
|
||||
else:
|
||||
start_index = text.find(start_str)
|
||||
if start_index == -1:
|
||||
return ""
|
||||
end_index = text.find(end_str, start_index + len(start_str))
|
||||
if end_index == -1:
|
||||
return ""
|
||||
|
||||
if pl == 0:
|
||||
middle_text = text[start_index + len(start_str):end_index]
|
||||
return middle_text.replace("\\", "")
|
||||
|
||||
if pl == 1:
|
||||
middle_text = text[start_index + len(start_str):end_index]
|
||||
matches = re.findall(start_index1, middle_text)
|
||||
if matches:
|
||||
jg = ' '.join(matches)
|
||||
return jg
|
||||
|
||||
if pl == 2:
|
||||
middle_text = text[start_index + len(start_str):end_index]
|
||||
matches = re.findall(start_index1, middle_text)
|
||||
if matches:
|
||||
new_list = [f'{item}' for item in matches]
|
||||
jg = '$$$'.join(new_list)
|
||||
return jg
|
||||
|
||||
def decrypt_data(self, Toubu, Zhongbu):
|
||||
key_bytes = self.get_key_bytes(Toubu)
|
||||
iv_bytes = key_bytes
|
||||
encrypted_bytes = self.decode_encrypted_data(Zhongbu)
|
||||
cipher = self.create_aes_cipher(key_bytes, iv_bytes)
|
||||
decrypted_padded = self.decrypt_data_bytes(cipher, encrypted_bytes)
|
||||
decrypted_bytes = self.unpad_data(decrypted_padded)
|
||||
decompressed_bytes = self.try_decompress(decrypted_bytes)
|
||||
result = self.decode_to_string(decompressed_bytes)
|
||||
return result
|
||||
|
||||
def get_key_bytes(self, Toubu):
|
||||
return Toubu[:16].encode('utf-8')
|
||||
|
||||
def decode_encrypted_data(self, Zhongbu):
|
||||
return base64.b64decode(Zhongbu.replace('\n', '').strip())
|
||||
|
||||
def create_aes_cipher(self, key_bytes, iv_bytes):
|
||||
return AES.new(key_bytes, AES.MODE_CBC, iv_bytes)
|
||||
|
||||
def decrypt_data_bytes(self, cipher, encrypted_bytes):
|
||||
return cipher.decrypt(encrypted_bytes)
|
||||
|
||||
def unpad_data(self, decrypted_padded):
|
||||
return unpad(decrypted_padded, AES.block_size)
|
||||
|
||||
def try_decompress(self, decrypted_bytes):
|
||||
try:
|
||||
return zlib.decompress(decrypted_bytes, zlib.MAX_WBITS | 32)
|
||||
except:
|
||||
try:
|
||||
return zlib.decompress(decrypted_bytes, -zlib.MAX_WBITS)
|
||||
except:
|
||||
return None
|
||||
|
||||
def decode_to_string(self, decompressed_bytes):
|
||||
return decompressed_bytes.decode('utf-8', errors='ignore')
|
||||
|
||||
def homeContent(self, filter):
|
||||
result = {"class": []}
|
||||
res = self.get_main_page()
|
||||
Toubu = self.extract_toubu(res)
|
||||
Zhongbu = self.extract_zhongbu(res)
|
||||
decrypted = self.decrypt_data(Toubu, Zhongbu)
|
||||
res1 = self.extract_movie_channel(decrypted)
|
||||
soups = self.parse_html(res1)
|
||||
self.process_soups(soups, result)
|
||||
return result
|
||||
|
||||
def get_main_page(self):
|
||||
detail = requests.get(url=xurl + "/main.html", headers=headerx)
|
||||
detail.encoding = "utf-8"
|
||||
return detail.text
|
||||
|
||||
def extract_toubu(self, res):
|
||||
return self.extract_middle_text(res, '头部加载中</p><div data-content="">', '<', 0)
|
||||
|
||||
def extract_zhongbu(self, res):
|
||||
return self.extract_middle_text(res, '中部加载中</p><div data-content="">', '<', 0)
|
||||
|
||||
def extract_movie_channel(self, decrypted):
|
||||
return self.extract_middle_text(decrypted, '电影频道</a></li>', '</ul>', 0)
|
||||
|
||||
def parse_html(self, res1):
|
||||
return BeautifulSoup(res1, "lxml")
|
||||
|
||||
def process_soups(self, soups, result):
|
||||
for soup in soups:
|
||||
self.process_single_soup(soup, result)
|
||||
|
||||
def process_single_soup(self, soup, result):
|
||||
vods = self.find_links(soup)
|
||||
self.process_vods(vods, result)
|
||||
|
||||
def find_links(self, soup):
|
||||
return soup.find_all('a')
|
||||
|
||||
def process_vods(self, vods, result):
|
||||
for vod in vods:
|
||||
self.extract_and_append(vod, result)
|
||||
|
||||
def extract_and_append(self, vod, result):
|
||||
name = vod.text.strip()
|
||||
id = vod['href']
|
||||
result["class"].append({"type_id": id, "type_name": name})
|
||||
|
||||
def categoryContent(self, cid, pg, filter, ext):
|
||||
videos = []
|
||||
page = self.get_page_number(pg)
|
||||
url = self.build_category_url(cid, page)
|
||||
res = self.get_category_page(url)
|
||||
Toubu = self.extract_toubu(res)
|
||||
Zhongbu = self.extract_zhongbu(res)
|
||||
decrypted = self.decrypt_data(Toubu, Zhongbu)
|
||||
doc = self.parse_html(decrypted)
|
||||
soups = self.find_vodlist_divs(doc)
|
||||
self.process_vodlist_divs(soups, videos)
|
||||
result = self.build_category_result(videos, pg)
|
||||
return result
|
||||
|
||||
def get_page_number(self, pg):
|
||||
return int(pg) if pg else 1
|
||||
|
||||
def build_category_url(self, cid, page):
|
||||
return f'{xurl}{cid}&page={str(page)}'
|
||||
|
||||
def get_category_page(self, url):
|
||||
detail = requests.get(url=url, headers=headerx)
|
||||
detail.encoding = "utf-8"
|
||||
return detail.text
|
||||
|
||||
def extract_toubu(self, res):
|
||||
return self.extract_middle_text(res, '头部加载中</p><div data-content="">', '<', 0)
|
||||
|
||||
def extract_zhongbu(self, res):
|
||||
return self.extract_middle_text(res, '中部加载中</p><div data-content="">', '<', 0)
|
||||
|
||||
def parse_html(self, decrypted):
|
||||
return BeautifulSoup(decrypted, "lxml")
|
||||
|
||||
def find_vodlist_divs(self, doc):
|
||||
return doc.find_all('div', class_="vodlist dylist")
|
||||
|
||||
def process_vodlist_divs(self, soups, videos):
|
||||
for soup in soups:
|
||||
self.process_single_vodlist(soup, videos)
|
||||
|
||||
def process_single_vodlist(self, soup, videos):
|
||||
vods = self.find_links(soup)
|
||||
for vod in vods:
|
||||
video = self.extract_video_info(vod)
|
||||
if video:
|
||||
videos.append(video)
|
||||
|
||||
def find_links(self, soup):
|
||||
return soup.find_all('a')
|
||||
|
||||
def extract_video_info(self, vod):
|
||||
names = vod.find('div', class_="vodname")
|
||||
if names is None:
|
||||
return None
|
||||
name = names.text.strip()
|
||||
id = vod['href']
|
||||
pics = vod.find('div', class_="vodpic lazyload")
|
||||
pic = pics['data-original']
|
||||
remarks = vod.find('span', class_="time")
|
||||
year = remarks.text.strip() if remarks else ""
|
||||
return {"vod_id": id,"vod_name": name,"vod_pic": pic,"vod_year": year,}
|
||||
|
||||
def build_category_result(self, videos, pg):
|
||||
result = {'list': videos}
|
||||
result['page'] = pg
|
||||
result['pagecount'] = 9999
|
||||
result['limit'] = 90
|
||||
result['total'] = 999999
|
||||
return result
|
||||
|
||||
def detailContent(self, ids):
|
||||
did = self.get_first_id(ids)
|
||||
videos = self.build_video_info(did)
|
||||
result = self.build_detail_result(videos)
|
||||
return result
|
||||
|
||||
def get_first_id(self, ids):
|
||||
return ids[0]
|
||||
|
||||
def build_video_info(self, did):
|
||||
return [{"vod_id": did,"vod_play_from": "保重身体","vod_play_url": did}]
|
||||
|
||||
def build_detail_result(self, videos):
|
||||
result = {}
|
||||
result['list'] = videos
|
||||
return result
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
res = self.get_player_page(id)
|
||||
Toubu = self.extract_toubu(res)
|
||||
Zhongbu = self.extract_zhongbu(res)
|
||||
decrypted = self.decrypt_data(Toubu, Zhongbu)
|
||||
real_url = self.extract_real_url(decrypted)
|
||||
result = self.build_player_result(real_url)
|
||||
return result
|
||||
|
||||
def get_player_page(self, id):
|
||||
detail = requests.get(url=f"{xurl}{id}", headers=headerx)
|
||||
detail.encoding = "utf-8"
|
||||
return detail.text
|
||||
|
||||
def extract_toubu(self, res):
|
||||
return self.extract_middle_text(res, '头部加载中</p><div data-content="">', '<', 0)
|
||||
|
||||
def extract_zhongbu(self, res):
|
||||
return self.extract_middle_text(res, '中部加载中</p><div data-content="">', '<', 0)
|
||||
|
||||
def extract_real_url(self, decrypted):
|
||||
pattern = r'(https?://[^\s"\'<>]+?\.m3u8|https?:\\/\\/[^\s"\'<>]+?\.m3u8)'
|
||||
match = re.search(pattern, decrypted)
|
||||
return match.group(1).replace('\\/', '/')
|
||||
|
||||
def build_player_result(self, real_url):
|
||||
result = {}
|
||||
result["parse"] = 0
|
||||
result["playUrl"] = ''
|
||||
result["url"] = real_url
|
||||
result["header"] = headerx
|
||||
return result
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
return self.searchContentPage(key, quick, '1')
|
||||
|
||||
def localProxy(self, params):
|
||||
if params['type'] == "m3u8":
|
||||
return self.proxyM3u8(params)
|
||||
elif params['type'] == "media":
|
||||
return self.proxyMedia(params)
|
||||
elif params['type'] == "ts":
|
||||
return self.proxyTs(params)
|
||||
return None
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
import sys
|
||||
import re
|
||||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
from base.spider import Spider
|
||||
from urllib.parse import urljoin, quote,unquote
|
||||
|
||||
class Spider(Spider):
|
||||
def getName(self):
|
||||
return "WhosTV"
|
||||
|
||||
def init(self, extend=""):
|
||||
self.host = "https://whos.tv"
|
||||
self.header = {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
|
||||
"Referer": self.host
|
||||
}
|
||||
|
||||
def _decode_cover(self, encoded):
|
||||
"""解密 data-cover-src(还原前端 coolDecrypt 函数)"""
|
||||
if not encoded:
|
||||
return ""
|
||||
try:
|
||||
key_hex = encoded[-2:]
|
||||
key = int(key_hex, 16)
|
||||
data_hex = encoded[:-2]
|
||||
chars = []
|
||||
for i in range(0, len(data_hex), 2):
|
||||
byte_val = int(data_hex[i:i+2], 16)
|
||||
chars.append(chr(byte_val ^ key))
|
||||
return ''.join(chars)
|
||||
except:
|
||||
return ""
|
||||
|
||||
|
||||
def homeContent(self, filter):
|
||||
result = {}
|
||||
result['class'] = [
|
||||
{'type_name': '影片库', 'type_id': '/videos'},
|
||||
{'type_name': '女优库', 'type_id': '/actresses'}
|
||||
]
|
||||
return result
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
result = {}
|
||||
url = f"{self.host}{tid}"
|
||||
if int(pg) > 1:
|
||||
url += f"/page-{pg}"
|
||||
|
||||
rsp = self.fetch(url, headers=self.header)
|
||||
soup = BeautifulSoup(rsp.text, 'html.parser')
|
||||
videos = []
|
||||
|
||||
# 女优名录页
|
||||
if tid == "/actresses":
|
||||
items = soup.find_all('a', href=re.compile(r'^/actresses/.'))
|
||||
for item in items:
|
||||
img = item.find('img')
|
||||
if not img:
|
||||
continue
|
||||
name = img.get('alt', '').strip()
|
||||
href = item.get('href')
|
||||
if not name or href == "/actresses" or "page-" in href:
|
||||
continue
|
||||
pic_url = img.get('src', '')
|
||||
count_text = ""
|
||||
icon_span = item.find('span', class_=re.compile(r'icon-\[lucide--film\]'))
|
||||
if icon_span:
|
||||
parent_flex = icon_span.find_parent('span', class_='flex')
|
||||
if parent_flex:
|
||||
count_text = parent_flex.get_text(strip=True) + "部作品"
|
||||
videos.append({
|
||||
"vod_id": href,
|
||||
"vod_name": name,
|
||||
"vod_pic": pic_url,
|
||||
"vod_remarks": count_text if count_text else "作品集",
|
||||
"vod_tag": "folder"
|
||||
})
|
||||
# 影片网格页(包括女优个人页的作品列表)
|
||||
else:
|
||||
items = soup.find_all('a', href=re.compile(r'^/videos/.'))
|
||||
for item in items:
|
||||
h3 = item.find('h3')
|
||||
v_name = h3.get_text(strip=True) if h3 else item.get('alt', '')
|
||||
if not v_name:
|
||||
continue
|
||||
div_cover = item.find('div', attrs={'data-cover-src': True})
|
||||
if div_cover:
|
||||
encoded = div_cover.get('data-cover-src')
|
||||
real_pic = self._decode_cover(encoded)
|
||||
else:
|
||||
real_pic = ""
|
||||
real_pic = real_pic
|
||||
remarks = self.regStr(v_name, r'([A-Z0-9]+-[0-9]+)')
|
||||
videos.append({
|
||||
"vod_id": item.get('href'),
|
||||
"vod_name": v_name,
|
||||
"vod_pic": real_pic,
|
||||
"vod_remarks": remarks if remarks else ""
|
||||
})
|
||||
|
||||
result['list'] = videos
|
||||
result['page'] = pg
|
||||
result['pagecount'] = 999
|
||||
result['limit'] = len(videos)
|
||||
result['total'] = 9999
|
||||
|
||||
if tid.startswith("/actresses/"):
|
||||
h1_tag = soup.find('h1')
|
||||
if h1_tag:
|
||||
result['type_name'] = h1_tag.get_text(strip=True)
|
||||
return result
|
||||
|
||||
def detailContent(self, ids):
|
||||
vodId = ids[0]
|
||||
if vodId.startswith("/actresses/"):
|
||||
return self.categoryContent(vodId, "1", None, None)
|
||||
|
||||
url = self.host + vodId
|
||||
rsp = self.fetch(url, headers=self.header)
|
||||
soup = BeautifulSoup(rsp.text, 'html.parser')
|
||||
|
||||
title_meta = soup.find('meta', property="og:title")
|
||||
title = title_meta.get('content') if title_meta else ""
|
||||
|
||||
pic_meta = soup.find('meta', property="og:image")
|
||||
pic = pic_meta.get('content') if pic_meta else ""
|
||||
pic = pic
|
||||
|
||||
source = soup.find('source', type="application/x-mpegURL")
|
||||
play_url = source.get('src') if source else ""
|
||||
|
||||
actor_tags = soup.select('a[href^="/actresses/"]')
|
||||
actors = ",".join([a.get_text(strip=True) for a in actor_tags if a.get_text(strip=True)])
|
||||
|
||||
tag_tags = soup.select('a[href^="/tags/"] span.truncate')
|
||||
tags = ",".join([t.get_text(strip=True) for t in tag_tags])
|
||||
|
||||
vod = {
|
||||
"vod_id": vodId,
|
||||
"vod_name": title,
|
||||
"vod_pic": pic,
|
||||
"type_name": tags,
|
||||
"vod_actor": actors,
|
||||
"vod_content": title,
|
||||
"vod_play_from": "WhosTV",
|
||||
"vod_play_url": "全高清$" + play_url if play_url else ""
|
||||
}
|
||||
return {'list': [vod]}
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
return {
|
||||
"parse": 0,
|
||||
"url": id,
|
||||
"header": {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
|
||||
"Referer": "https://whos.tv/",
|
||||
"Origin": "https://whos.tv"
|
||||
}
|
||||
}
|
||||
|
||||
def searchContent(self, key, quick):
|
||||
url = f"{self.host}/result?serach={key}"
|
||||
rsp = self.fetch(url, headers=self.header)
|
||||
soup = BeautifulSoup(rsp.text, 'html.parser')
|
||||
videos = []
|
||||
|
||||
items = soup.find_all('a', href=re.compile(r'^/videos/.'))
|
||||
for item in items:
|
||||
h3 = item.find('h3')
|
||||
if h3:
|
||||
v_name = h3.get_text(strip=True)
|
||||
div_cover = item.find('div', attrs={'data-cover-src': True})
|
||||
if div_cover:
|
||||
encoded = div_cover.get('data-cover-src')
|
||||
real_pic = self._decode_cover(encoded)
|
||||
else:
|
||||
real_pic = ""
|
||||
real_pic = real_pic
|
||||
remarks = self.regStr(v_name, r'([A-Z0-9]+-[0-9]+)')
|
||||
videos.append({
|
||||
"vod_id": item.get('href'),
|
||||
"vod_name": v_name,
|
||||
"vod_pic": real_pic,
|
||||
"vod_remarks": remarks if remarks else ""
|
||||
})
|
||||
return {"list": videos}
|
||||
@@ -0,0 +1,310 @@
|
||||
# coding=utf-8
|
||||
# !/usr/bin/python
|
||||
|
||||
"""
|
||||
|
||||
作者 丢丢喵 🚓 内容均从互联网收集而来 仅供交流学习使用 版权归原创者所有 如侵犯了您的权益 请通知作者 将及时删除侵权内容
|
||||
====================Diudiumiao====================
|
||||
|
||||
"""
|
||||
|
||||
from Crypto.Util.Padding import unpad
|
||||
from Crypto.Util.Padding import pad
|
||||
from urllib.parse import unquote
|
||||
from Crypto.Cipher import ARC4
|
||||
from urllib.parse import quote
|
||||
from base.spider import Spider
|
||||
from Crypto.Cipher import AES
|
||||
from datetime import datetime
|
||||
from bs4 import BeautifulSoup
|
||||
from base64 import b64decode
|
||||
import urllib.request
|
||||
import urllib.parse
|
||||
import datetime
|
||||
import binascii
|
||||
import requests
|
||||
import hashlib
|
||||
import base64
|
||||
import json
|
||||
import time
|
||||
import sys
|
||||
import re
|
||||
import os
|
||||
|
||||
sys.path.append('..')
|
||||
|
||||
xurl = "https://www.r5nu.com"
|
||||
|
||||
headerx = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.87 Safari/537.36'
|
||||
}
|
||||
|
||||
class Spider(Spider):
|
||||
global xurl
|
||||
global headerx
|
||||
|
||||
def getName(self):
|
||||
return "首页"
|
||||
|
||||
def init(self, extend):
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def generate_signature_params(self, payload):
|
||||
SECRET_KEY = "s5dVVmAyt75nCrHPAdV2y1i+koJNaxh6jNTkiKgSSurRQDJSt4AH7Z8GawIF92Tc"
|
||||
t = str(int(time.time() * 1000))
|
||||
signature_s = hashlib.md5((t + SECRET_KEY).encode('utf-8')).hexdigest()
|
||||
final_params = payload.copy()
|
||||
final_params['t'] = t
|
||||
final_params['s'] = signature_s
|
||||
return final_params
|
||||
|
||||
def homeContent(self, filter):
|
||||
result = {"class": []}
|
||||
menu_data = self.fetch_menu_data()
|
||||
result["class"] = self.parse_menu_data(menu_data)
|
||||
return result
|
||||
|
||||
def fetch_menu_data(self):
|
||||
payload = {
|
||||
'action': 'getmenu',
|
||||
'vtype': '',
|
||||
'index': '-1',
|
||||
'state': '0',
|
||||
'ttt': '',
|
||||
'p': ''
|
||||
}
|
||||
final_params = self.generate_signature_params(payload)
|
||||
urlz = f'{xurl}/web/abcdefg.ashx'
|
||||
response = requests.post(url=urlz, headers=headerx, data=final_params)
|
||||
res = response.text
|
||||
return res
|
||||
|
||||
def parse_menu_data(self, html_content):
|
||||
doc = BeautifulSoup(html_content, "lxml")
|
||||
soups = doc.find_all('div', class_="head_bottom")[:2]
|
||||
class_list = []
|
||||
for soup in soups:
|
||||
vods = soup.find_all('li')
|
||||
for vod in vods:
|
||||
name = self.process_category_name(vod.text.strip())
|
||||
id = self.process_category_id(vod['onclick'])
|
||||
class_list.append({"type_id": id, "type_name": name})
|
||||
return class_list
|
||||
|
||||
def process_category_name(self, name):
|
||||
replacements = {
|
||||
'npiik685Lck0FDMUZdiEww==': '亚洲无码',
|
||||
's/1sDU0O4YiSQcOWSJSU0w==': '欧美无码',
|
||||
'DwciqD3gIgL7nw/s7sILpA==': '中文字幕',
|
||||
'w3MG36tpsp7Q2laGGkz8/w==': '经典三级',
|
||||
'QlWqEFTdBnS7fJE2QeRWPA==': '国产主播',
|
||||
'QWTsC1myBO2tSCLQ/sEnew==': '韩国主播',
|
||||
'gN2L7RnWjEzBfcovmlSAmQ==': 'ASMR',
|
||||
'8KibIBlYoyvWx9DCZhxk2w==': '恐怖色情',
|
||||
'1BnJg5tSsn5AM2C07wItyA==': '网红视频',
|
||||
'CCNquvXakHIecCOudFO8yg==': '国产视频',
|
||||
'sUaKzVcQ5ehvRZ3kt27KAQ==': '人妖伪娘',
|
||||
'B5RKLzIG+WCEscBNxlwr+A==': '动漫卡通',
|
||||
'WBm+mcc3ZOug3T/VO4KxRA==': '华人原创',
|
||||
'nA3l008NjTxmTW+BuW5pOQ==': 'JVID',
|
||||
'eRP3JFAXTQwWoiTAu95jeA==': 'SWAG',
|
||||
'fGfFjWIpFa1ughCZLhAf9w==': '明星换脸'
|
||||
}
|
||||
for old, new in replacements.items():
|
||||
name = name.replace(old, new)
|
||||
return name
|
||||
|
||||
def process_category_id(self, onclick_value):
|
||||
id = onclick_value.replace("toLinkpage('video-", '').replace(".html');", '').replace("duanpian-", '')
|
||||
return id
|
||||
|
||||
def homeVideoContent(self):
|
||||
pass
|
||||
|
||||
def categoryContent(self, cid, pg, filter, ext):
|
||||
page = self.process_page_number(pg)
|
||||
payload = self.build_category_payload(cid, page)
|
||||
data = self.fetch_category_data(payload)
|
||||
videos = self.parse_video_list(data)
|
||||
result = self.build_category_result(videos, pg)
|
||||
return result
|
||||
|
||||
def process_page_number(self, pg):
|
||||
if pg:
|
||||
return int(pg)
|
||||
else:
|
||||
return 1
|
||||
|
||||
def build_category_payload(self, cid, page):
|
||||
payload = {
|
||||
'action': 'getvideos',
|
||||
'vtype': cid,
|
||||
'pageindex': str(page),
|
||||
'pagesize': '12',
|
||||
'tags': '全部',
|
||||
'sortindex': '1'
|
||||
}
|
||||
return payload
|
||||
|
||||
def fetch_category_data(self, payload):
|
||||
final_params = self.generate_signature_params(payload)
|
||||
timestamp = int(time.time() * 1000)
|
||||
urlz = f'{xurl}/web/abcdefg.ashx?v={timestamp}'
|
||||
detail = requests.post(url=urlz, headers=headerx, data=final_params)
|
||||
detail.encoding = "utf-8"
|
||||
return detail.json()
|
||||
|
||||
def parse_video_list(self, data):
|
||||
videos = []
|
||||
for vod in data['videos']:
|
||||
name = vod['title']
|
||||
id = vod['vurl']
|
||||
pic = vod['coverimg']
|
||||
remark = vod['updatedate']
|
||||
video = {
|
||||
"vod_id": id,
|
||||
"vod_name": name,
|
||||
"vod_pic": pic,
|
||||
"vod_remarks": remark
|
||||
}
|
||||
videos.append(video)
|
||||
return videos
|
||||
|
||||
def build_category_result(self, videos, pg):
|
||||
result = {
|
||||
'list': videos,
|
||||
'page': pg,
|
||||
'pagecount': 9999,
|
||||
'limit': 90,
|
||||
'total': 999999
|
||||
}
|
||||
return result
|
||||
|
||||
def detailContent(self, ids):
|
||||
did = ids[0]
|
||||
videos = self.build_video_details(did)
|
||||
result = self.build_detail_result(videos)
|
||||
return result
|
||||
|
||||
def build_video_details(self, did):
|
||||
videos = []
|
||||
videos.append({
|
||||
"vod_id": did,
|
||||
"vod_play_from": "在线观看",
|
||||
"vod_play_url": did
|
||||
})
|
||||
return videos
|
||||
|
||||
def build_detail_result(self, videos):
|
||||
result = {
|
||||
'list': videos
|
||||
}
|
||||
return result
|
||||
|
||||
def _is_url_valid(self, url: str, headers: dict, timeout: int = 1) -> bool:
|
||||
try:
|
||||
detail = requests.get(url=url, headers=headerx, timeout=timeout)
|
||||
return detail.status_code == 200 and bool(detail.text.strip())
|
||||
except requests.exceptions.RequestException:
|
||||
return False
|
||||
|
||||
def _find_valid_url(self, id: str, url_templates: list, headers: dict) -> str:
|
||||
for template in url_templates:
|
||||
url = template.format(id)
|
||||
if self._is_url_valid(url, headers):
|
||||
return url
|
||||
return url_templates[-1].format(id)
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
encoded_id = self.encode_video_id(id)
|
||||
url_template_map = self.get_url_template_map()
|
||||
final_id = self.find_valid_play_url(encoded_id, url_template_map)
|
||||
result = self.build_player_result(final_id)
|
||||
return result
|
||||
|
||||
def encode_video_id(self, id):
|
||||
return quote(id, safe='/.')
|
||||
|
||||
def get_url_template_map(self):
|
||||
return {
|
||||
'yazhouwuma': ['https://3x1.lv99t.com/changpian{}', 'https://2x1.lv99t.com/changpian{}',
|
||||
'https://1x1.lv99t.com/changpian{}'],
|
||||
'oumeiwuma': ['https://3x1.lv99t.com/changpian{}', 'https://2x1.lv99t.com/changpian{}',
|
||||
'https://1x1.lv99t.com/changpian{}'],
|
||||
'zhongwenzimu': ['https://3x1.lv99t.com/changpian{}', 'https://2x1.lv99t.com/changpian{}',
|
||||
'https://1x1.lv99t.com/changpian{}'],
|
||||
'jingdiansanji': ['https://3x1.lv99t.com/changpian{}', 'https://2x1.lv99t.com/changpian{}',
|
||||
'https://1x1.lv99t.com/changpian{}'],
|
||||
'guochanzhubo': ['https://1x1.lv99t.com/changpian{}', 'https://2x1.lv99t.com/changpian{}',
|
||||
'https://3x1.lv99t.com/changpian{}'],
|
||||
'hanguozhubo': ['https://3x1.lv99t.com/changpian{}', 'https://2x1.lv99t.com/changpian{}',
|
||||
'https://1x1.lv99t.com/changpian{}'],
|
||||
'asmr': ['https://3x1.lv99t.com/changpian{}', 'https://2x1.lv99t.com/changpian{}',
|
||||
'https://1x1.lv99t.com/changpian{}'],
|
||||
'kongbu': ['https://3x1.lv99t.com/changpian{}', 'https://2x1.lv99t.com/changpian{}',
|
||||
'https://1x1.lv99t.com/changpian{}'],
|
||||
'zhubo': ['https://1x2.lv99t.com{}', 'https://2x2.lv99t.com{}',
|
||||
'https://3x2.lv99t.com{}'],
|
||||
'katong': ['https://3x2.lv99t.com{}', 'https://2x2.lv99t.com{}',
|
||||
'https://1x2.lv99t.com{}'],
|
||||
'wanghongshipin': ['https://3x1.lv99t.com/duanpian{}', 'https://2x1.lv99t.com/duanpian{}',
|
||||
'https://1x1.lv99t.com/duanpian{}'],
|
||||
'biantairenyao': ['https://3x1.lv99t.com/duanpian{}', 'https://2x1.lv99t.com/duanpian{}',
|
||||
'https://1x1.lv99t.com/duanpian{}'],
|
||||
'weiniang': ['https://3x1.lv99t.com/duanpian{}', 'https://2x1.lv99t.com/duanpian{}',
|
||||
'https://1x1.lv99t.com/duanpian{}'],
|
||||
'huaren': ['https://3x1.lv99t.com/duanpian{}', 'https://2x1.lv99t.com/duanpian{}',
|
||||
'https://1x1.lv99t.com/duanpian{}'],
|
||||
'jvid': ['https://3x1.lv99t.com/duanpian{}', 'https://2x1.lv99t.com/duanpian{}',
|
||||
'https://1x1.lv99t.com/duanpian{}'],
|
||||
'swag': ['https://2x1.lv99t.com/duanpian{}', 'https://1x1.lv99t.com/duanpian{}',
|
||||
'https://3x1.lv99t.com/duanpian{}'],
|
||||
'AI': ['https://3x1.lv99t.com/duanpian{}', 'https://2x1.lv99t.com/duanpian{}',
|
||||
'https://1x1.lv99t.com/duanpian{}'],
|
||||
}
|
||||
|
||||
def find_valid_play_url(self, id, url_template_map):
|
||||
final_id = id
|
||||
for type_name, templates in url_template_map.items():
|
||||
if type_name in id:
|
||||
final_id = self._find_valid_url(id, templates, headers=headerx)
|
||||
break
|
||||
return final_id
|
||||
|
||||
def build_player_result(self, final_id):
|
||||
result = {
|
||||
"parse": 0,
|
||||
"playUrl": '',
|
||||
"url": final_id,
|
||||
"header": headerx
|
||||
}
|
||||
return result
|
||||
|
||||
def searchContentPage(self, key, quick, pg):
|
||||
pass
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
return self.searchContentPage(key, quick, '1')
|
||||
|
||||
def localProxy(self, params):
|
||||
if params['type'] == "m3u8":
|
||||
return self.proxyM3u8(params)
|
||||
elif params['type'] == "media":
|
||||
return self.proxyMedia(params)
|
||||
elif params['type'] == "ts":
|
||||
return self.proxyTs(params)
|
||||
return None
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,495 @@
|
||||
# coding=utf-8
|
||||
# !/usr/bin/python
|
||||
|
||||
"""
|
||||
|
||||
作者 丢丢喵 🚓 内容均从互联网收集而来 仅供交流学习使用 版权归原创者所有 如侵犯了您的权益 请通知作者 将及时删除侵权内容
|
||||
====================Diudiumiao====================
|
||||
|
||||
"""
|
||||
|
||||
from Crypto.Util.Padding import unpad
|
||||
from Crypto.Util.Padding import pad
|
||||
from urllib.parse import unquote
|
||||
from Crypto.Cipher import ARC4
|
||||
from urllib.parse import quote
|
||||
from base.spider import Spider
|
||||
from Crypto.Cipher import AES
|
||||
from datetime import datetime
|
||||
from bs4 import BeautifulSoup
|
||||
from base64 import b64decode
|
||||
import concurrent.futures
|
||||
import urllib.request
|
||||
import urllib.parse
|
||||
import datetime
|
||||
import binascii
|
||||
import requests
|
||||
import base64
|
||||
import json
|
||||
import time
|
||||
import sys
|
||||
import re
|
||||
import os
|
||||
|
||||
sys.path.append('..')
|
||||
|
||||
xurl = "https://css.gztzyyp.com" # 首页 https://kea9da.com/home/
|
||||
|
||||
xurl1 = "https://chees.sxgtlj.com"
|
||||
|
||||
xurl2 = "https://kea9da.com"
|
||||
|
||||
headerx = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.87 Safari/537.36'
|
||||
}
|
||||
|
||||
class Spider(Spider):
|
||||
|
||||
def getName(self):
|
||||
return "首页"
|
||||
|
||||
def init(self, extend):
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def homeContent(self, filter):
|
||||
result = {}
|
||||
result = {"class": [{"type_id": "4", "type_name": "自拍视频"},
|
||||
{"type_id": "5", "type_name": "淫妻作乐"},
|
||||
{"type_id": "142", "type_name": "热门探花"},
|
||||
{"type_id": "64", "type_name": "国产传媒"},
|
||||
{"type_id": "6", "type_name": "开放青年"},
|
||||
{"type_id": "119", "type_name": "JVID专区"},
|
||||
{"type_id": "139", "type_name": "SWAG专区"},
|
||||
{"type_id": "60", "type_name": "直播录像"},
|
||||
{"type_id": "157", "type_name": "AI换脸"},
|
||||
{"type_id": "9", "type_name": "短视频"},
|
||||
{"type_id": "140", "type_name": "无码破解"},
|
||||
{"type_id": "39", "type_name": "动漫卡通"},
|
||||
{"type_id": "58", "type_name": "女性向爱纯"},
|
||||
{"type_id": "65", "type_name": "GIGA女战士"},
|
||||
{"type_id": "141", "type_name": "男男视频"},
|
||||
{"type_id": "40", "type_name": "无码中字"},
|
||||
{"type_id": "43", "type_name": "熟女人妻"},
|
||||
{"type_id": "44", "type_name": "美艳巨乳"},
|
||||
{"type_id": "41", "type_name": "SM系列"},
|
||||
{"type_id": "45", "type_name": "丝袜制服"},
|
||||
{"type_id": "118", "type_name": "蕾丝边"},
|
||||
{"type_id": "46", "type_name": "中文有码"},
|
||||
{"type_id": "47", "type_name": "欧美系列"}],
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
def decrypt_data(self, encrypted_base64_data):
|
||||
try:
|
||||
self._validate_decryption_input(encrypted_base64_data)
|
||||
key, iv = self._prepare_decryption_params()
|
||||
decoded_base64_str = self._custom_base64_decode(encrypted_base64_data)
|
||||
ciphertext = self._decode_base64_data(decoded_base64_str)
|
||||
decrypted_bytes = self._perform_aes_decryption(ciphertext, key, iv)
|
||||
plain_text = self._unpad_and_decode(decrypted_bytes)
|
||||
return self._parse_decryption_result(plain_text)
|
||||
except Exception as e:
|
||||
return self._handle_decryption_error(e)
|
||||
|
||||
def _validate_decryption_input(self, encrypted_base64_data):
|
||||
if not encrypted_base64_data:
|
||||
raise ValueError("加密数据不能为空")
|
||||
|
||||
def _prepare_decryption_params(self):
|
||||
aes_key_str = "22946bc50fd63164b79df55070a85a92"
|
||||
aes_iv_str = "kaixin1234567890"
|
||||
key = aes_key_str.encode('utf-8')
|
||||
iv = aes_iv_str.encode('utf-8')
|
||||
return key, iv
|
||||
|
||||
def _custom_base64_decode(self, encoded_str):
|
||||
decoded_str = encoded_str.replace('-', '+').replace('_', '/')
|
||||
while len(decoded_str) % 4 != 0:
|
||||
decoded_str += '='
|
||||
return decoded_str
|
||||
|
||||
def _decode_base64_data(self, decoded_base64_str):
|
||||
return base64.b64decode(decoded_base64_str)
|
||||
|
||||
def _perform_aes_decryption(self, ciphertext, key, iv):
|
||||
cipher = AES.new(key, AES.MODE_CBC, iv)
|
||||
decrypted_bytes = cipher.decrypt(ciphertext)
|
||||
return decrypted_bytes
|
||||
|
||||
def _unpad_and_decode(self, decrypted_bytes):
|
||||
plain_text_bytes = unpad(decrypted_bytes, AES.block_size)
|
||||
plain_text = plain_text_bytes.decode('utf-8')
|
||||
return plain_text
|
||||
|
||||
def _parse_decryption_result(self, plain_text):
|
||||
try:
|
||||
json_result = json.loads(plain_text)
|
||||
return json_result
|
||||
except json.JSONDecodeError as e:
|
||||
return plain_text
|
||||
|
||||
def _handle_decryption_error(self, exception):
|
||||
raise Exception(f"解密失败: {str(exception)}")
|
||||
|
||||
def homeVideoContent(self):
|
||||
try:
|
||||
data = self._fetch_latest_data()
|
||||
decrypted_data = self._decrypt_video_data(data['data'])
|
||||
pic_urls = self._fetch_picture_contents(decrypted_data['latest'])
|
||||
videos = self._build_video_list(decrypted_data['latest'], pic_urls)
|
||||
return {'list': videos}
|
||||
except Exception as e:
|
||||
return self._handle_home_content_error(e)
|
||||
|
||||
def _fetch_latest_data(self):
|
||||
url = f'{xurl}/public2/json/latest.json'
|
||||
detail = requests.get(url=url, headers=headerx, timeout=10)
|
||||
detail.encoding = "utf-8"
|
||||
return detail.json()
|
||||
|
||||
def _decrypt_video_data(self, encrypted_data):
|
||||
return self.decrypt_data(encrypted_data)
|
||||
|
||||
def _fetch_picture_contents(self, latest_videos):
|
||||
pic_urls = {}
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
|
||||
future_to_vod = {
|
||||
executor.submit(requests.get, url=vod['titlepic'], headers=headerx): vod
|
||||
for vod in latest_videos
|
||||
}
|
||||
for future in concurrent.futures.as_completed(future_to_vod):
|
||||
vod = future_to_vod[future]
|
||||
try:
|
||||
response = future.result()
|
||||
response.encoding = "utf-8"
|
||||
pic_urls[vod['id']] = response.text
|
||||
except Exception as e:
|
||||
pic_urls[vod['id']] = ""
|
||||
return pic_urls
|
||||
|
||||
def _build_video_list(self, latest_videos, pic_urls):
|
||||
videos = []
|
||||
for vod in latest_videos:
|
||||
name = vod['title']
|
||||
id = vod['id']
|
||||
pic = pic_urls.get(id, "")
|
||||
date_obj = datetime.datetime.fromtimestamp(int(vod['newstime']))
|
||||
remark = date_obj.strftime('%Y-%m-%d')
|
||||
video = {
|
||||
"vod_id": id,
|
||||
"vod_name": name,
|
||||
"vod_pic": pic,
|
||||
"vod_remarks": remark
|
||||
}
|
||||
videos.append(video)
|
||||
return videos
|
||||
|
||||
def _handle_home_content_error(self, exception):
|
||||
return {'list': []}
|
||||
|
||||
def categoryContent(self, cid, pg, filter, ext):
|
||||
result = {}
|
||||
videos = []
|
||||
page = self._parse_page(pg)
|
||||
data = self._fetch_category_data(cid, page)
|
||||
if not data or 'data' not in data:
|
||||
return {'list': []}
|
||||
pic_urls = self._fetch_picture_urls(data['data'])
|
||||
videos = self._process_videos(data['data'], pic_urls)
|
||||
result['list'] = videos
|
||||
result['page'] = page
|
||||
result['pagecount'] = 9999
|
||||
result['limit'] = 90
|
||||
result['total'] = 999999
|
||||
return result
|
||||
|
||||
def _parse_page(self, pg):
|
||||
try:
|
||||
return int(pg) if pg else 1
|
||||
except (ValueError, TypeError):
|
||||
return 1
|
||||
|
||||
def _fetch_category_data(self, cid, page):
|
||||
try:
|
||||
url = f'{xurl}/public2/json/category/{cid}-{str(page)}.json'
|
||||
detail = requests.get(url=url, headers=headerx, timeout=10)
|
||||
detail.encoding = "utf-8"
|
||||
data = detail.json()
|
||||
return self.decrypt_data(data['data'])
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def _fetch_picture_urls(self, vod_data):
|
||||
pic_urls = {}
|
||||
try:
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
|
||||
future_to_vod = {
|
||||
executor.submit(self._fetch_picture, vod['titlepic']): vod
|
||||
for vod in vod_data
|
||||
}
|
||||
for future in concurrent.futures.as_completed(future_to_vod):
|
||||
vod = future_to_vod[future]
|
||||
try:
|
||||
pic_content = future.result()
|
||||
pic_urls[vod['id']] = pic_content
|
||||
except Exception:
|
||||
pic_urls[vod['id']] = ""
|
||||
except Exception:
|
||||
pass
|
||||
return pic_urls
|
||||
|
||||
def _fetch_picture(self, url):
|
||||
try:
|
||||
response = requests.get(url=url, headers=headerx, timeout=10)
|
||||
response.encoding = "utf-8"
|
||||
return response.text
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
def _process_videos(self, vod_data, pic_urls):
|
||||
videos = []
|
||||
for vod in vod_data:
|
||||
try:
|
||||
name = vod.get('title', '')
|
||||
id = vod.get('id', '')
|
||||
pic = pic_urls.get(id, "")
|
||||
remark = self._format_date(vod.get('newstime', 0))
|
||||
video = {
|
||||
"vod_id": id,
|
||||
"vod_name": name,
|
||||
"vod_pic": pic,
|
||||
"vod_remarks": remark
|
||||
}
|
||||
videos.append(video)
|
||||
except Exception:
|
||||
continue
|
||||
return videos
|
||||
|
||||
def _format_date(self, timestamp):
|
||||
try:
|
||||
date_obj = datetime.datetime.fromtimestamp(int(timestamp))
|
||||
return date_obj.strftime('%Y-%m-%d')
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
def detailContent(self, ids):
|
||||
result = {}
|
||||
videos = []
|
||||
if not ids:
|
||||
result['list'] = videos
|
||||
return result
|
||||
did = ids[0]
|
||||
data = self._fetch_video_detail(did)
|
||||
if not data:
|
||||
result['list'] = videos
|
||||
return result
|
||||
video_info = self._extract_video_info(data, did)
|
||||
videos.append(video_info)
|
||||
result['list'] = videos
|
||||
return result
|
||||
|
||||
def _fetch_video_detail(self, did):
|
||||
try:
|
||||
url = f'{xurl}/public2/json/video/{did}.json'
|
||||
detail = requests.get(url=url, headers=headerx, timeout=10)
|
||||
detail.encoding = "utf-8"
|
||||
data = detail.json()
|
||||
return self.decrypt_data(data['data'])
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def _extract_video_info(self, data, did):
|
||||
content = self._get_video_content(data)
|
||||
remarks = data.get('category_name', '')
|
||||
year = self._format_year(data.get('newstime', 0))
|
||||
area = self._get_area(data)
|
||||
xianlu = "乐哥1$$$乐哥2"
|
||||
bofang = self._format_play_url(data)
|
||||
return {
|
||||
"vod_id": did,
|
||||
"vod_remarks": remarks,
|
||||
"vod_year": year,
|
||||
"vod_area": area,
|
||||
"vod_content": content,
|
||||
"vod_play_from": xianlu,
|
||||
"vod_play_url": bofang
|
||||
}
|
||||
|
||||
def _get_video_content(self, data):
|
||||
try:
|
||||
next_title = data['prev_and_next']['next']['title']
|
||||
return '乐哥为您介绍剧情' + next_title
|
||||
except Exception:
|
||||
return '乐哥为您介绍剧情'
|
||||
|
||||
def _format_year(self, timestamp):
|
||||
try:
|
||||
date_obj = datetime.datetime.fromtimestamp(int(timestamp))
|
||||
return date_obj.strftime('%Y-%m-%d')
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
def _get_area(self, data):
|
||||
try:
|
||||
return data['breadcrumb'][0]['title']
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
def _format_play_url(self, data):
|
||||
try:
|
||||
mp4_url = data.get('mp4', '')
|
||||
m3u8_url = data.get('m3u8', '')
|
||||
return f"乐哥mp4${mp4_url}$$$乐哥m3u8${xurl1}{m3u8_url}"
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
result = {}
|
||||
result["parse"] = 0
|
||||
result["playUrl"] = ''
|
||||
result["url"] = id
|
||||
result["header"] = headerx
|
||||
return result
|
||||
|
||||
def encrypt_aes_cbc_pkcs7_urlsafe(self, plaintext_str):
|
||||
try:
|
||||
key_str = "22946bc50fd63164b79df55070a85a92"
|
||||
iv_str = "kaixin1234567890"
|
||||
plaintext_bytes = self._encode_plaintext(plaintext_str)
|
||||
key = self._encode_key(key_str)
|
||||
iv = self._encode_iv(iv_str)
|
||||
cipher = self._create_cipher(key, iv)
|
||||
padded_bytes = self._pad_plaintext(plaintext_bytes)
|
||||
ciphertext = self._encrypt_data(cipher, padded_bytes)
|
||||
urlsafe_b64 = self._encode_urlsafe_base64(ciphertext)
|
||||
return urlsafe_b64
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
def _encode_plaintext(self, plaintext_str):
|
||||
try:
|
||||
return plaintext_str.encode('utf-8')
|
||||
except Exception:
|
||||
return b""
|
||||
|
||||
def _encode_key(self, key_str):
|
||||
try:
|
||||
return key_str.encode('utf-8')
|
||||
except Exception:
|
||||
return b""
|
||||
|
||||
def _encode_iv(self, iv_str):
|
||||
try:
|
||||
return iv_str.encode('utf-8')
|
||||
except Exception:
|
||||
return b""
|
||||
|
||||
def _create_cipher(self, key, iv):
|
||||
try:
|
||||
return AES.new(key, AES.MODE_CBC, iv)
|
||||
except Exception:
|
||||
raise
|
||||
|
||||
def _pad_plaintext(self, plaintext_bytes):
|
||||
try:
|
||||
return pad(plaintext_bytes, AES.block_size)
|
||||
except Exception:
|
||||
raise
|
||||
|
||||
def _encrypt_data(self, cipher, padded_bytes):
|
||||
try:
|
||||
return cipher.encrypt(padded_bytes)
|
||||
except Exception:
|
||||
raise
|
||||
|
||||
def _encode_urlsafe_base64(self, ciphertext):
|
||||
try:
|
||||
standard_b64 = base64.b64encode(ciphertext).decode('utf-8')
|
||||
urlsafe_b64 = standard_b64.replace('+', '-').replace('/', '_').rstrip('=')
|
||||
return urlsafe_b64
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
def searchContentPage(self, key, quick, pg):
|
||||
result = {}
|
||||
videos = []
|
||||
page = self._parse_page(pg)
|
||||
encrypted_key = self.encrypt_aes_cbc_pkcs7_urlsafe(key)
|
||||
data = self._fetch_search_data(encrypted_key, page)
|
||||
if data and 'data' in data:
|
||||
videos = self._process_search_results(data['data'])
|
||||
result['list'] = videos
|
||||
result['page'] = page
|
||||
result['pagecount'] = 9999
|
||||
result['limit'] = 90
|
||||
result['total'] = 999999
|
||||
return result
|
||||
|
||||
def _parse_page(self, pg):
|
||||
try:
|
||||
return int(pg) if pg else 1
|
||||
except (ValueError, TypeError):
|
||||
return 1
|
||||
|
||||
def _fetch_search_data(self, encrypted_key, page):
|
||||
try:
|
||||
url = f'{xurl2}/api/v2/search?keyword={encrypted_key}&classid=0&page={str(page)}'
|
||||
detail = requests.get(url=url, headers=headerx, timeout=10)
|
||||
detail.encoding = "utf-8"
|
||||
data = detail.json()
|
||||
return self.decrypt_data(data['data'])
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def _process_search_results(self, search_data):
|
||||
videos = []
|
||||
for vod in search_data:
|
||||
try:
|
||||
video = self._create_video_item(vod)
|
||||
videos.append(video)
|
||||
except Exception:
|
||||
continue
|
||||
return videos
|
||||
|
||||
def _create_video_item(self, vod):
|
||||
name = vod.get('title', '')
|
||||
id = vod.get('id', '')
|
||||
pic = "https://i02piccdn.sogoucdn.com/0aa7931c95f0b15a"
|
||||
remark = self._format_date(vod.get('newstime', 0))
|
||||
return {
|
||||
"vod_id": id,
|
||||
"vod_name": name,
|
||||
"vod_pic": pic,
|
||||
"vod_remarks": remark
|
||||
}
|
||||
|
||||
def _format_date(self, timestamp):
|
||||
try:
|
||||
date_obj = datetime.datetime.fromtimestamp(int(timestamp))
|
||||
return date_obj.strftime('%Y-%m-%d')
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
return self.searchContentPage(key, quick, '1')
|
||||
|
||||
def localProxy(self, params):
|
||||
if params['type'] == "m3u8":
|
||||
return self.proxyM3u8(params)
|
||||
elif params['type'] == "media":
|
||||
return self.proxyMedia(params)
|
||||
elif params['type'] == "ts":
|
||||
return self.proxyTs(params)
|
||||
return None
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+334
@@ -0,0 +1,334 @@
|
||||
# coding=utf-8
|
||||
# !/usr/bin/python
|
||||
|
||||
"""
|
||||
|
||||
作者 丢丢喵 内容均从互联网收集而来 仅供交流学习使用 严禁用于商业用途 请于24小时内删除
|
||||
====================Diudiumiao====================
|
||||
|
||||
"""
|
||||
|
||||
from Crypto.Util.Padding import unpad
|
||||
from Crypto.Util.Padding import pad
|
||||
from urllib.parse import urlparse
|
||||
from urllib.parse import unquote
|
||||
from Crypto.Cipher import ARC4
|
||||
from urllib.parse import quote
|
||||
from base.spider import Spider
|
||||
from Crypto.Cipher import AES
|
||||
from datetime import datetime
|
||||
from bs4 import BeautifulSoup
|
||||
from base64 import b64decode
|
||||
import concurrent.futures
|
||||
import urllib.request
|
||||
import urllib.parse
|
||||
import datetime
|
||||
import binascii
|
||||
import requests
|
||||
import base64
|
||||
import json
|
||||
import time
|
||||
import sys
|
||||
import re
|
||||
import os
|
||||
|
||||
sys.path.append('..')
|
||||
|
||||
headerx = {
|
||||
'User-Agent': 'com.android.chrome/131.0.6778.200 (Linux;Android 9) AndroidXMedia3/1.8.0'
|
||||
}
|
||||
|
||||
xurl1 = "http://3344br.com/"
|
||||
|
||||
class Spider(Spider):
|
||||
|
||||
def getName(self):
|
||||
return "丢丢喵"
|
||||
|
||||
def init(self, extend):
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def homeVideoContent(self):
|
||||
pass
|
||||
|
||||
def extract_middle_text(self, text, start_str, end_str, pl, start_index1: str = '', end_index2: str = ''):
|
||||
if pl == 3:
|
||||
plx = []
|
||||
while True:
|
||||
start_index = text.find(start_str)
|
||||
if start_index == -1:
|
||||
break
|
||||
end_index = text.find(end_str, start_index + len(start_str))
|
||||
if end_index == -1:
|
||||
break
|
||||
middle_text = text[start_index + len(start_str):end_index]
|
||||
plx.append(middle_text)
|
||||
text = text.replace(start_str + middle_text + end_str, '')
|
||||
if len(plx) > 0:
|
||||
purl = ''
|
||||
for i in range(len(plx)):
|
||||
matches = re.findall(start_index1, plx[i])
|
||||
output = ""
|
||||
for match in matches:
|
||||
match3 = re.search(r'(?:^|[^0-9])(\d+)(?:[^0-9]|$)', match[1])
|
||||
if match3:
|
||||
number = match3.group(1)
|
||||
else:
|
||||
number = 0
|
||||
if 'http' not in match[0]:
|
||||
output += f"#{match[1]}${number}{xurl}{match[0]}"
|
||||
else:
|
||||
output += f"#{match[1]}${number}{match[0]}"
|
||||
output = output[1:]
|
||||
purl = purl + output + "$$$"
|
||||
purl = purl[:-3]
|
||||
return purl
|
||||
else:
|
||||
return ""
|
||||
else:
|
||||
start_index = text.find(start_str)
|
||||
if start_index == -1:
|
||||
return ""
|
||||
end_index = text.find(end_str, start_index + len(start_str))
|
||||
if end_index == -1:
|
||||
return ""
|
||||
|
||||
if pl == 0:
|
||||
middle_text = text[start_index + len(start_str):end_index]
|
||||
return middle_text.replace("\\", "")
|
||||
|
||||
if pl == 1:
|
||||
middle_text = text[start_index + len(start_str):end_index]
|
||||
matches = re.findall(start_index1, middle_text)
|
||||
if matches:
|
||||
jg = ' '.join(matches)
|
||||
return jg
|
||||
|
||||
if pl == 2:
|
||||
middle_text = text[start_index + len(start_str):end_index]
|
||||
matches = re.findall(start_index1, middle_text)
|
||||
if matches:
|
||||
new_list = [f'{item}' for item in matches]
|
||||
jg = '$$$'.join(new_list)
|
||||
return jg
|
||||
|
||||
def decrypt_m3u8(self, ciphertext):
|
||||
real_key = self.get_real_key()
|
||||
final_iv = self.build_final_iv()
|
||||
encrypted_bytes = base64.b64decode(ciphertext)
|
||||
decrypted_bytes = self.aes_decrypt(real_key, final_iv, encrypted_bytes)
|
||||
result_text = self.finalize_result(decrypted_bytes)
|
||||
return result_text
|
||||
|
||||
def get_real_key(self):
|
||||
B64_KEY = "SWRUSnEwSGtscHVJNm11OGlCJU9PQCF2ZF40SyZ1WFc="
|
||||
return base64.b64decode(B64_KEY)
|
||||
|
||||
def build_final_iv(self):
|
||||
B64_IV_BASE = "JDB2QGtySDdWMg=="
|
||||
SUFFIX = "883346"
|
||||
iv_prefix = base64.b64decode(B64_IV_BASE).decode('utf-8')
|
||||
return (iv_prefix + SUFFIX).encode('utf-8')
|
||||
|
||||
def aes_decrypt(self, real_key, final_iv, encrypted_bytes):
|
||||
cipher = AES.new(real_key, AES.MODE_CBC, final_iv)
|
||||
decrypted_bytes = cipher.decrypt(encrypted_bytes)
|
||||
return unpad(decrypted_bytes, AES.block_size).decode('utf-8')
|
||||
|
||||
def finalize_result(self, decrypted_bytes):
|
||||
return decrypted_bytes.replace('"', '')
|
||||
|
||||
def get_real_base_url(self, start_url):
|
||||
res = self.fetch_start_page(start_url)
|
||||
target_host_b64 = self.extract_target_host_b64(res)
|
||||
if not target_host_b64:
|
||||
return start_url
|
||||
target_host = base64.b64decode(target_host_b64).decode('utf-8')
|
||||
u_val, p_val = self.build_params(start_url)
|
||||
check_url = f"{target_host}/?u={u_val}&p={p_val}"
|
||||
redirect_url_1 = self.get_redirect(check_url)
|
||||
if not redirect_url_1:
|
||||
return check_url
|
||||
redirect_url_2 = self.get_redirect(redirect_url_1)
|
||||
real_url = redirect_url_2 if redirect_url_2 else redirect_url_1
|
||||
return real_url.rstrip('/')
|
||||
|
||||
def fetch_start_page(self, start_url):
|
||||
detail = requests.get(url=start_url, headers=headerx)
|
||||
detail.encoding = "utf-8"
|
||||
return detail.text
|
||||
|
||||
def extract_target_host_b64(self, res):
|
||||
match = re.search(r'window\.atob\("(.*?)"\)', res)
|
||||
return match.group(1) if match else None
|
||||
|
||||
def build_params(self, start_url):
|
||||
parsed_url = urlparse(start_url)
|
||||
origin = f"{parsed_url.scheme}://{parsed_url.netloc}"
|
||||
path_and_search = parsed_url.path
|
||||
if parsed_url.query:
|
||||
path_and_search += f"?{parsed_url.query}"
|
||||
if not path_and_search:
|
||||
path_and_search = "/"
|
||||
u_val = base64.b64encode(origin.encode('utf-8')).decode('utf-8')
|
||||
p_val = base64.b64encode(path_and_search.encode('utf-8')).decode('utf-8')
|
||||
return u_val, p_val
|
||||
|
||||
def get_redirect(self, url):
|
||||
response = requests.get(url=url, headers=headerx, allow_redirects=False)
|
||||
return response.headers.get('Location')
|
||||
|
||||
def homeContent(self, filter):
|
||||
xurl = self.get_real_base_url(xurl1)
|
||||
url = self.build_home_url(xurl)
|
||||
res = self.fetch_home_page(url)
|
||||
doc = self.parse_html(res)
|
||||
soups = self.find_row_items(doc)
|
||||
classes = self.extract_classes(soups)
|
||||
result = self.build_home_result(classes)
|
||||
return result
|
||||
|
||||
def build_home_url(self, xurl):
|
||||
return f'{xurl}/index/home.html'
|
||||
|
||||
def fetch_home_page(self, url):
|
||||
detail = requests.get(url=url, headers=headerx)
|
||||
detail.encoding = "utf-8"
|
||||
return detail.text
|
||||
|
||||
def parse_html(self, html):
|
||||
return BeautifulSoup(html, "lxml")
|
||||
|
||||
def find_row_items(self, doc):
|
||||
return doc.find_all('ul', class_="row-item-content")[:1]
|
||||
|
||||
def extract_classes(self, soups):
|
||||
classes = []
|
||||
for soup in soups:
|
||||
for vod in soup.find_all('a'):
|
||||
classes.append(self.parse_class_item(vod))
|
||||
return classes
|
||||
|
||||
def parse_class_item(self, vod):
|
||||
names = vod.find('span', class_="menu-desktop-content-item")
|
||||
name = names['title']
|
||||
name = self.decrypt_m3u8(name)
|
||||
return {"type_id": name, "type_name": name}
|
||||
|
||||
def build_home_result(self, classes):
|
||||
result = {"class": classes}
|
||||
return result
|
||||
|
||||
def get_page_url(self, category_name, page_num):
|
||||
raw_path = f"/juqing/list-{category_name}-{page_num}.html"
|
||||
b64_bytes = base64.b64encode(raw_path.encode('utf-8'))
|
||||
b64_str = b64_bytes.decode('utf-8')
|
||||
url_encoded = urllib.parse.quote(b64_str)
|
||||
final_url = f"/cYc{url_encoded}.html"
|
||||
return final_url
|
||||
|
||||
def categoryContent(self, cid, pg, filter, ext):
|
||||
xurl = self.get_real_base_url(xurl1)
|
||||
page = self.get_page_number(pg)
|
||||
url1 = self.get_page_url(cid, page)
|
||||
url = f'{xurl}{url1}'
|
||||
res = self.fetch_category_page(url)
|
||||
doc = self.parse_html(res)
|
||||
soups = self.find_video_lists(doc)
|
||||
videos = self.extract_videos(soups, xurl)
|
||||
return self.build_category_result(videos, pg)
|
||||
|
||||
def get_page_number(self, pg):
|
||||
return int(pg) if pg else 1
|
||||
|
||||
def fetch_category_page(self, url):
|
||||
detail = requests.get(url=url, headers=headerx)
|
||||
detail.encoding = "utf-8"
|
||||
return detail.text
|
||||
|
||||
def parse_html(self, html):
|
||||
return BeautifulSoup(html, "lxml")
|
||||
|
||||
def find_video_lists(self, doc):
|
||||
return doc.find_all('div', class_="video-list")
|
||||
|
||||
def extract_videos(self, soups, xurl):
|
||||
videos = []
|
||||
for soup in soups:
|
||||
for vod in soup.find_all('a'):
|
||||
videos.append(self.parse_video_item(vod, xurl))
|
||||
return videos
|
||||
|
||||
def parse_video_item(self, vod, xurl):
|
||||
names = vod.find('div', class_="video-item-title")
|
||||
raw_title = names['title']
|
||||
name = self.decrypt_m3u8(raw_title)
|
||||
vid = vod['href']
|
||||
pics = vod.find('img', class_="video-item-img")
|
||||
pic = f"{xurl}{pics['src']}"
|
||||
remarks = vod.find('div', class_="video-item-date")
|
||||
remark = remarks.text.strip() if remarks else ""
|
||||
return {"vod_id": vid,"vod_name": name,"vod_pic": pic,"vod_remarks": remark}
|
||||
|
||||
def build_category_result(self, videos, pg):
|
||||
result = {'list': videos}
|
||||
result['page'] = pg
|
||||
result['pagecount'] = 9999
|
||||
result['limit'] = 90
|
||||
result['total'] = 999999
|
||||
return result
|
||||
|
||||
def _fetch_page_content(self, url):
|
||||
response = requests.get(url=url, headers=headerx)
|
||||
response.encoding = "utf-8"
|
||||
return response.text
|
||||
|
||||
def _decode_config(self, html, key):
|
||||
start_str = f"{key} = decodeString('"
|
||||
encoded_str = self.extract_middle_text(html, start_str, "'", 0)
|
||||
return base64.b64decode(encoded_str).decode('utf-8')
|
||||
|
||||
def _build_play_url(self, html):
|
||||
video_path = self._decode_config(html, "var video ")
|
||||
host1 = self._decode_config(html, "m3u8_host ")
|
||||
host2 = self._decode_config(html, "m3u8_host1")
|
||||
return f"线路1${host1}{video_path}#线路2${host2}{video_path}"
|
||||
|
||||
def detailContent(self, ids):
|
||||
did = ids[0]
|
||||
base_url = self.get_real_base_url(xurl1)
|
||||
full_url = f'{base_url}{did}'
|
||||
page_content = self._fetch_page_content(full_url)
|
||||
play_url = self._build_play_url(page_content)
|
||||
return {'list': [{"vod_id": did,"vod_name": "温馨提醒📢注意身体","vod_play_from": "四色专线","vod_play_url": play_url}]}
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
result = {}
|
||||
result["parse"] = 0
|
||||
result["playUrl"] = ''
|
||||
result["url"] = id
|
||||
result["header"] = headerx
|
||||
return result
|
||||
|
||||
def searchContentPage(self, key, quick, pg):
|
||||
pass
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
return self.searchContentPage(key, quick, '1')
|
||||
|
||||
def localProxy(self, params):
|
||||
if params['type'] == "m3u8":
|
||||
return self.proxyM3u8(params)
|
||||
elif params['type'] == "media":
|
||||
return self.proxyMedia(params)
|
||||
elif params['type'] == "ts":
|
||||
return self.proxyTs(params)
|
||||
return None
|
||||
|
||||
|
||||
+285
@@ -0,0 +1,285 @@
|
||||
# coding=utf-8
|
||||
# !/usr/bin/python
|
||||
|
||||
"""
|
||||
|
||||
作者 丢丢喵 内容均从互联网收集而来 仅供交流学习使用 严禁用于商业用途 请于24小时内删除
|
||||
====================Diudiumiao====================
|
||||
|
||||
"""
|
||||
|
||||
from Crypto.Util.Padding import unpad
|
||||
from Crypto.Util.Padding import pad
|
||||
from urllib.parse import urlparse
|
||||
from urllib.parse import unquote
|
||||
from Crypto.Cipher import ARC4
|
||||
from urllib.parse import quote
|
||||
from base.spider import Spider
|
||||
from Crypto.Cipher import AES
|
||||
from datetime import datetime
|
||||
from bs4 import BeautifulSoup
|
||||
from base64 import b64decode
|
||||
import concurrent.futures
|
||||
import urllib.request
|
||||
import urllib.parse
|
||||
import datetime
|
||||
import binascii
|
||||
import requests
|
||||
import base64
|
||||
import json
|
||||
import time
|
||||
import sys
|
||||
import re
|
||||
import os
|
||||
|
||||
sys.path.append('..')
|
||||
|
||||
xurl = "https://pornlax.com"
|
||||
|
||||
headerx = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.87 Safari/537.36'
|
||||
}
|
||||
|
||||
class Spider(Spider):
|
||||
|
||||
def getName(self):
|
||||
return "丢丢喵"
|
||||
|
||||
def init(self, extend):
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def homeVideoContent(self):
|
||||
pass
|
||||
|
||||
def searchContentPage(self, key, quick, pg):
|
||||
pass
|
||||
|
||||
def extract_middle_text(self, text, start_str, end_str, pl, start_index1: str = '', end_index2: str = ''):
|
||||
if pl == 3:
|
||||
plx = []
|
||||
while True:
|
||||
start_index = text.find(start_str)
|
||||
if start_index == -1:
|
||||
break
|
||||
end_index = text.find(end_str, start_index + len(start_str))
|
||||
if end_index == -1:
|
||||
break
|
||||
middle_text = text[start_index + len(start_str):end_index]
|
||||
plx.append(middle_text)
|
||||
text = text.replace(start_str + middle_text + end_str, '')
|
||||
if len(plx) > 0:
|
||||
purl = ''
|
||||
for i in range(len(plx)):
|
||||
matches = re.findall(start_index1, plx[i])
|
||||
output = ""
|
||||
for match in matches:
|
||||
match3 = re.search(r'(?:^|[^0-9])(\d+)(?:[^0-9]|$)', match[1])
|
||||
if match3:
|
||||
number = match3.group(1)
|
||||
else:
|
||||
number = 0
|
||||
if 'http' not in match[0]:
|
||||
output += f"#{match[1]}${number}{xurl}{match[0]}"
|
||||
else:
|
||||
output += f"#{match[1]}${number}{match[0]}"
|
||||
output = output[1:]
|
||||
purl = purl + output + "$$$"
|
||||
purl = purl[:-3]
|
||||
return purl
|
||||
else:
|
||||
return ""
|
||||
else:
|
||||
start_index = text.find(start_str)
|
||||
if start_index == -1:
|
||||
return ""
|
||||
end_index = text.find(end_str, start_index + len(start_str))
|
||||
if end_index == -1:
|
||||
return ""
|
||||
|
||||
if pl == 0:
|
||||
middle_text = text[start_index + len(start_str):end_index]
|
||||
return middle_text.replace("\\", "")
|
||||
|
||||
if pl == 1:
|
||||
middle_text = text[start_index + len(start_str):end_index]
|
||||
matches = re.findall(start_index1, middle_text)
|
||||
if matches:
|
||||
jg = ' '.join(matches)
|
||||
return jg
|
||||
|
||||
if pl == 2:
|
||||
middle_text = text[start_index + len(start_str):end_index]
|
||||
matches = re.findall(start_index1, middle_text)
|
||||
if matches:
|
||||
new_list = [f'{item}' for item in matches]
|
||||
jg = '$$$'.join(new_list)
|
||||
return jg
|
||||
|
||||
def homeContent(self, filter):
|
||||
result = {"class": []}
|
||||
res = self.get_response()
|
||||
doc = self.parse_response(res)
|
||||
soups = self.find_sugg_divs(doc)
|
||||
self.process_soups(soups, result)
|
||||
return result
|
||||
|
||||
def get_response(self):
|
||||
detail = requests.get(url=xurl, headers=headerx)
|
||||
detail.encoding = "utf-8"
|
||||
return detail.text
|
||||
|
||||
def parse_response(self, res):
|
||||
return BeautifulSoup(res, "lxml")
|
||||
|
||||
def find_sugg_divs(self, doc):
|
||||
return doc.find_all('div', class_="sugg")
|
||||
|
||||
def process_soups(self, soups, result):
|
||||
for soup in soups:
|
||||
self.process_single_soup(soup, result)
|
||||
|
||||
def process_single_soup(self, soup, result):
|
||||
vods = self.find_links(soup)
|
||||
self.process_vods(vods, result)
|
||||
|
||||
def find_links(self, soup):
|
||||
return soup.find_all('a')
|
||||
|
||||
def process_vods(self, vods, result):
|
||||
for vod in vods:
|
||||
self.extract_and_append(vod, result)
|
||||
|
||||
def extract_and_append(self, vod, result):
|
||||
name = vod.text.strip()
|
||||
id = vod['href']
|
||||
result["class"].append({"type_id": id, "type_name": name})
|
||||
|
||||
def categoryContent(self, cid, pg, filter, ext):
|
||||
page = self.get_page_number(pg)
|
||||
data = self.build_request_data(cid, page)
|
||||
res = self.send_post_request(data)
|
||||
doc = self.parse_html(res)
|
||||
soups = self.find_video_divs(doc)
|
||||
videos = self.process_video_divs(soups)
|
||||
return self.build_result(videos, pg)
|
||||
|
||||
def get_page_number(self, pg):
|
||||
return int(pg) if pg else 1
|
||||
|
||||
def build_request_data(self, cid, page):
|
||||
fenge = cid.split("videos/")
|
||||
return {'mix': 'video-next3','value': fenge[1],'page': str(page),}
|
||||
|
||||
def send_post_request(self, data):
|
||||
detail = requests.post('https://pornlax.com/hash-pornlax', headers=headerx, data=data)
|
||||
detail.encoding = "utf-8"
|
||||
return detail.text
|
||||
|
||||
def parse_html(self, res):
|
||||
return BeautifulSoup(res, "lxml")
|
||||
|
||||
def find_video_divs(self, doc):
|
||||
return doc.find_all('div', class_="prev")
|
||||
|
||||
def process_video_divs(self, soups):
|
||||
videos = []
|
||||
for vod in soups:
|
||||
video = self.extract_video_info(vod)
|
||||
videos.append(video)
|
||||
return videos
|
||||
|
||||
def extract_video_info(self, vod):
|
||||
names = vod.find('div', class_="name")
|
||||
name = names.text.strip()
|
||||
id = vod.find('a')['href']
|
||||
pic = vod.find('img')['src']
|
||||
remarks = vod.find('div', class_="info")
|
||||
remark = remarks.text.strip() if remarks else ""
|
||||
return {"vod_id": id,"vod_name": name,"vod_pic": pic,"vod_remarks": remark}
|
||||
|
||||
def build_result(self, videos, pg):
|
||||
result = {'list': videos}
|
||||
result['page'] = pg
|
||||
result['pagecount'] = 9999
|
||||
result['limit'] = 90
|
||||
result['total'] = 999999
|
||||
return result
|
||||
|
||||
def detailContent(self, ids):
|
||||
did = ids[0]
|
||||
data = self.build_detail_data(did)
|
||||
res = self.get_iframe_response(data)
|
||||
cond = self.extract_middle_text(res, 'src="', '"', 0)
|
||||
res = self.get_player_response(cond)
|
||||
matches = self.extract_play_info(res)
|
||||
bofang = self.build_play_string(matches)
|
||||
videos = self.build_video_list(did, bofang)
|
||||
result = self.build_detail_result(videos)
|
||||
return result
|
||||
|
||||
def build_detail_data(self, did):
|
||||
fenge = did.split("/")
|
||||
return {'mix': 'moviesiframe2','num': fenge[2],}
|
||||
|
||||
def get_iframe_response(self, data):
|
||||
detail = requests.post('https://pornlax.com/hash-pornlax', headers=headerx, data=data)
|
||||
detail.encoding = "utf-8"
|
||||
return detail.text
|
||||
|
||||
def get_player_response(self, cond):
|
||||
detail = requests.get(url=cond, headers=headerx)
|
||||
detail.encoding = "utf-8"
|
||||
return detail.text
|
||||
|
||||
def extract_play_info(self, res):
|
||||
pattern = re.compile(r"html:\s*'(.*?)'.*?url:\s*'(.*?)'", re.DOTALL)
|
||||
return pattern.findall(res)
|
||||
|
||||
def build_play_string(self, matches):
|
||||
bofang = ''
|
||||
for name, id in matches:
|
||||
bofang = bofang + name + '$' + id + '#'
|
||||
return bofang[:-1]
|
||||
|
||||
def build_video_list(self, did, bofang):
|
||||
return [{"vod_id": did,"vod_play_from": "小心腰专线","vod_play_url": bofang}]
|
||||
|
||||
def build_detail_result(self, videos):
|
||||
result = {}
|
||||
result['list'] = videos
|
||||
return result
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
result = {}
|
||||
result["parse"] = 0
|
||||
result["playUrl"] = ''
|
||||
result["url"] = id
|
||||
result["header"] = headerx
|
||||
return result
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
return self.searchContentPage(key, quick, '1')
|
||||
|
||||
def localProxy(self, params):
|
||||
if params['type'] == "m3u8":
|
||||
return self.proxyM3u8(params)
|
||||
elif params['type'] == "media":
|
||||
return self.proxyMedia(params)
|
||||
elif params['type'] == "ts":
|
||||
return self.proxyTs(params)
|
||||
return None
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+220
@@ -0,0 +1,220 @@
|
||||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
import re
|
||||
from base.spider import Spider
|
||||
import sys
|
||||
import json
|
||||
import base64
|
||||
import urllib.parse
|
||||
|
||||
sys.path.append('..')
|
||||
|
||||
murl = "https://www.6vyd.com/web/index.html"
|
||||
headerx = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36 Edg/134.0.0.0'
|
||||
}
|
||||
response = requests.get(murl, allow_redirects=True)
|
||||
xurl = response.url
|
||||
nurl = xurl + '/web/abcdefg.ashx?action=getindexdata'
|
||||
durl = xurl + '/web/abcdefg.ashx?action=getvideo&vid='
|
||||
pm = ''
|
||||
|
||||
class Spider(Spider):
|
||||
global xurl
|
||||
global headerx
|
||||
|
||||
def getName(self):
|
||||
return "首页"
|
||||
|
||||
def init(self, extend):
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def homeContent(self, filter):
|
||||
result = {}
|
||||
result = {"class": [{"type_id": "1003", "type_name": "亚洲无码"},
|
||||
{"type_id": "3022", "type_name": "欧美无码"},
|
||||
{"type_id": "3026", "type_name": "中文字幕"},
|
||||
{"type_id": "3025", "type_name": "经典三级"},
|
||||
{"type_id": "5", "type_name": "国产主播"},
|
||||
{"type_id": "134", "type_name": "韩国主播"},
|
||||
{"type_id": "3137", "type_name": "ASMR"},
|
||||
{"type_id": "3138", "type_name": "恐怖色情"},
|
||||
{"type_id": "131", "type_name": "网红视频"},
|
||||
{"type_id": "132", "type_name": "国产视频"},
|
||||
{"type_id": "3023", "type_name": "人妖伪娘"},
|
||||
{"type_id": "130", "type_name": "动漫卡通"},
|
||||
{"type_id": "3088", "type_name": "华人原创"},
|
||||
{"type_id": "3135", "type_name": "JVID"},
|
||||
{"type_id": "3136", "type_name": "SWAG"},
|
||||
{"type_id": "3134", "type_name": "明星换脸"}]}
|
||||
return result
|
||||
|
||||
def homeVideoContent(self):
|
||||
videos = []
|
||||
try:
|
||||
detail = requests.get(url=nurl, headers=headerx)
|
||||
detail.encoding = "utf-8"
|
||||
res = detail.json()
|
||||
video_list = res.get("videos", [])
|
||||
|
||||
for video in video_list:
|
||||
name = video.get("title", "")
|
||||
id = video.get("id", "")
|
||||
pic = video.get("coverimg", "")
|
||||
remarks = video.get("updatedate", "")
|
||||
video = {
|
||||
"vod_id": id,
|
||||
"vod_name": name,
|
||||
"vod_pic": pic,
|
||||
"vod_remarks": remarks
|
||||
}
|
||||
videos.append(video)
|
||||
|
||||
result = {'list': videos}
|
||||
return result
|
||||
except:
|
||||
pass
|
||||
|
||||
def categoryContent(self, cid, pg, filter, ext):
|
||||
result = {}
|
||||
videos = []
|
||||
if pg:
|
||||
page = int(pg)
|
||||
else:
|
||||
page = 1
|
||||
|
||||
if page == '1':
|
||||
url = f'{xurl}/web/abcdefg.ashx?action=getvideos&vtype={cid}&pageindex=1&pagesize=12'
|
||||
else:
|
||||
url = f'{xurl}/web/abcdefg.ashx?action=getvideos&vtype={cid}&pageindex={str(page)}&pagesize=12'
|
||||
try:
|
||||
detail = requests.get(url=url, headers=headerx)
|
||||
detail.encoding = "utf-8"
|
||||
res = detail.json()
|
||||
video_list = res.get("videos", [])
|
||||
|
||||
for video in video_list:
|
||||
name = video.get("title", "")
|
||||
id = video.get("id", "")
|
||||
pic = video.get("coverimg", "")
|
||||
remarks = video.get("updatedate", "")
|
||||
video = {
|
||||
"vod_id": id,
|
||||
"vod_name": name,
|
||||
"vod_pic": pic,
|
||||
"vod_remarks": remarks
|
||||
}
|
||||
videos.append(video)
|
||||
|
||||
except:
|
||||
pass
|
||||
result = {'list': videos}
|
||||
result['page'] = pg
|
||||
result['pagecount'] = 99
|
||||
result['limit'] = 90
|
||||
result['total'] = 99
|
||||
return result
|
||||
|
||||
def detailContent(self, ids):
|
||||
did = ids[0]
|
||||
result = {}
|
||||
videos = []
|
||||
playurl = ''
|
||||
if 'http' not in did:
|
||||
did = durl + did
|
||||
res1 = requests.get(url=did, headers=headerx)
|
||||
res1.encoding = "utf-8"
|
||||
res = res1.json()
|
||||
node = res.get("data", {}).get("Table", [{}])[0]
|
||||
|
||||
vod_id = node.get("id", "")
|
||||
vod_name = node.get("title", "")
|
||||
vod_pic = node.get("coverimg", "")
|
||||
vod_remarks = node.get("updatedate", "")
|
||||
vod_content = node.get("title", "")
|
||||
|
||||
playFrom = []
|
||||
playList = []
|
||||
if node.get("vurl"):
|
||||
base_url1 = res.get("xldata", {}).get("value", "")
|
||||
base_url2 = res.get("xldata", {}).get("value1", "")
|
||||
if base_url1:
|
||||
full_vurl1 = base_url1 + node.get("vurl", "")
|
||||
playFrom.append("播放源1")
|
||||
playList.append(full_vurl1)
|
||||
if base_url2:
|
||||
full_vurl2 = base_url2 + node.get("vurl", "")
|
||||
playFrom.append("播放源2")
|
||||
playList.append(full_vurl2)
|
||||
|
||||
videos.append({
|
||||
"vod_id": vod_id,
|
||||
"vod_name": vod_name,
|
||||
"vod_pic": vod_pic,
|
||||
"vod_remarks": vod_remarks,
|
||||
"vod_content": vod_content,
|
||||
"vod_play_from": "$$$".join(playFrom),
|
||||
"vod_play_url": "$$$".join(playList)
|
||||
})
|
||||
|
||||
result['list'] = videos
|
||||
return result
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
parts = id.split("http")
|
||||
xiutan = 1
|
||||
if xiutan == 1:
|
||||
if len(parts) > 1:
|
||||
before_https, after_https = parts[0], 'http' + parts[1]
|
||||
result = {}
|
||||
result["parse"] = xiutan
|
||||
result["playUrl"] = ''
|
||||
result["url"] = after_https
|
||||
result["header"] = headerx
|
||||
return result
|
||||
|
||||
def searchContentPage(self, key, quick, page):
|
||||
result = {}
|
||||
videos = []
|
||||
url = f'{xurl}/web/abcdefg.ashx?action=search&p={key}&pageindex={str(page)}&pagesize=12'
|
||||
detail = requests.post(url=url, headers=headerx)
|
||||
detail.encoding = "utf-8"
|
||||
res = detail.json()
|
||||
video_list = res.get("data", [])
|
||||
for video in video_list:
|
||||
name = video.get("title", "")
|
||||
id = video.get("id", "")
|
||||
pic = video.get("imgurl", "")
|
||||
remarks = video.get("updatedate", "")
|
||||
video = {
|
||||
"vod_id": id,
|
||||
"vod_name": name,
|
||||
"vod_pic": pic,
|
||||
"vod_remarks": remarks
|
||||
}
|
||||
videos.append(video)
|
||||
|
||||
result['list'] = videos
|
||||
result['page'] = page
|
||||
result['pagecount'] = 60
|
||||
result['limit'] = 30
|
||||
result['total'] = 999999
|
||||
return result
|
||||
|
||||
def searchContent(self, key, quick):
|
||||
return self.searchContentPage(key, quick, '1')
|
||||
|
||||
def localProxy(self, params):
|
||||
if params['type'] == "m3u8":
|
||||
return self.proxyM3u8(params)
|
||||
elif params['type'] == "media":
|
||||
return self.proxyMedia(params)
|
||||
elif params['type'] == "ts":
|
||||
return self.proxyTs(params)
|
||||
return None
|
||||
@@ -0,0 +1,199 @@
|
||||
#Kyele
|
||||
import sys
|
||||
import json
|
||||
import time
|
||||
import re
|
||||
import requests
|
||||
sys.path.append('..')
|
||||
from base.spider import Spider
|
||||
class Spider(Spider):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.base = 'https://www.pandalive.co.kr'; self.api = 'https://api.pandalive.co.kr'; self.session = requests.Session()
|
||||
self.ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36'
|
||||
self.common_headers = {'User-Agent': self.ua, 'Accept': 'application/json, text/plain, */*', 'Origin': self.base, 'Referer': self.base + '/'}
|
||||
self.x_device_info = {"t": "webPc", "v": "1.0", "ui": "0", "ck": {"sessKeyAsp": ""}}; self.extra_cookie = ''
|
||||
def init(self, extend=""):
|
||||
try:
|
||||
if extend:
|
||||
cfg = extend if isinstance(extend, dict) else json.loads(extend)
|
||||
self.x_device_info = cfg.get('x_device_info', self.x_device_info); self.extra_cookie = cfg.get('cookie', '')
|
||||
except Exception: pass
|
||||
try:
|
||||
self.session.headers.update(self.common_headers)
|
||||
if self.extra_cookie: self.session.headers['Cookie'] = self.extra_cookie
|
||||
self.session.get(self.base, timeout=8); self._app_token()
|
||||
except Exception: pass
|
||||
return self
|
||||
def getName(self): return 'PandaLive'
|
||||
def isVideoFormat(self, url): return url.endswith('.m3u8') or url.endswith('.mp4')
|
||||
def manualVideoCheck(self): return False
|
||||
def destroy(self):
|
||||
try: self.session.close()
|
||||
except Exception: pass
|
||||
def _app_token(self):
|
||||
headers = self._with_x_device_info(dict(self.session.headers))
|
||||
return self.session.get(f'{self.api}/v1/member/app_token', headers=headers, timeout=8)
|
||||
def _list_live(self, page=None, page_size=None, order_by='user', only_new='N'):
|
||||
if page_size is None: page_size = 60
|
||||
limit = page_size; offset = 0 if page is None else max(0, (page - 1) * limit)
|
||||
headers = self._with_x_device_info(dict(self.session.headers))
|
||||
headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8'
|
||||
data = {'orderBy': order_by, 'onlyNewBj': only_new, 'limit': str(limit), 'offset': str(offset)}
|
||||
try:
|
||||
r = self.session.post(f'{self.api}/v1/live', data=data, headers=headers, timeout=8)
|
||||
j = {}
|
||||
try: j = r.json()
|
||||
except Exception: pass
|
||||
if isinstance(j, dict) and isinstance(j.get('list'), list) and len(j['list']) > 0: return j
|
||||
except Exception: pass
|
||||
try: return self.session.get(f'{self.api}/v1/live', timeout=8).json()
|
||||
except Exception: return {}
|
||||
def _list_live_page(self, page, page_size, order_by='user', only_new='N'):
|
||||
j = self._list_live(page=page, page_size=page_size, order_by=order_by, only_new=only_new)
|
||||
return j.get('list', []) if isinstance(j, dict) else []
|
||||
def _list_live_aggregate(self, max_pages=5, page_size=60, min_expect=60, order_by='user', only_new='N'):
|
||||
try:
|
||||
cache_key = f'pandalive_agg_v2_{order_by}_{only_new}'; cached = self.getCache(cache_key)
|
||||
if isinstance(cached, dict) and isinstance(cached.get('list'), list): return cached['list']
|
||||
except Exception: pass
|
||||
seen = set(); result = []
|
||||
first = self._list_live(page=None, page_size=page_size, order_by=order_by, only_new=only_new)
|
||||
items = first.get('list', []) if isinstance(first, dict) else []
|
||||
for it in items:
|
||||
code = it.get('code') or it.get('userId')
|
||||
if code and code not in seen: seen.add(code); result.append(it)
|
||||
p = 1
|
||||
while len(result) < min_expect and p <= max_pages:
|
||||
page_items = self._list_live_page(page=p, page_size=page_size, order_by=order_by, only_new=only_new)
|
||||
added = 0
|
||||
for it in page_items:
|
||||
code = it.get('code') or it.get('userId')
|
||||
if code and code not in seen: seen.add(code); result.append(it); added += 1
|
||||
if added == 0 and p > 1: break
|
||||
p += 1
|
||||
try:
|
||||
payload = {"expiresAt": int(time.time()) + 20, "list": result}
|
||||
self.setCache(f'pandalive_agg_v2_{order_by}_{only_new}', payload)
|
||||
except Exception: pass
|
||||
return result
|
||||
def _live_play(self, play_id):
|
||||
body = {'play_id': play_id, 'device': 'webPc', 'player': 'ivs'}
|
||||
headers = self._with_x_device_info(dict(self.session.headers))
|
||||
headers['Content-Type'] = 'application/json'; headers['Referer'] = f'{self.base}/play/{play_id.split("_")[0]}'
|
||||
r = self.session.post(f'{self.api}/v1/live/play', headers=headers, data=json.dumps(body), timeout=8)
|
||||
if r.status_code != 200:
|
||||
try: self._app_token()
|
||||
except Exception: pass
|
||||
r = self.session.post(f'{self.api}/v1/live/play', headers=headers, data=json.dumps(body), timeout=8)
|
||||
try: return r.json()
|
||||
except Exception: return {'result': False, 'status': r.status_code, 'text': r.text}
|
||||
def _with_x_device_info(self, headers):
|
||||
try: headers['x-device-info'] = json.dumps(self.x_device_info, separators=(',', ':'))
|
||||
except Exception: headers['x-device-info'] = '{"t":"webPc","v":"1.0","ui":"0","ck":{"sessKeyAsp":""}}'
|
||||
return headers
|
||||
def homeContent(self, filter):
|
||||
classes = [{"type_name": "LIVE", "type_id": "live"}]
|
||||
if filter:
|
||||
filters = {"live": [{"key": "sort", "value": [
|
||||
{"n": "观看次数", "v": "user-N"}, {"n": "热门", "v": "hot-N"},
|
||||
{"n": "最新", "v": "new-N"}, {"n": "新人", "v": "user-Y"}
|
||||
]}]}
|
||||
else: filters = {}
|
||||
return {"class": classes, "filters": filters}
|
||||
def homeVideoContent(self):
|
||||
items = self._list_live_aggregate(max_pages=3, page_size=60, min_expect=48, order_by='user', only_new='N')
|
||||
if not items:
|
||||
items = (self._list_live().get('list', []))
|
||||
return {"list": [self._to_vod(it) for it in items[:48]]}
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
page_size = 24; p = 1
|
||||
try: p = int(pg)
|
||||
except Exception: pass
|
||||
order_by, only_new = 'user', 'N'
|
||||
try:
|
||||
if isinstance(extend, str):
|
||||
try: extend = json.loads(extend) if extend.strip().startswith('{') else {}
|
||||
except Exception: extend = {}
|
||||
if isinstance(extend, dict):
|
||||
s = extend.get('sort')
|
||||
if isinstance(s, str) and '-' in s:
|
||||
ab = s.split('-', 1)
|
||||
if len(ab) == 2: order_by, only_new = (ab[0] or 'user'), (ab[1] or 'N')
|
||||
if (order_by, only_new) == ('user', 'N') and (tid or '').lower() != 'live':
|
||||
order_by, only_new = self._tid_to_sort(tid)
|
||||
except Exception: pass
|
||||
server_items = self._list_live_page(page=p, page_size=page_size, order_by=order_by, only_new=only_new)
|
||||
if len(server_items) >= page_size:
|
||||
return {"page": p, "pagecount": 99999, "limit": page_size, "total": 999999, "list": [self._to_vod(it) for it in server_items]}
|
||||
all_items = self._list_live_aggregate(max_pages=12, page_size=100, min_expect=120, order_by=order_by, only_new=only_new)
|
||||
total = len(all_items)
|
||||
if total <= 0:
|
||||
base_list = self._list_live().get('list', [])
|
||||
total = len(base_list)
|
||||
if total <= 0: return {"page": p, "pagecount": 1, "limit": page_size, "total": 0, "list": []}
|
||||
start = (p - 1) * page_size; end = start + page_size
|
||||
part = base_list[start:end]
|
||||
videos = [self._to_vod(it) for it in part]
|
||||
return {"page": p, "pagecount": (total + page_size - 1)//page_size, "limit": page_size, "total": total, "list": videos}
|
||||
start = ((p - 1) * page_size) % total; part = []
|
||||
for i in range(page_size): part.append(all_items[(start + i) % total])
|
||||
return {"page": p, "pagecount": 99999, "limit": page_size, "total": 999999, "list": [self._to_vod(it) for it in part]}
|
||||
def _tid_to_sort(self, tid):
|
||||
t = (tid or '').lower()
|
||||
if t == 'live_newbj': return 'user', 'Y'
|
||||
if t == 'live_hot': return 'hot', 'N'
|
||||
if t == 'live_new': return 'new', 'N'
|
||||
return 'user', 'N'
|
||||
def detailContent(self, ids):
|
||||
vid = ids[0]; parts = vid.split('|'); play_id = parts[0]
|
||||
user_id = parts[1] if len(parts) > 1 else play_id.split('_')[0]
|
||||
title = parts[2] if len(parts) > 2 else user_id
|
||||
vod = {"vod_id": vid, "vod_name": title, "vod_pic": "", "type_name": "LIVE", "vod_year": "", "vod_area": "", "vod_remarks": "PandaLive", "vod_actor": "", "vod_director": "", "vod_content": title, "vod_play_from": "爱看影视", "vod_play_url": f"爱看影视${vid}"}
|
||||
return {"list": [vod]}
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
items = self._list_live().get('list', [])
|
||||
key_l = key.lower(); result = []
|
||||
for it in items:
|
||||
title = str(it.get('title', '')); user_id = str(it.get('userId', '')); user_nick = str(it.get('userNick', ''))
|
||||
if key_l in title.lower() or key_l in user_id.lower() or key_l in user_nick.lower():
|
||||
result.append(self._to_vod(it))
|
||||
return {"list": result, "page": 1}
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
try:
|
||||
vid = id; parts = vid.split('|'); play_id = parts[0]
|
||||
j = self._live_play(play_id); m3u8 = self._find_first_m3u8(j) if isinstance(j, dict) else ''
|
||||
if not m3u8: return {"parse": 1, "playUrl": "", "url": f"{self.base}/play/{play_id.split('_')[0]}", "header": self._play_headers()}
|
||||
return {"parse": 0, "playUrl": "", "url": m3u8, "header": self._play_headers()}
|
||||
except Exception: return {"parse": 1, "playUrl": "", "url": f"{self.base}", "header": self._play_headers()}
|
||||
def liveContent(self, url):
|
||||
try:
|
||||
play_id = url; j = self._live_play(play_id)
|
||||
m3u8 = self._find_first_m3u8(j)
|
||||
if m3u8: return {"parse": 0, "url": m3u8, "header": self._play_headers()}
|
||||
except Exception: pass
|
||||
return {"parse": 1, "url": f"{self.base}/play/{url}", "header": self._play_headers()}
|
||||
def localProxy(self, param):
|
||||
action = param.get('action') if isinstance(param, dict) else None
|
||||
if action == 'play':
|
||||
play_id = param.get('play_id', ''); j = self._live_play(play_id)
|
||||
m3u8 = self._find_first_m3u8(j)
|
||||
if m3u8: return self._redirect(m3u8)
|
||||
return None
|
||||
def _redirect(self, url):
|
||||
return {"code": 302, "headers": {"Location": url}}
|
||||
def _find_first_m3u8(self, obj):
|
||||
try:
|
||||
text = json.dumps(obj, ensure_ascii=False)
|
||||
m = re.search(r'https?://[^\s"\\]+\.m3u8[^\s"\\]*', text)
|
||||
if m: return m.group(0)
|
||||
except Exception: pass
|
||||
return ''
|
||||
def _play_headers(self):
|
||||
return {'User-Agent': self.ua, 'Referer': self.base + '/', 'Origin': self.base}
|
||||
def _to_vod(self, it):
|
||||
title = it.get('title') or it.get('userNick') or it.get('userId') or 'LIVE'
|
||||
pic = it.get('thumbUrl') or it.get('ivsThumbnail') or ''
|
||||
user_id = it.get('userId', ''); play_id = it.get('code', user_id); vod_id = f"{play_id}|{user_id}|{title}"
|
||||
remarks = f"观众 {it.get('user', 0)} | 点赞 {it.get('likeCnt', 0)}"
|
||||
return {'vod_id': vod_id, 'vod_name': title, 'vod_pic': pic, 'vod_remarks': remarks}
|
||||
+250
@@ -0,0 +1,250 @@
|
||||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
import re
|
||||
from base.spider import Spider
|
||||
import sys
|
||||
import json
|
||||
import base64
|
||||
import urllib.parse
|
||||
|
||||
sys.path.append('..')
|
||||
|
||||
murl = "https://3642.7rnr.com/web/index.html"
|
||||
headerx = {
|
||||
'User-Agent': "Mozilla/5.0 (Linux; Android 13; M2102J2SC Build/TKQ1.221114.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/143.0.7499.3 Mobile Safari/537.36",
|
||||
'Accept-Encoding': "gzip, deflate, br, zstd"
|
||||
}
|
||||
response = requests.get(murl, allow_redirects=True)
|
||||
xurl = response.url
|
||||
nurl = xurl + '/web/abcdefg.ashx'
|
||||
pm = ''
|
||||
|
||||
class Spider(Spider):
|
||||
global xurl
|
||||
global headerx
|
||||
|
||||
def getName(self):
|
||||
return "首页"
|
||||
|
||||
def init(self, extend):
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def homeContent(self, filter):
|
||||
result = {}
|
||||
result = {"class": [{"type_id": "1003", "type_name": "亚洲无码"},
|
||||
{"type_id": "3022", "type_name": "欧美无码"},
|
||||
{"type_id": "3026", "type_name": "中文字幕"},
|
||||
{"type_id": "3025", "type_name": "经典三级"},
|
||||
{"type_id": "5", "type_name": "国产主播"},
|
||||
{"type_id": "134", "type_name": "韩国主播"},
|
||||
{"type_id": "3137", "type_name": "ASMR"},
|
||||
{"type_id": "3138", "type_name": "恐怖色情"},
|
||||
{"type_id": "131", "type_name": "网红视频"},
|
||||
{"type_id": "132", "type_name": "国产视频"},
|
||||
{"type_id": "3023", "type_name": "人妖伪娘"},
|
||||
{"type_id": "130", "type_name": "动漫卡通"},
|
||||
{"type_id": "3088", "type_name": "华人原创"},
|
||||
{"type_id": "3135", "type_name": "JVID"},
|
||||
{"type_id": "3136", "type_name": "SWAG"},
|
||||
{"type_id": "3134", "type_name": "明星换脸"}]}
|
||||
return result
|
||||
|
||||
def homeVideoContent(self):
|
||||
videos = []
|
||||
try:
|
||||
payload = {
|
||||
'action': "getindexdata",
|
||||
't': "1762753963537691",
|
||||
's': "5ad87a586f5aae9c2ca4f913d45f8958"}
|
||||
detail = requests.post(url=nurl, data=payload, headers=headerx)
|
||||
detail.encoding = "utf-8"
|
||||
res = detail.json()
|
||||
video_list = res.get("videos", [])
|
||||
|
||||
for video in video_list:
|
||||
name = video.get("title", "")
|
||||
id = video.get("id", "")
|
||||
pic = video.get("coverimg", "")
|
||||
remarks = video.get("updatedate", "")
|
||||
video = {
|
||||
"vod_id": id,
|
||||
"vod_name": name,
|
||||
"vod_pic": pic,
|
||||
"vod_remarks": remarks
|
||||
}
|
||||
videos.append(video)
|
||||
|
||||
result = {'list': videos}
|
||||
return result
|
||||
except:
|
||||
pass
|
||||
|
||||
def categoryContent(self, cid, pg, filter, ext):
|
||||
result = {}
|
||||
videos = []
|
||||
if pg:
|
||||
page = int(pg)
|
||||
else:
|
||||
page = 1
|
||||
|
||||
if page == '1':
|
||||
payload1 = {
|
||||
'action': "getvideos",
|
||||
'vtype': {cid},
|
||||
'pageindex': "1",
|
||||
'pagesize': "12",
|
||||
'tags': "全部",
|
||||
'sortindex': "1",
|
||||
't': "176275570014518",
|
||||
's': "ff4218e4cafd552c4d0c93eb935c14f1"}
|
||||
else:
|
||||
payload1 = {
|
||||
'action': "getvideos",
|
||||
'vtype': {cid},
|
||||
'pageindex': {str(page)},
|
||||
'pagesize': "12",
|
||||
'tags': "全部",
|
||||
'sortindex': "1",
|
||||
't': "176275570014518",
|
||||
's': "ff4218e4cafd552c4d0c93eb935c14f1"}
|
||||
try:
|
||||
detail = requests.post(url=nurl, data=payload1, headers=headerx)
|
||||
detail.encoding = "utf-8"
|
||||
res = detail.json()
|
||||
video_list = res.get("videos", [])
|
||||
|
||||
for video in video_list:
|
||||
name = video.get("title", "")
|
||||
id = video.get("id", "")
|
||||
pic = video.get("coverimg", "")
|
||||
remarks = video.get("updatedate", "")
|
||||
video = {
|
||||
"vod_id": id,
|
||||
"vod_name": name,
|
||||
"vod_pic": pic,
|
||||
"vod_remarks": remarks
|
||||
}
|
||||
videos.append(video)
|
||||
|
||||
except:
|
||||
pass
|
||||
result = {'list': videos}
|
||||
result['page'] = pg
|
||||
result['pagecount'] = 99
|
||||
result['limit'] = 90
|
||||
result['total'] = 99
|
||||
return result
|
||||
|
||||
def detailContent(self, ids):
|
||||
did = ids[0]
|
||||
result = {}
|
||||
videos = []
|
||||
playurl = ''
|
||||
payload2 = {
|
||||
'action': "getvideo",
|
||||
'vid': did,
|
||||
't': "1762756475175265",
|
||||
's': "656d5b40c2122f86bc35895dc58fd113"}
|
||||
res1 = requests.post(url=nurl, data=payload2, headers=headerx)
|
||||
res1.encoding = "utf-8"
|
||||
res = res1.json()
|
||||
node = res.get("data", {}).get("Table", [{}])[0]
|
||||
|
||||
vod_id = node.get("id", "")
|
||||
vod_name = node.get("title", "")
|
||||
vod_pic = node.get("coverimg", "")
|
||||
vod_remarks = node.get("updatedate", "")
|
||||
vod_content = node.get("title", "")
|
||||
|
||||
playFrom = []
|
||||
playList = []
|
||||
if node.get("vurl"):
|
||||
base_url1 = res.get("xldata", {}).get("value", "")
|
||||
base_url2 = res.get("xldata", {}).get("value1", "")
|
||||
if base_url1:
|
||||
full_vurl1 = base_url1 + node.get("vurl", "")
|
||||
playFrom.append("播放源1")
|
||||
playList.append(full_vurl1)
|
||||
if base_url2:
|
||||
full_vurl2 = base_url2 + node.get("vurl", "")
|
||||
playFrom.append("播放源2")
|
||||
playList.append(full_vurl2)
|
||||
|
||||
videos.append({
|
||||
"vod_id": vod_id,
|
||||
"vod_name": vod_name,
|
||||
"vod_pic": vod_pic,
|
||||
"vod_remarks": vod_remarks,
|
||||
"vod_content": vod_content,
|
||||
"vod_play_from": "$$$".join(playFrom),
|
||||
"vod_play_url": "$$$".join(playList)
|
||||
})
|
||||
|
||||
result['list'] = videos
|
||||
return result
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
parts = id.split("http")
|
||||
xiutan = 1
|
||||
if xiutan == 1:
|
||||
if len(parts) > 1:
|
||||
before_https, after_https = parts[0], 'http' + parts[1]
|
||||
result = {}
|
||||
result["parse"] = xiutan
|
||||
result["playUrl"] = ''
|
||||
result["url"] = after_https
|
||||
result["header"] = headerx
|
||||
return result
|
||||
|
||||
def searchContentPage(self, key, quick, page):
|
||||
result = {}
|
||||
videos = []
|
||||
payload3 = {
|
||||
'action': "search",
|
||||
'p': {key},
|
||||
'pageindex': {str(page)},
|
||||
'pagesize': "12",
|
||||
'channelid': "0",
|
||||
't': "1762756927087982",
|
||||
's': "2392bd117b4e6e35b5ec1fa9bc380b6f"}
|
||||
detail = requests.post(url=nurl, data=payload3, headers=headerx)
|
||||
detail.encoding = "utf-8"
|
||||
res = detail.json()
|
||||
video_list = res.get("data", [])
|
||||
for video in video_list:
|
||||
name = video.get("title", "")
|
||||
id = video.get("id", "")
|
||||
pic = video.get("imgurl", "")
|
||||
remarks = video.get("updatedate", "")
|
||||
video = {
|
||||
"vod_id": id,
|
||||
"vod_name": name,
|
||||
"vod_pic": pic,
|
||||
"vod_remarks": remarks
|
||||
}
|
||||
videos.append(video)
|
||||
|
||||
result['list'] = videos
|
||||
result['page'] = page
|
||||
result['pagecount'] = 60
|
||||
result['limit'] = 30
|
||||
result['total'] = 999999
|
||||
return result
|
||||
|
||||
def searchContent(self, key, quick):
|
||||
return self.searchContentPage(key, quick, '1')
|
||||
|
||||
def localProxy(self, params):
|
||||
if params['type'] == "m3u8":
|
||||
return self.proxyM3u8(params)
|
||||
elif params['type'] == "media":
|
||||
return self.proxyMedia(params)
|
||||
elif params['type'] == "ts":
|
||||
return self.proxyTs(params)
|
||||
return None
|
||||
@@ -0,0 +1,303 @@
|
||||
|
||||
import sys
|
||||
import urllib.parse
|
||||
import re
|
||||
from lxml import etree
|
||||
|
||||
sys.path.append('..')
|
||||
from base.spider import Spider
|
||||
|
||||
|
||||
class Spider(Spider):
|
||||
def getName(self):
|
||||
return "禁片天堂"
|
||||
|
||||
def init(self, extend):
|
||||
pass
|
||||
|
||||
def homeContent(self, filter):
|
||||
cateManual = {
|
||||
"中文": "278",
|
||||
"巨乳": "15",
|
||||
"熟女": "95",
|
||||
"騎乘位": "74",
|
||||
"口交": "34",
|
||||
"癡女": "75",
|
||||
"潮吹": "32",
|
||||
"企劃片": "84",
|
||||
"美尻": "156",
|
||||
"打手槍": "98",
|
||||
"戲劇、連續劇": "58",
|
||||
"制服": "19",
|
||||
"美腿": "157",
|
||||
"舔鮑": "122",
|
||||
"美乳": "166",
|
||||
"搭訕": "12",
|
||||
"妄想族": "184",
|
||||
"第一人稱視點": "167",
|
||||
"媽媽系": "193",
|
||||
"人妻・主婦": "26",
|
||||
"多種職業": "84",
|
||||
"羞辱": "163",
|
||||
"女教師": "131",
|
||||
"淫語": "151",
|
||||
"肉感": "136",
|
||||
"愛美臀": "111",
|
||||
"背後位": "178",
|
||||
"調教": "395",
|
||||
"處男": "23",
|
||||
"護士": "283",
|
||||
"修長": "147",
|
||||
"露內褲": "169",
|
||||
"絲襪": "115",
|
||||
"愛巨乳": "200",
|
||||
"眼鏡": "290",
|
||||
"超乳": "211",
|
||||
"顏面騎乘": "263",
|
||||
"惡作劇": "145",
|
||||
"義母": "144",
|
||||
"淫亂・過激系": "63",
|
||||
"愛美腿": "11",
|
||||
"爆乳": "483",
|
||||
"女上司": "137",
|
||||
"正太": "415",
|
||||
"穿衣幹砲": "179",
|
||||
"緊身皮衣": "304",
|
||||
"學園": "421",
|
||||
"空姐": "132",
|
||||
"粉絲感謝祭": "190",
|
||||
"背面騎乗位": "646",
|
||||
"秘書": "363",
|
||||
"女主播": "106",
|
||||
"反向搭訕": "305",
|
||||
"健身教練": "233",
|
||||
"部下・同僚": "150",
|
||||
"舞蹈": "130",
|
||||
"緊身衣激凸": "321",
|
||||
"3D影片": "508",
|
||||
"早洩": "403"
|
||||
}
|
||||
result = {'class': [{'type_name': k, 'type_id': v} for k, v in cateManual.items()]}
|
||||
return result
|
||||
|
||||
def homeVideoContent(self):
|
||||
return {}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
result = {}
|
||||
url = f'https://jptt.tv/tag_list?tid={tid}&idx={pg}'
|
||||
try:
|
||||
rsp = self.fetch(url)
|
||||
root = etree.HTML(rsp.text)
|
||||
videos = root.xpath('//div[contains(@class,"oneVideo")]')
|
||||
vodList = []
|
||||
for video in videos:
|
||||
try:
|
||||
name_elements = video.xpath('.//h3/text()')
|
||||
if not name_elements:
|
||||
continue
|
||||
name = name_elements[0].strip()
|
||||
|
||||
img_elements = video.xpath('.//img/@src')
|
||||
if not img_elements:
|
||||
continue
|
||||
img = img_elements[0]
|
||||
if not img.startswith('http'):
|
||||
img = 'https://jptt.tv' + img
|
||||
|
||||
desc_elements = video.xpath('.//p[contains(@class,"p_duration")]/text()')
|
||||
desc = desc_elements[0].strip() if desc_elements else ''
|
||||
|
||||
link_elements = video.xpath('.//a/@href')
|
||||
if not link_elements:
|
||||
continue
|
||||
link = link_elements[0]
|
||||
|
||||
vodList.append({
|
||||
"vod_name": name,
|
||||
"vod_pic": img,
|
||||
"vod_remarks": desc,
|
||||
"vod_id": link
|
||||
})
|
||||
except Exception as e:
|
||||
print(f"[categoryContent video parse error]: {e}")
|
||||
continue
|
||||
|
||||
result['list'] = vodList
|
||||
result['page'] = pg
|
||||
result['pagecount'] = 9999
|
||||
result['limit'] = 90
|
||||
result['total'] = 999999
|
||||
except Exception as e:
|
||||
print(f"[categoryContent fetch error]: {e}")
|
||||
result['list'] = []
|
||||
result['page'] = pg
|
||||
result['pagecount'] = 0
|
||||
result['limit'] = 0
|
||||
result['total'] = 0
|
||||
return result
|
||||
|
||||
def detailContent(self, array):
|
||||
tid = array[0]
|
||||
url = tid if tid.startswith('http') else f'https://jptt.tv{tid}'
|
||||
try:
|
||||
rsp = self.fetch(url)
|
||||
root = etree.HTML(rsp.text)
|
||||
|
||||
title_elements = root.xpath('//h1[@class="h1_title"]/text()')
|
||||
title = title_elements[0].strip() if title_elements else "未知标题"
|
||||
|
||||
pic_elements = root.xpath('//video/@poster')
|
||||
pic = pic_elements[0] if pic_elements else ""
|
||||
if pic and not pic.startswith('http'):
|
||||
pic = 'https://jptt.tv' + pic
|
||||
|
||||
desc_elements = root.xpath('//div[contains(@class,"info_original")]//p/text()')
|
||||
desc = desc_elements[0].strip() if desc_elements else title
|
||||
|
||||
play_url = self.extractVideoUrl(rsp.text)
|
||||
|
||||
vod = {
|
||||
"vod_id": tid,
|
||||
"vod_name": title,
|
||||
"vod_pic": pic,
|
||||
"vod_content": desc,
|
||||
"vod_play_from": "屌戳插操",
|
||||
"vod_play_url": "日你底下$" + play_url
|
||||
}
|
||||
return {'list': [vod]}
|
||||
except Exception as e:
|
||||
print(f"[detailContent error]: {e}")
|
||||
return {'list': []}
|
||||
|
||||
def extractVideoUrl(self, html):
|
||||
try:
|
||||
source_match = re.search(r'<source\s+src="([^"]+)"', html)
|
||||
if source_match:
|
||||
video_url = source_match.group(1)
|
||||
if video_url.startswith('//'):
|
||||
video_url = 'https:' + video_url
|
||||
return video_url
|
||||
|
||||
hls_patterns = [
|
||||
r'//cdn-[^"\']+\.m3u8[^"\']*',
|
||||
r'https?://[^"\']+\.m3u8[^"\']*',
|
||||
r'/hlsredirect/[^"\']+\.m3u8'
|
||||
]
|
||||
for pattern in hls_patterns:
|
||||
matches = re.findall(pattern, html)
|
||||
if matches:
|
||||
for match in matches:
|
||||
if match.startswith('//'):
|
||||
return 'https:' + match
|
||||
elif match.startswith('http'):
|
||||
return match
|
||||
else:
|
||||
return 'https://jptt.tv' + match
|
||||
|
||||
js_patterns = [
|
||||
r'src\s*:\s*["\']([^"\']+\.m3u8[^"\']*)["\']',
|
||||
r'url\s*:\s*["\']([^"\']+\.m3u8[^"\']*)["\']',
|
||||
r'file\s*:\s*["\']([^"\']+\.m3u8[^"\']*)["\']'
|
||||
]
|
||||
for pattern in js_patterns:
|
||||
match = re.search(pattern, html)
|
||||
if match:
|
||||
video_url = match.group(1)
|
||||
if video_url.startswith('//'):
|
||||
return 'https:' + video_url
|
||||
elif video_url.startswith('http'):
|
||||
return video_url
|
||||
else:
|
||||
return 'https://jptt.tv' + video_url
|
||||
|
||||
all_m3u8 = re.findall(r'["\'](https?://[^"\']+\.m3u8[^"\']*)["\']', html)
|
||||
if all_m3u8:
|
||||
return all_m3u8[0]
|
||||
except Exception as e:
|
||||
print(f"[extractVideoUrl error]: {e}")
|
||||
|
||||
return "https://cdn-mso2.jptt1.cc/hlsredirect/EXBrcBO4G9RhgaUlZQhY1w/1760457600/hls/video/1/99-22-00164.3gp/index.m3u8"
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
result = {}
|
||||
url = f'https://jptt.tv/search?kw={urllib.parse.quote(key)}'
|
||||
try:
|
||||
rsp = self.fetch(url)
|
||||
root = etree.HTML(rsp.text)
|
||||
videos = root.xpath('//div[contains(@class,"oneVideo")]')
|
||||
vodList = []
|
||||
for video in videos:
|
||||
try:
|
||||
name_elements = video.xpath('.//h3/text()')
|
||||
if not name_elements:
|
||||
continue
|
||||
name = name_elements[0].strip()
|
||||
|
||||
img_elements = video.xpath('.//img/@src')
|
||||
if not img_elements:
|
||||
continue
|
||||
img = img_elements[0]
|
||||
if not img.startswith('http'):
|
||||
img = 'https://jptt.tv' + img
|
||||
|
||||
desc_elements = video.xpath('.//p[contains(@class,"p_duration")]/text()')
|
||||
desc = desc_elements[0].strip() if desc_elements else ''
|
||||
|
||||
link_elements = video.xpath('.//a/@href')
|
||||
if not link_elements:
|
||||
continue
|
||||
link = link_elements[0]
|
||||
|
||||
vodList.append({
|
||||
"vod_name": name,
|
||||
"vod_pic": img,
|
||||
"vod_remarks": desc,
|
||||
"vod_id": link
|
||||
})
|
||||
except Exception as e:
|
||||
print(f"[searchContent video parse error]: {e}")
|
||||
continue
|
||||
|
||||
result['list'] = vodList
|
||||
except Exception as e:
|
||||
print(f"[searchContent fetch error]: {e}")
|
||||
result['list'] = []
|
||||
return result
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
result = {}
|
||||
if flag == "屌戳插操":
|
||||
try:
|
||||
if id.startswith('http') and '.m3u8' in id:
|
||||
result["parse"] = 0
|
||||
result["playUrl"] = ''
|
||||
result["url"] = id
|
||||
else:
|
||||
url = id if id.startswith('http') else f'https://jptt.tv{id}'
|
||||
rsp = self.fetch(url)
|
||||
play_url = self.extractVideoUrl(rsp.text)
|
||||
result["parse"] = 0
|
||||
result["playUrl"] = ''
|
||||
result["url"] = play_url
|
||||
except Exception as e:
|
||||
print(f"[playerContent error]: {e}")
|
||||
result["parse"] = 0
|
||||
result["playUrl"] = ''
|
||||
result["url"] = "https://cdn-mso2.jptt1.cc/hlsredirect/EXBrcBO4G9RhgaUlZQhY1w/1760457600/hls/video/1/99-22-00164.3gp/index.m3u8"
|
||||
|
||||
result["header"] = {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.54 Safari/537.36",
|
||||
"Referer": "https://jptt.tv/",
|
||||
"Origin": "https://jptt.tv"
|
||||
}
|
||||
return result
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def localProxy(self, param):
|
||||
pass
|
||||
@@ -0,0 +1,385 @@
|
||||
# coding=utf-8
|
||||
# !/usr/bin/python
|
||||
|
||||
"""
|
||||
|
||||
作者 丢丢喵 🚓 内容均从互联网收集而来 仅供交流学习使用 版权归原创者所有 如侵犯了您的权益 请通知作者 将及时删除侵权内容
|
||||
====================Diudiumiao====================
|
||||
|
||||
"""
|
||||
|
||||
from Crypto.Util.Padding import unpad
|
||||
from Crypto.Util.Padding import pad
|
||||
from urllib.parse import unquote
|
||||
from Crypto.Cipher import ARC4
|
||||
from urllib.parse import quote
|
||||
from base.spider import Spider
|
||||
from Crypto.Cipher import AES
|
||||
from datetime import datetime
|
||||
from bs4 import BeautifulSoup
|
||||
from base64 import b64decode
|
||||
import urllib.request
|
||||
import urllib.parse
|
||||
import datetime
|
||||
import binascii
|
||||
import requests
|
||||
import random
|
||||
import base64
|
||||
import html
|
||||
import json
|
||||
import time
|
||||
import sys
|
||||
import re
|
||||
import os
|
||||
|
||||
sys.path.append('..')
|
||||
|
||||
headerz = {
|
||||
'sec-ch-ua': '"Microsoft Edge";v="129", "Not=A?Brand";v="8", "Chromium";v="129"',
|
||||
'sec-ch-ua-mobile': '?0',
|
||||
'sec-ch-ua-platform': '"Windows"',
|
||||
'Upgrade-Insecure-Requests': '1',
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36 Edg/129.0.0.0',
|
||||
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
|
||||
'Sec-Fetch-Site': 'none',
|
||||
'Sec-Fetch-Mode': 'navigate',
|
||||
'Sec-Fetch-User': '?1',
|
||||
'Sec-Fetch-Dest': 'document',
|
||||
'Accept-Language': 'zh-CN,zh;q=0.9',
|
||||
'Accept-Encoding': 'gzip, deflate'
|
||||
}
|
||||
|
||||
xurl = "https://rb.huaduys.org"
|
||||
|
||||
response = requests.get(xurl, headers=headerz)
|
||||
cookie_dict = {}
|
||||
for cookie in response.cookies:
|
||||
cookie_dict[cookie.name] = cookie.value
|
||||
first_cookie_key = None
|
||||
first_cookie_value = None
|
||||
server_session_value = cookie_dict.get('server_name_session')
|
||||
for key, value in cookie_dict.items():
|
||||
if key != 'server_name_session':
|
||||
first_cookie_key = key
|
||||
first_cookie_value = value
|
||||
break
|
||||
|
||||
headerx = {
|
||||
"Host": "rb.huaduys.org",
|
||||
"Connection": "keep-alive",
|
||||
"sec-ch-ua": '"Microsoft Edge";v="129", "Not=A?Brand";v="8", "Chromium";v="129"',
|
||||
"sec-ch-ua-mobile": "?0",
|
||||
"sec-ch-ua-platform": '"Windows"',
|
||||
"Upgrade-Insecure-Requests": "1",
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36 Edg/129.0.0.0",
|
||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
|
||||
"Sec-Fetch-Site": "same-origin",
|
||||
"Sec-Fetch-Mode": "navigate",
|
||||
"Sec-Fetch-Dest": "document",
|
||||
"Referer": "https://rb.huaduys.org/",
|
||||
"Accept-Language": "zh-CN,zh;q=0.9",
|
||||
"Cookie": f"{first_cookie_key}={first_cookie_value}; server_name_session={server_session_value}",
|
||||
"Accept-Encoding": "gzip, deflate"
|
||||
}
|
||||
|
||||
headers = {
|
||||
'User-Agent': 'Linux; Android 12; Pixel 3 XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.101 Mobile Safari/537.36'
|
||||
}
|
||||
|
||||
class Spider(Spider):
|
||||
global xurl
|
||||
global headerx
|
||||
global headers
|
||||
|
||||
def getName(self):
|
||||
return "首页"
|
||||
|
||||
def init(self, extend):
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def extract_middle_text(self, text, start_str, end_str, pl, start_index1: str = '', end_index2: str = ''):
|
||||
if pl == 3:
|
||||
plx = []
|
||||
while True:
|
||||
start_index = text.find(start_str)
|
||||
if start_index == -1:
|
||||
break
|
||||
end_index = text.find(end_str, start_index + len(start_str))
|
||||
if end_index == -1:
|
||||
break
|
||||
middle_text = text[start_index + len(start_str):end_index]
|
||||
plx.append(middle_text)
|
||||
text = text.replace(start_str + middle_text + end_str, '')
|
||||
if len(plx) > 0:
|
||||
purl = ''
|
||||
for i in range(len(plx)):
|
||||
matches = re.findall(start_index1, plx[i])
|
||||
output = ""
|
||||
for match in matches:
|
||||
match3 = re.search(r'(?:^|[^0-9])(\d+)(?:[^0-9]|$)', match[1])
|
||||
if match3:
|
||||
number = match3.group(1)
|
||||
else:
|
||||
number = 0
|
||||
if 'http' not in match[0]:
|
||||
output += f"#{match[1]}${number}{xurl}{match[0]}"
|
||||
else:
|
||||
output += f"#{match[1]}${number}{match[0]}"
|
||||
output = output[1:]
|
||||
purl = purl + output + "$$$"
|
||||
purl = purl[:-3]
|
||||
return purl
|
||||
else:
|
||||
return ""
|
||||
else:
|
||||
start_index = text.find(start_str)
|
||||
if start_index == -1:
|
||||
return ""
|
||||
end_index = text.find(end_str, start_index + len(start_str))
|
||||
if end_index == -1:
|
||||
return ""
|
||||
|
||||
if pl == 0:
|
||||
middle_text = text[start_index + len(start_str):end_index]
|
||||
return middle_text.replace("\\", "")
|
||||
|
||||
if pl == 1:
|
||||
middle_text = text[start_index + len(start_str):end_index]
|
||||
matches = re.findall(start_index1, middle_text)
|
||||
if matches:
|
||||
jg = ' '.join(matches)
|
||||
return jg
|
||||
|
||||
if pl == 2:
|
||||
middle_text = text[start_index + len(start_str):end_index]
|
||||
matches = re.findall(start_index1, middle_text)
|
||||
if matches:
|
||||
new_list = [f'{item}' for item in matches]
|
||||
jg = '$$$'.join(new_list)
|
||||
return jg
|
||||
|
||||
def parse_videos_from_doc(self, doc, xurl):
|
||||
videos = []
|
||||
|
||||
skip_names = ["广告点赞"]
|
||||
|
||||
soups = doc.find_all('ul', class_="stui-vodlist clearfix")
|
||||
|
||||
for soup in soups:
|
||||
vods = soup.find_all('li')
|
||||
|
||||
for vod in vods:
|
||||
|
||||
remarks = vod.find('a', class_="stui-vodlist__thumb picture w-thumb img-shadow")
|
||||
remark = remarks.text.strip() + "点赞"
|
||||
if remark in skip_names:
|
||||
continue
|
||||
|
||||
names = vod.find('h4', class_="title text-overflow")
|
||||
name = names.text.strip()
|
||||
|
||||
id = names.find('a')['href']
|
||||
|
||||
pic = vod.find('img')['data-original']
|
||||
if 'http' not in pic:
|
||||
pic = xurl + pic
|
||||
|
||||
video = {
|
||||
"vod_id": id,
|
||||
"vod_name": name,
|
||||
"vod_pic": pic,
|
||||
"vod_remarks": '集多▶️' + remark
|
||||
}
|
||||
videos.append(video)
|
||||
|
||||
return videos
|
||||
|
||||
def homeContent(self, filter):
|
||||
result = {"class": []}
|
||||
|
||||
detail = requests.get(url=xurl, headers=headerx)
|
||||
detail.encoding = "utf-8"
|
||||
res = detail.text
|
||||
doc = BeautifulSoup(res, "lxml")
|
||||
|
||||
soups = doc.find_all('ul', class_="stui-header__menu type-slide")
|
||||
|
||||
for soup in soups:
|
||||
vods = soup.find_all('li')
|
||||
|
||||
for vod in vods:
|
||||
|
||||
name = vod.text.strip()
|
||||
skip_names = ["首页", "发布页", "免费VPN下载"]
|
||||
if name in skip_names:
|
||||
continue
|
||||
|
||||
id1 = vod.find('a')['href']
|
||||
fenge = id1.split(".html")
|
||||
id = f"{fenge[0]}-----------.html"
|
||||
id = id.replace('vodtype', 'vodshow')
|
||||
|
||||
result["class"].append({"type_id": id, "type_name": "集多🌠" + name})
|
||||
|
||||
return result
|
||||
|
||||
def homeVideoContent(self):
|
||||
videos = []
|
||||
|
||||
detail = requests.get(url=xurl, headers=headerx)
|
||||
detail.encoding = "utf-8"
|
||||
res = detail.text
|
||||
doc = BeautifulSoup(res, "lxml")
|
||||
videos = self.parse_videos_from_doc(doc, xurl)
|
||||
|
||||
result = {'list': videos}
|
||||
return result
|
||||
|
||||
def categoryContent(self, cid, pg, filter, ext):
|
||||
result = {}
|
||||
videos = []
|
||||
|
||||
if pg:
|
||||
page = int(pg)
|
||||
else:
|
||||
page = 1
|
||||
|
||||
fenge = cid.split("---.html")
|
||||
url = f'{xurl}{fenge[0]}{str(page)}---.html'
|
||||
detail = requests.get(url=url, headers=headerx)
|
||||
detail.encoding = "utf-8"
|
||||
res = detail.text
|
||||
doc = BeautifulSoup(res, "lxml")
|
||||
videos = self.parse_videos_from_doc(doc, xurl)
|
||||
|
||||
result = {'list': videos}
|
||||
result['page'] = pg
|
||||
result['pagecount'] = 9999
|
||||
result['limit'] = 90
|
||||
result['total'] = 999999
|
||||
return result
|
||||
|
||||
def detailContent(self, ids):
|
||||
did = ids[0]
|
||||
result = {}
|
||||
videos = []
|
||||
xianlu = ''
|
||||
bofang = ''
|
||||
|
||||
if 'http' not in did:
|
||||
did = xurl + did
|
||||
|
||||
res = requests.get(url=did, headers=headerx)
|
||||
res.encoding = "utf-8"
|
||||
res = res.text
|
||||
res = html.unescape(res)
|
||||
|
||||
url = 'http://rihou.cc:88/je.json'
|
||||
response = requests.get(url)
|
||||
response.encoding = 'utf-8'
|
||||
code = response.text
|
||||
name = self.extract_middle_text(code, "s1='", "'", 0)
|
||||
Jumps = self.extract_middle_text(code, "s2='", "'", 0)
|
||||
|
||||
content = '集多🎉为您介绍剧情📢' + self.extract_middle_text(res, '标题:', '</span>', 1, 'alt="(.*?)">')
|
||||
|
||||
director = self.extract_middle_text(res, '分类:', '</p>', 1, 'target=".*?">(.*?)</a>')
|
||||
|
||||
actor = self.extract_middle_text(res, '演员:', '</span>', 1, 'target=".*?">(.*?)</a>')
|
||||
|
||||
remarks = self.extract_middle_text(res, '类别:', '</li>', 1, 'target=".*?">(.*?)</a>')
|
||||
|
||||
year = self.extract_middle_text(res, '日期:', 'p>', 1, '</strong>(.*?)<')
|
||||
|
||||
area = self.extract_middle_text(res, '时长:', 'p>', 1, '</strong>(.*?)<')
|
||||
|
||||
if name not in content:
|
||||
bofang = Jumps
|
||||
xianlu = '1'
|
||||
else:
|
||||
id = self.extract_middle_text(res, 'class="btn btn-primary" href="', '"', 0)
|
||||
if 'http' not in id:
|
||||
id = xurl + id
|
||||
|
||||
name = "集多请您欣赏"
|
||||
|
||||
bofang = name + '$' + id
|
||||
|
||||
xianlu = '花都专线'
|
||||
|
||||
videos.append({
|
||||
"vod_id": did,
|
||||
"vod_director": director,
|
||||
"vod_actor": actor,
|
||||
"vod_remarks": remarks,
|
||||
"vod_year": year,
|
||||
"vod_area": area,
|
||||
"vod_content": content,
|
||||
"vod_play_from": xianlu,
|
||||
"vod_play_url": bofang
|
||||
})
|
||||
|
||||
result['list'] = videos
|
||||
return result
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
|
||||
detail = requests.get(url=id, headers=headerx)
|
||||
detail.encoding = "utf-8"
|
||||
res = detail.text
|
||||
|
||||
url = self.extract_middle_text(res, '"","url":"', '"', 0).replace('\\', '')
|
||||
base64_decoded_bytes = base64.b64decode(url)
|
||||
base64_decoded_string = base64_decoded_bytes.decode('utf-8')
|
||||
url = unquote(base64_decoded_string)
|
||||
|
||||
result = {}
|
||||
result["parse"] = 0
|
||||
result["playUrl"] = ''
|
||||
result["url"] = url
|
||||
result["header"] = headers
|
||||
return result
|
||||
|
||||
def searchContentPage(self, key, quick, pg):
|
||||
result = {}
|
||||
videos = []
|
||||
|
||||
url = f'{xurl}/vodsearch/-------------.html?wd={key}'
|
||||
detail = requests.get(url=url, headers=headerx)
|
||||
detail.encoding = "utf-8"
|
||||
res = detail.text
|
||||
doc = BeautifulSoup(res, "lxml")
|
||||
videos = self.parse_videos_from_doc(doc, xurl)
|
||||
|
||||
result['list'] = videos
|
||||
result['page'] = pg
|
||||
result['pagecount'] = 9999
|
||||
result['limit'] = 90
|
||||
result['total'] = 999999
|
||||
return result
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
return self.searchContentPage(key, quick, '1')
|
||||
|
||||
def localProxy(self, params):
|
||||
if params['type'] == "m3u8":
|
||||
return self.proxyM3u8(params)
|
||||
elif params['type'] == "media":
|
||||
return self.proxyMedia(params)
|
||||
elif params['type'] == "ts":
|
||||
return self.proxyTs(params)
|
||||
return None
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+371
@@ -0,0 +1,371 @@
|
||||
# coding=utf-8
|
||||
# !/usr/bin/python
|
||||
|
||||
"""
|
||||
|
||||
作者 丢丢喵 内容均从互联网收集而来 仅供交流学习使用 严禁用于商业用途 请于24小时内删除
|
||||
====================Diudiumiao====================
|
||||
|
||||
"""
|
||||
|
||||
from Crypto.Util.Padding import unpad
|
||||
from Crypto.Util.Padding import pad
|
||||
from urllib.parse import urlparse
|
||||
from urllib.parse import unquote
|
||||
from Crypto.Cipher import ARC4
|
||||
from urllib.parse import quote
|
||||
from base.spider import Spider
|
||||
from Crypto.Cipher import AES
|
||||
from datetime import datetime
|
||||
from bs4 import BeautifulSoup
|
||||
from base64 import b64decode
|
||||
import concurrent.futures
|
||||
import urllib.request
|
||||
import urllib.parse
|
||||
import datetime
|
||||
import binascii
|
||||
import requests
|
||||
import base64
|
||||
import json
|
||||
import time
|
||||
import sys
|
||||
import re
|
||||
import os
|
||||
|
||||
sys.path.append('..')
|
||||
|
||||
xurl = "https://eibdlw.hq123.icu"
|
||||
|
||||
headerx = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.87 Safari/537.36'
|
||||
}
|
||||
|
||||
class Spider(Spider):
|
||||
|
||||
def getName(self):
|
||||
return "丢丢喵"
|
||||
|
||||
def init(self, extend):
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def searchContentPage(self, key, quick, pg):
|
||||
pass
|
||||
|
||||
def homeVideoContent(self):
|
||||
pass
|
||||
|
||||
def extract_middle_text(self, text, start_str, end_str, pl, start_index1: str = '', end_index2: str = ''):
|
||||
if pl == 3:
|
||||
plx = []
|
||||
while True:
|
||||
start_index = text.find(start_str)
|
||||
if start_index == -1:
|
||||
break
|
||||
end_index = text.find(end_str, start_index + len(start_str))
|
||||
if end_index == -1:
|
||||
break
|
||||
middle_text = text[start_index + len(start_str):end_index]
|
||||
plx.append(middle_text)
|
||||
text = text.replace(start_str + middle_text + end_str, '')
|
||||
if len(plx) > 0:
|
||||
purl = ''
|
||||
for i in range(len(plx)):
|
||||
matches = re.findall(start_index1, plx[i])
|
||||
output = ""
|
||||
for match in matches:
|
||||
match3 = re.search(r'(?:^|[^0-9])(\d+)(?:[^0-9]|$)', match[1])
|
||||
if match3:
|
||||
number = match3.group(1)
|
||||
else:
|
||||
number = 0
|
||||
if 'http' not in match[0]:
|
||||
output += f"#{match[1]}${number}{xurl}{match[0]}"
|
||||
else:
|
||||
output += f"#{match[1]}${number}{match[0]}"
|
||||
output = output[1:]
|
||||
purl = purl + output + "$$$"
|
||||
purl = purl[:-3]
|
||||
return purl
|
||||
else:
|
||||
return ""
|
||||
else:
|
||||
start_index = text.find(start_str)
|
||||
if start_index == -1:
|
||||
return ""
|
||||
end_index = text.find(end_str, start_index + len(start_str))
|
||||
if end_index == -1:
|
||||
return ""
|
||||
|
||||
if pl == 0:
|
||||
middle_text = text[start_index + len(start_str):end_index]
|
||||
return middle_text.replace("\\", "")
|
||||
|
||||
if pl == 1:
|
||||
middle_text = text[start_index + len(start_str):end_index]
|
||||
matches = re.findall(start_index1, middle_text)
|
||||
if matches:
|
||||
jg = ' '.join(matches)
|
||||
return jg
|
||||
|
||||
if pl == 2:
|
||||
middle_text = text[start_index + len(start_str):end_index]
|
||||
matches = re.findall(start_index1, middle_text)
|
||||
if matches:
|
||||
new_list = [f'{item}' for item in matches]
|
||||
jg = '$$$'.join(new_list)
|
||||
return jg
|
||||
|
||||
def decrypt_data(self, token: str, key: str = "UC2FmMyG928hRZY4") -> dict:
|
||||
padded_token = self.pad_token(token)
|
||||
bin_data = self.base64_decode(padded_token)
|
||||
k = self.encode_key(key)
|
||||
payload = self.xor_decrypt(bin_data, k)
|
||||
body = self.decompress_payload(payload)
|
||||
return self.parse_json_body(body)
|
||||
|
||||
def pad_token(self, token):
|
||||
return token + '=' * (4 - len(token) % 4)
|
||||
|
||||
def base64_decode(self, token):
|
||||
return base64.urlsafe_b64decode(token)
|
||||
|
||||
def encode_key(self, key):
|
||||
return key.encode('utf-8')
|
||||
|
||||
def xor_decrypt(self, bin_data, k):
|
||||
return bytes(b ^ k[i % len(k)] for i, b in enumerate(bin_data))
|
||||
|
||||
def decompress_payload(self, payload):
|
||||
if payload[0] == 1:
|
||||
return zlib.decompress(payload[1:], -15)
|
||||
return payload[1:]
|
||||
|
||||
def parse_json_body(self, body):
|
||||
return json.loads(body.decode('utf-8'))
|
||||
|
||||
def homeContent(self, filter):
|
||||
result = {"class": []}
|
||||
res = self.get_home_page()
|
||||
ress = self.extract_app_index(res)
|
||||
data = self.decrypt_data(ress)
|
||||
self.process_menu_items(data['menu'], result)
|
||||
return result
|
||||
|
||||
def get_home_page(self):
|
||||
detail = requests.get(url=xurl, headers=headerx)
|
||||
detail.encoding = "utf-8"
|
||||
return detail.text
|
||||
|
||||
def extract_app_index(self, res):
|
||||
return self.extract_middle_text(res, "APP.Index('", "'", 0)
|
||||
|
||||
def process_menu_items(self, menu, result):
|
||||
for vod in menu:
|
||||
if not self.is_skipped_name(vod['name']):
|
||||
self.append_class(result, vod)
|
||||
|
||||
def is_skipped_name(self, name):
|
||||
skip_names = ["首頁", "AI脱衣", "小说", "影视剧", "涩漫", "抖淫", "黄游", "AI伴侣", "同城泻火"]
|
||||
return name in skip_names
|
||||
|
||||
def append_class(self, result, vod):
|
||||
result["class"].append({"type_id": vod['id'], "type_name": vod['name']})
|
||||
|
||||
def categoryContent(self, cid, pg, filter, ext):
|
||||
videos = []
|
||||
page = self.get_page_number(pg)
|
||||
if '9' in cid:
|
||||
url = self.build_gua_url(cid, page)
|
||||
res = self.get_page_content(url)
|
||||
ress = self.extract_app_index(res)
|
||||
data = self.decrypt_data(ress)
|
||||
self.process_gua_list(data['list'], videos)
|
||||
else:
|
||||
url = self.build_video_url(cid, page)
|
||||
res = self.get_page_content(url)
|
||||
ress = self.extract_app_index(res)
|
||||
data = self.decrypt_data(ress)
|
||||
self.process_video_list(data['list'], videos)
|
||||
result = self.build_category_result(videos, pg)
|
||||
return result
|
||||
|
||||
def get_page_number(self, pg):
|
||||
return int(pg) if pg else 1
|
||||
|
||||
def build_gua_url(self, cid, page):
|
||||
return f'{xurl}/category/{cid}/-{str(page)}-'
|
||||
|
||||
def build_video_url(self, cid, page):
|
||||
return f'{xurl}/category/{cid}/---{str(page)}'
|
||||
|
||||
def get_page_content(self, url):
|
||||
detail = requests.get(url=url, headers=headerx)
|
||||
detail.encoding = "utf-8"
|
||||
return detail.text
|
||||
|
||||
def extract_app_index(self, res):
|
||||
return self.extract_middle_text(res, "APP.Index('", "'", 0)
|
||||
|
||||
def process_gua_list(self, vod_list, videos):
|
||||
for vod in vod_list:
|
||||
video = self.parse_gua_video(vod)
|
||||
videos.append(video)
|
||||
|
||||
def parse_gua_video(self, vod):
|
||||
return {
|
||||
"vod_id": f"{vod.get('art_id')}@gua_details",
|
||||
"vod_name": vod.get('art_name'),
|
||||
"vod_pic": vod.get('art_pic')
|
||||
}
|
||||
|
||||
def process_video_list(self, vod_list, videos):
|
||||
for vod in vod_list:
|
||||
video = self.parse_video(vod)
|
||||
videos.append(video)
|
||||
|
||||
def parse_video(self, vod):
|
||||
return {
|
||||
"vod_id": f"{vod.get('vod_id')}@videoplay",
|
||||
"vod_name": vod.get('vod_name'),
|
||||
"vod_pic": vod.get('vod_pic')
|
||||
}
|
||||
|
||||
def build_category_result(self, videos, pg):
|
||||
result = {'list': videos}
|
||||
result['page'] = pg
|
||||
result['pagecount'] = 9999
|
||||
result['limit'] = 90
|
||||
result['total'] = 999999
|
||||
return result
|
||||
|
||||
def decode_video_url(self, encrypted_hex):
|
||||
key = self.get_aes_key()
|
||||
cipher_hex, iv_hex = self.split_hex_data(encrypted_hex)
|
||||
ciphertext = self.hex_to_bytes(cipher_hex)
|
||||
iv = self.hex_to_bytes(iv_hex)
|
||||
cipher = self.create_aes_cipher(key, iv)
|
||||
decrypted_bytes = self.decrypt_ciphertext(cipher, ciphertext)
|
||||
decrypted_str = self.bytes_to_string(decrypted_bytes)
|
||||
return self.parse_json(decrypted_str)
|
||||
|
||||
def get_aes_key(self):
|
||||
return b"WB0nMZHXlxNndORe"
|
||||
|
||||
def split_hex_data(self, encrypted_hex):
|
||||
n = len(encrypted_hex)
|
||||
if n < 48:
|
||||
cipher_hex = encrypted_hex[:n - 32]
|
||||
iv_hex = encrypted_hex[n - 32:]
|
||||
else:
|
||||
cipher_hex = encrypted_hex[:16] + encrypted_hex[48:]
|
||||
iv_hex = encrypted_hex[16:48]
|
||||
return cipher_hex, iv_hex
|
||||
|
||||
def hex_to_bytes(self, hex_str):
|
||||
return binascii.unhexlify(hex_str)
|
||||
|
||||
def create_aes_cipher(self, key, iv):
|
||||
return AES.new(key, AES.MODE_CFB, iv=iv, segment_size=128)
|
||||
|
||||
def decrypt_ciphertext(self, cipher, ciphertext):
|
||||
return cipher.decrypt(ciphertext)
|
||||
|
||||
def bytes_to_string(self, decrypted_bytes):
|
||||
return decrypted_bytes.decode('utf-8')
|
||||
|
||||
def parse_json(self, decrypted_str):
|
||||
return json.loads(decrypted_str)
|
||||
|
||||
def detailContent(self, ids):
|
||||
did = self.get_first_id(ids)
|
||||
fenge = self.split_did(did)
|
||||
if self.is_gua_details(fenge):
|
||||
data = self.get_gua_details_data(fenge)
|
||||
bofang = self.extract_gua_play_url(data)
|
||||
else:
|
||||
data = self.get_normal_details_data(fenge)
|
||||
bofang = self.extract_normal_play_url(data)
|
||||
videos = self.build_video_info(did, bofang)
|
||||
result = self.build_detail_result(videos)
|
||||
return result
|
||||
|
||||
def get_first_id(self, ids):
|
||||
return ids[0]
|
||||
|
||||
def split_did(self, did):
|
||||
return did.split("@")
|
||||
|
||||
def is_gua_details(self, fenge):
|
||||
return 'gua_details' in fenge[1]
|
||||
|
||||
def get_gua_details_data(self, fenge):
|
||||
url = self.build_gua_details_url(fenge)
|
||||
res = self.get_page_content(url)
|
||||
ress = self.extract_middle_text(res, "content:'", "'", 0)
|
||||
return self.decode_video_url(ress)
|
||||
|
||||
def build_gua_details_url(self, fenge):
|
||||
return f'{xurl}/{fenge[1]}/{fenge[0]}'
|
||||
|
||||
def get_page_content(self, url):
|
||||
detail = requests.get(url=url, headers=headerx)
|
||||
detail.encoding = "utf-8"
|
||||
return detail.text
|
||||
|
||||
def extract_gua_play_url(self, data):
|
||||
return data['0']['vod_play_url'][0]['list'][0]['h264']
|
||||
|
||||
def get_normal_details_data(self, fenge):
|
||||
url = self.build_normal_details_url(fenge)
|
||||
res = self.get_page_content(url)
|
||||
ress = self.extract_middle_text(res, "APP.Index('", "'", 0)
|
||||
return self.decrypt_data(ress)
|
||||
|
||||
def build_normal_details_url(self, fenge):
|
||||
return f'{xurl}/{fenge[1]}/{fenge[0]}'
|
||||
|
||||
def extract_normal_play_url(self, data):
|
||||
return data.get('info', {}).get('vod_play_url') or data.get('info', {}).get('vod_down_url')
|
||||
|
||||
def build_video_info(self, did, bofang):
|
||||
return [{"vod_id": did,"vod_play_from": "黄区专线","vod_play_url": bofang}]
|
||||
|
||||
def build_detail_result(self, videos):
|
||||
result = {}
|
||||
result['list'] = videos
|
||||
return result
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
result = {}
|
||||
result["parse"] = 0
|
||||
result["playUrl"] = ''
|
||||
result["url"] = id
|
||||
result["header"] = headerx
|
||||
return result
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
return self.searchContentPage(key, quick, '1')
|
||||
|
||||
def localProxy(self, params):
|
||||
if params['type'] == "m3u8":
|
||||
return self.proxyM3u8(params)
|
||||
elif params['type'] == "media":
|
||||
return self.proxyMedia(params)
|
||||
elif params['type'] == "ts":
|
||||
return self.proxyTs(params)
|
||||
return None
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,278 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import json,re,sys,base64,requests,threading,time,random,colorsys
|
||||
from Crypto.Cipher import AES
|
||||
from pyquery import PyQuery as pq
|
||||
from urllib.parse import quote, unquote
|
||||
sys.path.append('..')
|
||||
from base.spider import Spider
|
||||
|
||||
class Spider(Spider):
|
||||
SELECTORS=['.video-item','.video-list .item','.list-item','.post-item']
|
||||
def getName(self):return"黑料不打烊"
|
||||
def init(self,extend=""):pass
|
||||
def homeContent(self,filter):
|
||||
cateManual={"最新黑料":"hlcg","今日热瓜":"jrrs","每日TOP10":"mrrb","反差女友":"fczq","校园黑料":"xycg","网红黑料":"whhl","明星丑闻":"mxcw","原创社区":"ycsq","推特社区":"ttsq","社会新闻":"shxw","官场爆料":"gchl","影视短剧":"ysdj","全球奇闻":"qqqw","黑料课堂":"hlkt","每日大赛":"mrds","激情小说":"jqxs","桃图杂志":"ttzz","深夜综艺":"syzy","独家爆料":"djbl"}
|
||||
return{'class':[{'type_name':k,'type_id':v}for k,v in cateManual.items()]}
|
||||
def homeVideoContent(self):return{}
|
||||
def categoryContent(self,tid,pg,filter,extend):
|
||||
url=f'https://heiliao.com/{tid}/'if int(pg)==1 else f'https://heiliao.com/{tid}/page/{pg}/'
|
||||
videos=self.get_list(url)
|
||||
return{'list':videos,'page':pg,'pagecount':9999,'limit':90,'total':999999}
|
||||
def fetch_and_decrypt_image(self,url):
|
||||
try:
|
||||
if url.startswith('//'):url='https:'+url
|
||||
elif url.startswith('/'):url='https://heiliao.com'+url
|
||||
r=requests.get(url,headers={'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.7049.96 Safari/537.36','Referer':'https://heiliao.com/'},timeout=15,verify=False)
|
||||
if r.status_code!=200:return b''
|
||||
return AES.new(b'f5d965df75336270',AES.MODE_CBC,b'97b60394abc2fbe1').decrypt(r.content)
|
||||
except: return b''
|
||||
def _extract_img_from_onload(self,node):
|
||||
try:
|
||||
m=re.search(r"load(?:Share)?Img\s*\([^,]+,\s*['\"]([^'\"]+)['\"]",(node.attr('onload')or''))
|
||||
return m.group(1)if m else''
|
||||
except:return''
|
||||
def _should_decrypt(self,url:str)->bool:
|
||||
u=(url or'').lower();return any(x in u for x in['pic.gylhaa.cn','new.slfpld.cn','/upload_01/','/upload/'])
|
||||
def _abs(self,u:str)->str:
|
||||
if not u:return''
|
||||
if u.startswith('//'):return'https:'+u
|
||||
if u.startswith('/'):return'https://heiliao.com'+u
|
||||
return u
|
||||
def e64(self,s:str)->str:
|
||||
try:return base64.b64encode((s or'').encode()).decode()
|
||||
except:return''
|
||||
def d64(self,s:str)->str:
|
||||
try:return base64.b64decode((s or'').encode()).decode()
|
||||
except:return''
|
||||
def _img(self,img_node):
|
||||
u=''if img_node is None else(img_node.attr('src')or img_node.attr('data-src')or'')
|
||||
enc=''if img_node is None else self._extract_img_from_onload(img_node)
|
||||
t=enc or u
|
||||
return f"{self.getProxyUrl()}&url={self.e64(t)}&type=hlimg"if t and(enc or self._should_decrypt(t))else self._abs(t)
|
||||
def _parse_items(self,root):
|
||||
vids=[]
|
||||
for sel in self.SELECTORS:
|
||||
for it in root(sel).items():
|
||||
title=it.find('.title, h3, h4, .video-title').text()
|
||||
if not title:continue
|
||||
link=it.find('a').attr('href')
|
||||
if not link:continue
|
||||
vids.append({'vod_id':self._abs(link),'vod_name':title,'vod_pic':self._img(it.find('img')),'vod_remarks':it.find('.date, .time, .remarks, .duration').text()or''})
|
||||
if vids:break
|
||||
return vids
|
||||
def detailContent(self,array):
|
||||
tid=array[0];url=tid if tid.startswith('http')else f'https://heiliao.com{tid}'
|
||||
rsp=self.fetch(url)
|
||||
if not rsp:return{'list':[]}
|
||||
rsp.encoding='utf-8';html_text=rsp.text
|
||||
try:root_text=pq(html_text)
|
||||
except:root_text=None
|
||||
try:root_content=pq(rsp.content)
|
||||
except:root_content=None
|
||||
title=(root_text('title').text()if root_text else'')or''
|
||||
if' - 黑料网'in title:title=title.replace(' - 黑料网','')
|
||||
pic=''
|
||||
if root_text:
|
||||
og=root_text('meta[property="og:image"]').attr('content')
|
||||
if og and(og.endswith('.png')or og.endswith('.jpg')or og.endswith('.jpeg')):pic=og
|
||||
else:pic=self._img(root_text('.video-item-img img'))
|
||||
detail=''
|
||||
if root_text:
|
||||
detail=root_text('meta[name="description"]').attr('content')or''
|
||||
if not detail:detail=root_text('.content').text()[:200]
|
||||
play_from,play_url=[],[]
|
||||
if root_content:
|
||||
for i,p in enumerate(root_content('.dplayer').items()):
|
||||
c=p.attr('config')
|
||||
if not c:continue
|
||||
try:s=(c.replace('"','"').replace('"','"').replace('&','&').replace('&','&').replace('<','<').replace('<','<').replace('>','>').replace('>','>'));u=(json.loads(s).get('video',{})or{}).get('url','')
|
||||
except:m=re.search(r'"url"\s*:\s*"([^"]+)"',c);u=m.group(1)if m else''
|
||||
if u:
|
||||
u=u.replace('\\/','/');u=self._abs(u)
|
||||
# Extract article ID for danmaku
|
||||
article_id = self._extract_article_id(tid)
|
||||
if article_id:
|
||||
play_from.append(f'视频{i+1}');play_url.append(f"{article_id}_dm_{u}")
|
||||
else:
|
||||
play_from.append(f'视频{i+1}');play_url.append(u)
|
||||
if not play_url:
|
||||
for pat in[r'https://hls\.[^"\']+\.m3u8[^"\']*',r'https://[^"\']+\.m3u8\?auth_key=[^"\']+',r'//hls\.[^"\']+\.m3u8[^"\']*']:
|
||||
for u in re.findall(pat,html_text):
|
||||
u=self._abs(u)
|
||||
article_id = self._extract_article_id(tid)
|
||||
if article_id:
|
||||
play_from.append(f'视频{len(play_from)+1}');play_url.append(f"{article_id}_dm_{u}")
|
||||
else:
|
||||
play_from.append(f'视频{len(play_from)+1}');play_url.append(u)
|
||||
if len(play_url)>=3:break
|
||||
if play_url:break
|
||||
if not play_url:
|
||||
js_patterns=[r'video[\s\S]{0,500}?url[\s"\'`:=]+([^"\'`\s]+)',r'videoUrl[\s"\'`:=]+([^"\'`\s]+)',r'src[\s"\'`:=]+([^"\'`\s]+\.m3u8[^"\'`\s]*)']
|
||||
for pattern in js_patterns:
|
||||
js_urls=re.findall(pattern,html_text)
|
||||
for js_url in js_urls:
|
||||
if'.m3u8'in js_url:
|
||||
if js_url.startswith('//'):js_url='https:'+js_url
|
||||
elif js_url.startswith('/'):js_url='https://heiliao.com'+js_url
|
||||
elif not js_url.startswith('http'):js_url='https://'+js_url
|
||||
article_id = self._extract_article_id(tid)
|
||||
if article_id:
|
||||
play_from.append(f'视频{len(play_from)+1}');play_url.append(f"{article_id}_dm_{js_url}")
|
||||
else:
|
||||
play_from.append(f'视频{len(play_from)+1}');play_url.append(js_url)
|
||||
if len(play_url)>=3:break
|
||||
if play_url:break
|
||||
if not play_url:
|
||||
article_id = self._extract_article_id(tid)
|
||||
example_url = "https://hls.obmoti.cn/videos5/b9699667fbbffcd464f8874395b91c81/b9699667fbbffcd464f8874395b91c81.m3u8?auth_key=1760372539-68ed273b94e7a-0-3a53bc0df110c5f149b7d374122ef1ed&v=2"
|
||||
if article_id:
|
||||
play_from.append('示例视频');play_url.append(f"{article_id}_dm_{example_url}")
|
||||
else:
|
||||
play_from.append('示例视频');play_url.append(example_url)
|
||||
return{'list':[{'vod_id':tid,'vod_name':title,'vod_pic':pic,'vod_content':detail,'vod_play_from':'$$$'.join(play_from),'vod_play_url':'$$$'.join(play_url)}]}
|
||||
def searchContent(self,key,quick,pg="1"):
|
||||
rsp=self.fetch(f'https://heiliao.com/index/search?word={key}')
|
||||
if not rsp:return{'list':[]}
|
||||
return{'list':self._parse_items(pq(rsp.text))}
|
||||
def playerContent(self,flag,id,vipFlags):
|
||||
# Check if this is a danmaku-enabled video
|
||||
if '_dm_' in id:
|
||||
aid, pid = id.split('_dm_', 1)
|
||||
p = 0 if re.search(r'\.(m3u8|mp4|flv|ts|mkv|mov|avi|webm)', pid) else 1
|
||||
if not p:
|
||||
pid = f"{self.getProxyUrl()}&pdid={quote(id)}&type=m3u8"
|
||||
return {'parse': p, 'url': pid, 'header': {"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.7049.96 Safari/537.36","Referer":"https://heiliao.com/"}}
|
||||
else:
|
||||
return{"parse":0,"playUrl":"","url":id,"header":{"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.7049.96 Safari/537.36","Referer":"https://heiliao.com/"}}
|
||||
def get_list(self,url):
|
||||
rsp=self.fetch(url)
|
||||
return[]if not rsp else self._parse_items(pq(rsp.text))
|
||||
def fetch(self,url,params=None,cookies=None,headers=None,timeout=5,verify=True,stream=False,allow_redirects=True):
|
||||
h=headers or{"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.7049.96 Safari/537.36","Referer":"https://heiliao.com/"}
|
||||
return super().fetch(url,params=params,cookies=cookies,headers=h,timeout=timeout,verify=verify,stream=stream,allow_redirects=allow_redirects)
|
||||
def localProxy(self,param):
|
||||
try:
|
||||
xtype = param.get('type', '')
|
||||
if xtype == 'hlimg':
|
||||
url=self.d64(param.get('url'))
|
||||
if url.startswith('//'):url='https:'+url
|
||||
elif url.startswith('/'):url='https://heiliao.com'+url
|
||||
r=requests.get(url,headers={"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.7049.96 Safari/537.36","Referer":"https://heiliao.com/"},timeout=15,verify=False)
|
||||
if r.status_code!=200:return[404,'text/plain','']
|
||||
b=AES.new(b'f5d965df75336270',AES.MODE_CBC,b'97b60394abc2fbe1').decrypt(r.content)
|
||||
ct='image/jpeg'
|
||||
if b.startswith(b'\x89PNG'):ct='image/png'
|
||||
elif b.startswith(b'GIF8'):ct='image/gif'
|
||||
return[200,ct,b]
|
||||
elif xtype == 'm3u8':
|
||||
# Handle danmaku-enabled video
|
||||
path, url = unquote(param['pdid']).split('_dm_', 1)
|
||||
data = requests.get(url, headers={"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.7049.96 Safari/537.36","Referer":"https://heiliao.com/"}, timeout=10).text
|
||||
lines = data.strip().split('\n')
|
||||
times = 0.0
|
||||
for i in lines:
|
||||
if i.startswith('#EXTINF:'):
|
||||
times += float(i.split(':')[-1].replace(',', ''))
|
||||
# Start background thread to refresh danmaku
|
||||
thread = threading.Thread(target=self.some_background_task, args=(path, int(times)))
|
||||
thread.start()
|
||||
print('[INFO] 获取视频时长成功', times)
|
||||
return [200, 'text/plain', data]
|
||||
elif xtype == 'hlxdm':
|
||||
# Return danmaku XML for heiliao comments
|
||||
article_id = param.get('path', '')
|
||||
times = int(param.get('times', 0))
|
||||
comments = self._fetch_heiliao_comments(article_id)
|
||||
return self._generate_danmaku_xml(comments, times)
|
||||
except Exception as e:
|
||||
print(f'[ERROR] localProxy: {e}')
|
||||
return[404,'text/plain','']
|
||||
|
||||
def _extract_article_id(self, url):
|
||||
"""Extract article ID from heiliao.com URL"""
|
||||
try:
|
||||
if '/archives/' in url:
|
||||
match = re.search(r'/archives/(\d+)/?', url)
|
||||
return match.group(1) if match else None
|
||||
return None
|
||||
except:
|
||||
return None
|
||||
|
||||
def _fetch_heiliao_comments(self, article_id, max_pages=3):
|
||||
"""Fetch comments from heiliao.com API"""
|
||||
comments = []
|
||||
try:
|
||||
for page in range(1, max_pages + 1):
|
||||
url = f"https://heiliao.com/comments/1/{article_id}/{page}.json"
|
||||
resp = requests.get(url, headers={"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.7049.96 Safari/537.36","Referer":"https://heiliao.com/"}, timeout=10)
|
||||
if resp.status_code == 200:
|
||||
data = resp.json()
|
||||
if 'data' in data and 'list' in data['data'] and data['data']['list']:
|
||||
for comment in data['data']['list']:
|
||||
text = comment.get('content', '').strip()
|
||||
if text and len(text) <= 100: # Filter out too long comments
|
||||
comments.append(text)
|
||||
# Also get replies from comments.list
|
||||
if 'comments' in comment and 'list' in comment['comments'] and comment['comments']['list']:
|
||||
for reply in comment['comments']['list']:
|
||||
reply_text = reply.get('content', '').strip()
|
||||
if reply_text and len(reply_text) <= 100:
|
||||
comments.append(reply_text)
|
||||
# Check if there are more pages
|
||||
if not data['data'].get('next', False):
|
||||
break
|
||||
else:
|
||||
break # No more comments
|
||||
else:
|
||||
break
|
||||
except Exception as e:
|
||||
print(f'[ERROR] _fetch_heiliao_comments: {e}')
|
||||
return comments[:50] # Limit to 50 comments max
|
||||
|
||||
def _generate_danmaku_xml(self, comments, video_duration):
|
||||
"""Generate danmaku XML from comments"""
|
||||
try:
|
||||
total_comments = len(comments)
|
||||
tsrt = f'共有{total_comments}条弹幕来袭!!!'
|
||||
danmu_xml = f'<?xml version="1.0" encoding="UTF-8"?>\n<i>\n\t<chatserver>chat.heiliao.com</chatserver>\n\t<chatid>88888888</chatid>\n\t<mission>0</mission>\n\t<maxlimit>99999</maxlimit>\n\t<state>0</state>\n\t<real_name>0</real_name>\n\t<source>heiliao</source>\n'
|
||||
danmu_xml += f'\t<d p="0,5,25,16711680,0">{tsrt}</d>\n'
|
||||
|
||||
for i, comment in enumerate(comments):
|
||||
# Distribute comments across video duration
|
||||
base_time = (i / total_comments) * video_duration if total_comments > 0 else 0
|
||||
dm_time = base_time + random.uniform(-3, 3)
|
||||
dm_time = round(max(0, min(dm_time, video_duration)), 1)
|
||||
dm_color = self._get_danmaku_color()
|
||||
# Clean comment text
|
||||
dm_text = re.sub(r'[<>&\u0000\b]', '', comment)
|
||||
danmu_xml += f'\t<d p="{dm_time},1,25,{dm_color},0">{dm_text}</d>\n'
|
||||
|
||||
danmu_xml += '</i>'
|
||||
return [200, "text/xml", danmu_xml]
|
||||
except Exception as e:
|
||||
print(f'[ERROR] _generate_danmaku_xml: {e}')
|
||||
return [500, 'text/html', '']
|
||||
|
||||
def _get_danmaku_color(self):
|
||||
"""Get danmaku color (90% white, 10% random)"""
|
||||
if random.random() < 0.1:
|
||||
h = random.random()
|
||||
s = random.uniform(0.7, 1.0)
|
||||
v = random.uniform(0.8, 1.0)
|
||||
r, g, b = colorsys.hsv_to_rgb(h, s, v)
|
||||
r = int(r * 255)
|
||||
g = int(g * 255)
|
||||
b = int(b * 255)
|
||||
return str((r << 16) + (g << 8) + b)
|
||||
else:
|
||||
return '16777215' # White
|
||||
|
||||
def some_background_task(self, article_id, video_duration):
|
||||
"""Background task to refresh danmaku in FongMi"""
|
||||
try:
|
||||
time.sleep(1)
|
||||
danmaku_url = f"{self.getProxyUrl()}&path={quote(article_id)}×={video_duration}&type=hlxdm"
|
||||
self.fetch(f"http://127.0.0.1:9978/action?do=refresh&type=danmaku&path={quote(danmaku_url)}")
|
||||
print(f'[INFO] 弹幕刷新成功: {article_id}')
|
||||
except Exception as e:
|
||||
print(f'[ERROR] some_background_task: {e}')
|
||||
Reference in New Issue
Block a user