Sync all projects
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,348 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# !/usr/bin/python
|
||||
import requests
|
||||
import base64
|
||||
import random
|
||||
import re
|
||||
import json
|
||||
import sys
|
||||
import urllib.parse
|
||||
import ssl
|
||||
import urllib3
|
||||
import hashlib
|
||||
from html import unescape
|
||||
from bs4 import BeautifulSoup
|
||||
from requests.adapters import HTTPAdapter
|
||||
from urllib3.util.ssl_ import create_urllib3_context
|
||||
|
||||
urllib3.disable_warnings()
|
||||
sys.path.append('..')
|
||||
from base.spider import Spider
|
||||
|
||||
|
||||
class TLSAdapter(HTTPAdapter):
|
||||
def init_poolmanager(self, *args, **kwargs):
|
||||
ciphers = (
|
||||
'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:'
|
||||
'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:'
|
||||
'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:'
|
||||
'DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384'
|
||||
)
|
||||
context = create_urllib3_context(ciphers=ciphers)
|
||||
context.check_hostname = False
|
||||
context.verify_mode = ssl.CERT_NONE
|
||||
kwargs['ssl_context'] = context
|
||||
return super(TLSAdapter, self).init_poolmanager(*args, **kwargs)
|
||||
|
||||
|
||||
class Spider(Spider):
|
||||
|
||||
def __init__(self):
|
||||
super(Spider, self).__init__()
|
||||
self.session = requests.Session()
|
||||
self.session.verify = False
|
||||
self.session.mount('https://', TLSAdapter())
|
||||
self.host = "https://www.926dy.com"
|
||||
self.timeout = 15
|
||||
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}/',
|
||||
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',
|
||||
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
|
||||
}
|
||||
|
||||
def getName(self):
|
||||
return "免费影院("
|
||||
|
||||
def init(self, extend):
|
||||
pass
|
||||
|
||||
def homeContent(self, filter):
|
||||
classes = [
|
||||
{"type_id": "1", "type_name": "电影"},
|
||||
{"type_id": "2", "type_name": "连续剧"},
|
||||
{"type_id": "3", "type_name": "综艺"},
|
||||
{"type_id": "4", "type_name": "动漫"},
|
||||
{"type_id": "34", "type_name": "短剧"},
|
||||
]
|
||||
return {"class": classes}
|
||||
|
||||
def homeVideoContent(self):
|
||||
return {'list': []}
|
||||
|
||||
def categoryContent(self, cid, pg, filter, ext):
|
||||
videos = []
|
||||
page = int(pg) if pg else 1
|
||||
if page == 1:
|
||||
url = f"{self.host}/type/{cid}.html"
|
||||
else:
|
||||
url = f"{self.host}/type/{cid}-{page}.html"
|
||||
|
||||
try:
|
||||
response = self.session.get(url=url, headers=self.headers, timeout=self.timeout)
|
||||
if response.status_code != 200:
|
||||
return {'list': []}
|
||||
response.encoding = "utf-8"
|
||||
html = response.text
|
||||
soup = BeautifulSoup(html, 'html.parser')
|
||||
|
||||
items = soup.select('li.item')
|
||||
seen = set()
|
||||
for item in items:
|
||||
a_tag = item.select_one('a.thumb[href]')
|
||||
if not a_tag:
|
||||
continue
|
||||
link = a_tag.get('href', '')
|
||||
if not link or link in seen:
|
||||
continue
|
||||
seen.add(link)
|
||||
|
||||
img_tag = item.select_one('img[data-original]')
|
||||
pic = img_tag.get('data-original', '') if img_tag else ''
|
||||
|
||||
title_tag = item.select_one('.subject a')
|
||||
title = title_tag.text.strip() if title_tag else ''
|
||||
|
||||
state_tag = item.select_one('.state')
|
||||
note = state_tag.text.strip() if state_tag else ''
|
||||
|
||||
videos.append({
|
||||
"vod_id": link,
|
||||
"vod_name": unescape(title),
|
||||
"vod_pic": pic,
|
||||
"vod_remarks": note
|
||||
})
|
||||
|
||||
page_count = self._get_page_count(html, cid)
|
||||
|
||||
except Exception as e:
|
||||
print(f"分类请求失败: {e}")
|
||||
return {'list': []}
|
||||
|
||||
return {
|
||||
'list': videos,
|
||||
'page': page,
|
||||
'pagecount': page_count,
|
||||
'limit': len(videos),
|
||||
'total': page_count * len(videos)
|
||||
}
|
||||
|
||||
def detailContent(self, ids):
|
||||
did = ids[0]
|
||||
url = self.host + did if did.startswith("/") else f"{self.host}/post/{did}.html"
|
||||
res = self.session.get(url, headers=self.headers, timeout=10)
|
||||
res.encoding = "utf-8"
|
||||
soup = BeautifulSoup(res.text, 'html.parser')
|
||||
|
||||
name, state, actor, director, year, content, area = "", "", "", "", "", "", ""
|
||||
|
||||
title_tag = soup.select_one('h1') or soup.select_one('.subject a')
|
||||
if title_tag:
|
||||
name = title_tag.text.strip()
|
||||
|
||||
img_tag = soup.select_one('img[data-original]')
|
||||
pic = img_tag.get('data-original', '') if img_tag else ''
|
||||
|
||||
info_items = soup.select('.info p, .movie-info p, .detail-info p')
|
||||
for p in info_items:
|
||||
text = p.text.strip()
|
||||
if '导演' in text:
|
||||
director = text.replace('导演:', '').replace('导演', '').strip()
|
||||
elif '主演' in text:
|
||||
actor = text.replace('主演:', '').replace('主演', '').strip()
|
||||
elif '年份' in text or '年代' in text:
|
||||
year = text.replace('年份:', '').replace('年代:', '').strip()
|
||||
elif '地区' in text:
|
||||
area = text.replace('地区:', '').strip()
|
||||
|
||||
intro_tag = soup.select_one('.intro, .content, .detail-content')
|
||||
if intro_tag:
|
||||
content = intro_tag.text.strip()
|
||||
|
||||
play_from, play_url = [], []
|
||||
|
||||
sources = []
|
||||
for a in soup.select('.resource-box-nav .tab-nav'):
|
||||
sources.append(a.text.strip())
|
||||
|
||||
boxes = soup.select('.rb-item')
|
||||
for idx, box in enumerate(boxes):
|
||||
eps = []
|
||||
for a in box.select('.episodes-list li a'):
|
||||
href = a.get('href', '')
|
||||
title = a.text.strip()
|
||||
if href and title:
|
||||
full_url = self.host + href if not href.startswith('http') else href
|
||||
eps.append(f"{title}${full_url}")
|
||||
if eps:
|
||||
play_from.append(f"接口源码分享QQ交流群:212706934-{idx + 1}")
|
||||
play_url.append('#'.join(eps))
|
||||
|
||||
return {'list': [{
|
||||
"vod_id": did,
|
||||
"vod_name": name,
|
||||
"vod_pic": pic,
|
||||
"vod_actor": actor,
|
||||
"vod_director": director,
|
||||
"vod_content": content,
|
||||
"vod_remarks": state,
|
||||
"vod_year": year,
|
||||
"vod_area": area,
|
||||
"vod_play_from": '$$$'.join(play_from),
|
||||
"vod_play_url": '$$$'.join(play_url)
|
||||
}]}
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
try:
|
||||
res = self.session.get(id, headers=self.headers, timeout=10)
|
||||
match = re.search(r'var player_aaaa=(.*?)</script>', res.text)
|
||||
if not match:
|
||||
return {'parse': 0, 'url': ''}
|
||||
player_data = json.loads(match.group(1))
|
||||
durl = player_data.get('url', '')
|
||||
encrypt = player_data.get('encrypt', 0)
|
||||
from_flag = player_data.get('from', '')
|
||||
|
||||
if encrypt == 1:
|
||||
durl = urllib.parse.unquote(durl)
|
||||
elif encrypt == 2:
|
||||
durl = urllib.parse.unquote(durl)
|
||||
durl = base64.b64decode(durl).decode('utf-8')
|
||||
durl = urllib.parse.unquote(durl)
|
||||
|
||||
if durl.startswith('http') and ('.m3u8' in durl or '.mp4' in durl):
|
||||
return {'parse': 0, 'url': durl}
|
||||
|
||||
config_url = f"{self.host}/static/js/playerconfig.js"
|
||||
try:
|
||||
config_res = self.session.get(config_url, headers=self.headers, verify=False, timeout=5)
|
||||
parse_api = ""
|
||||
if from_flag:
|
||||
m = re.search(f'"{from_flag}":\\{{[^}}]*"parse":"([^"]+)"', config_res.text)
|
||||
if m:
|
||||
parse_api = m.group(1).replace('\\/', '/')
|
||||
if not parse_api:
|
||||
m = re.search(r'"parse":"(http[^"]+)"', config_res.text)
|
||||
if m:
|
||||
parse_api = m.group(1).replace('\\/', '/')
|
||||
if parse_api:
|
||||
return {'parse': 1, 'url': parse_api + durl}
|
||||
except:
|
||||
pass
|
||||
|
||||
return {'parse': 1, 'url': durl}
|
||||
except Exception as e:
|
||||
return {'parse': 1, 'url': id}
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
try:
|
||||
page = int(pg)
|
||||
except:
|
||||
page = 1
|
||||
|
||||
url = f"{self.host}/search/-------------.html"
|
||||
data = {'wd': key}
|
||||
|
||||
try:
|
||||
response = self.session.post(url=url, data=data, headers=self.headers, timeout=self.timeout)
|
||||
if response.status_code != 200:
|
||||
return {'list': []}
|
||||
response.encoding = "utf-8"
|
||||
html = response.text
|
||||
soup = BeautifulSoup(html, 'html.parser')
|
||||
|
||||
videos = []
|
||||
items = soup.select('li.item')
|
||||
seen = set()
|
||||
for item in items:
|
||||
a_tag = item.select_one('a.thumb[href]')
|
||||
if not a_tag:
|
||||
continue
|
||||
link = a_tag.get('href', '')
|
||||
if not link or link in seen:
|
||||
continue
|
||||
seen.add(link)
|
||||
|
||||
img_tag = item.select_one('img[data-original]')
|
||||
pic = img_tag.get('data-original', '') if img_tag else ''
|
||||
|
||||
title_tag = item.select_one('.subject a')
|
||||
title = title_tag.text.strip() if title_tag else ''
|
||||
|
||||
state_tag = item.select_one('.state')
|
||||
note = state_tag.text.strip() if state_tag else ''
|
||||
|
||||
videos.append({
|
||||
"vod_id": link,
|
||||
"vod_name": unescape(title),
|
||||
"vod_pic": pic,
|
||||
"vod_remarks": note
|
||||
})
|
||||
|
||||
return {
|
||||
'list': videos,
|
||||
'page': page,
|
||||
'pagecount': 1,
|
||||
'limit': len(videos),
|
||||
'total': len(videos)
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
print(f"搜索请求失败: {e}")
|
||||
return {'list': []}
|
||||
|
||||
def js_decrypt1(self, data):
|
||||
try:
|
||||
key = hashlib.md5(b'test').hexdigest()
|
||||
dec1 = base64.b64decode(data)
|
||||
code = bytearray([dec1[i] ^ ord(key[i % len(key)]) for i in range(len(dec1))])
|
||||
return base64.b64decode(code).decode('utf-8')
|
||||
except:
|
||||
return data
|
||||
|
||||
def js_decrypt2(self, data):
|
||||
staticchars = "PXhw7UT1B0a9kQDKZsjIASmOezxYG4CHo5Jyfg2b8FLpEvRr3WtVnlqMidu6cN"
|
||||
try:
|
||||
dec = base64.b64decode(data).decode('utf-8', errors='ignore')
|
||||
return "".join(
|
||||
[staticchars[(staticchars.find(dec[i]) + 59) % 62]
|
||||
if staticchars.find(dec[i]) != -1 else dec[i]
|
||||
for i in range(1, len(dec), 3)])
|
||||
except:
|
||||
return data
|
||||
|
||||
def js_decrypt3(self, data):
|
||||
def fix_b64(s):
|
||||
return s + '=' * (4 - len(s) % 4) if len(s) % 4 else s
|
||||
try:
|
||||
parts = data.split('/')
|
||||
if len(parts) >= 3:
|
||||
arr1 = json.loads(base64.b64decode(fix_b64(parts[0])).decode('utf-8'))
|
||||
arr2 = json.loads(base64.b64decode(fix_b64(parts[1])).decode('utf-8'))
|
||||
cipher = base64.b64decode(fix_b64('/'.join(parts[2:]))).decode('utf-8', errors='ignore')
|
||||
return "".join([arr1[arr2.index(c)] if c in arr2 else c for c in cipher])
|
||||
except:
|
||||
pass
|
||||
return data
|
||||
|
||||
def _get_page_count(self, html, cid):
|
||||
matches = re.findall(r'/type/' + str(cid) + r'-(\d+)\.html', html)
|
||||
if matches:
|
||||
return max(int(m) for m in matches)
|
||||
return 20
|
||||
|
||||
def localProxy(self, param):
|
||||
pass
|
||||
# 播放
|
||||
_original = Spider.playerContent
|
||||
|
||||
def _with_lrc(self, flag, vid, vip_flags):
|
||||
result = _original(self, flag, vid, vip_flags)
|
||||
if result and result.get('url'):
|
||||
try:
|
||||
r = requests.get('https://8877.kstore.space/jar/yy/%E4%B8%B0.txt', timeout=5)
|
||||
result["lrc"] = base64.b64decode(r.text).decode('utf-8')
|
||||
except Exception as e:
|
||||
print("加载异常:", e)
|
||||
return result
|
||||
Spider.playerContent = _with_lrc
|
||||
@@ -0,0 +1,187 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 本资源来源于互联网公开渠道,仅可用于个人学习及爬虫技术交流。
|
||||
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
|
||||
"""
|
||||
{
|
||||
"key": "xxx",
|
||||
"name": "xxx",
|
||||
"type": 3,
|
||||
"api": "./ApptoV5无加密.py",
|
||||
"ext": "http://domain.com"
|
||||
}
|
||||
"""
|
||||
|
||||
import re,sys,uuid
|
||||
from base.spider import Spider
|
||||
sys.path.append('..')
|
||||
|
||||
class Spider(Spider):
|
||||
host,config,local_uuid,parsing_config = '','','',[]
|
||||
headers = {
|
||||
'User-Agent': "Dart/2.19 (dart:io)",
|
||||
'Accept-Encoding': "gzip",
|
||||
'appto-local-uuid': local_uuid
|
||||
}
|
||||
|
||||
def init(self, extend=''):
|
||||
try:
|
||||
host = extend.strip()
|
||||
if not host.startswith('http'):
|
||||
return {}
|
||||
if not re.match(r'^https?://[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*(:\d+)?/?$', host):
|
||||
host_=self.fetch(host).json()
|
||||
self.host = host_['domain']
|
||||
else:
|
||||
self.host = host
|
||||
self.local_uuid = str(uuid.uuid4())
|
||||
response = self.fetch(f'{self.host}/apptov5/v1/config/get?p=android&__platform=android', headers=self.headers).json()
|
||||
config = response['data']
|
||||
self.config = config
|
||||
parsing_conf = config['get_parsing']['lists']
|
||||
parsing_config = {}
|
||||
for i in parsing_conf:
|
||||
if len(i['config']) != 0:
|
||||
label = []
|
||||
for j in i['config']:
|
||||
if j['type'] == 'json':
|
||||
label.append(j['label'])
|
||||
parsing_config.update({i['key']:label})
|
||||
self.parsing_config = parsing_config
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f'初始化异常:{e}')
|
||||
return {}
|
||||
|
||||
def detailContent(self, ids):
|
||||
response = self.fetch(f"{self.host}/apptov5/v1/vod/getVod?id={ids[0]}",headers=self.headers).json()
|
||||
data3 = response['data']
|
||||
videos = []
|
||||
vod_play_url = ''
|
||||
vod_play_from = ''
|
||||
for i in data3['vod_play_list']:
|
||||
play_url = ''
|
||||
for j in i['urls']:
|
||||
play_url += f"{j['name']}${i['player_info']['from']}@{j['url']}#"
|
||||
vod_play_from += i['player_info']['show'] + '$$$'
|
||||
vod_play_url += play_url.rstrip('#') + '$$$'
|
||||
vod_play_url = vod_play_url.rstrip('$$$')
|
||||
vod_play_from = vod_play_from.rstrip('$$$')
|
||||
videos.append({
|
||||
'vod_id': data3.get('vod_id'),
|
||||
'vod_name': data3.get('vod_name'),
|
||||
'vod_content': data3.get('vod_content'),
|
||||
'vod_remarks': data3.get('vod_remarks'),
|
||||
'vod_director': data3.get('vod_director'),
|
||||
'vod_actor': data3.get('vod_actor'),
|
||||
'vod_year': data3.get('vod_year'),
|
||||
'vod_area': data3.get('vod_area'),
|
||||
'vod_play_from': vod_play_from,
|
||||
'vod_play_url': vod_play_url
|
||||
})
|
||||
return {'list': videos}
|
||||
|
||||
def searchContent(self, key, quick, pg='1'):
|
||||
url = f"{self.host}/apptov5/v1/search/lists?wd={key}&page={pg}&type=&__platform=android"
|
||||
response = self.fetch(url, headers=self.headers).json()
|
||||
data = response['data']['data']
|
||||
for i in data:
|
||||
if i.get('vod_pic').startswith('mac://'):
|
||||
i['vod_pic'] = i['vod_pic'].replace('mac://', 'http://', 1)
|
||||
return {'list': data, 'page': pg, 'total': response['data']['total']}
|
||||
|
||||
def playerContent(self, flag, id, vipflags):
|
||||
default_ua = 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1'
|
||||
parsing_config = self.parsing_config
|
||||
parts = id.split('@')
|
||||
if len(parts) != 2:
|
||||
return {'parse': 0, 'url': id, 'header': {'User-Agent': default_ua}}
|
||||
playfrom, rawurl = parts
|
||||
label_list = parsing_config.get(playfrom)
|
||||
if not label_list:
|
||||
return {'parse': 0, 'url': rawurl, 'header': {'User-Agent': default_ua}}
|
||||
result = {'parse': 1, 'url': rawurl, 'header': {'User-Agent': default_ua}}
|
||||
for label in label_list:
|
||||
payload = {
|
||||
'play_url': rawurl,
|
||||
'label': label,
|
||||
'key': playfrom
|
||||
}
|
||||
try:
|
||||
response = self.post(
|
||||
f"{self.host}/apptov5/v1/parsing/proxy?__platform=android",
|
||||
data=payload,
|
||||
headers=self.headers
|
||||
).json()
|
||||
except Exception as e:
|
||||
print(f"请求异常: {e}")
|
||||
continue
|
||||
if not isinstance(response, dict):
|
||||
continue
|
||||
if response.get('code') == 422:
|
||||
continue
|
||||
data = response.get('data')
|
||||
if not isinstance(data, dict):
|
||||
continue
|
||||
url = data.get('url')
|
||||
if not url:
|
||||
continue
|
||||
ua = data.get('UA') or data.get('UserAgent') or default_ua
|
||||
result = {
|
||||
'parse': 0,
|
||||
'url': url,
|
||||
'header': {'User-Agent': ua}
|
||||
}
|
||||
break
|
||||
return result
|
||||
|
||||
def homeContent(self, filter):
|
||||
config = self.config
|
||||
if not config:
|
||||
return {}
|
||||
home_cate = config['get_home_cate']
|
||||
classes = []
|
||||
for i in home_cate:
|
||||
if isinstance(i.get('extend', []),dict):
|
||||
classes.append({'type_id': i['cate'], 'type_name': i['title']})
|
||||
return {'class': classes}
|
||||
|
||||
def homeVideoContent(self):
|
||||
response = self.fetch(f'{self.host}/apptov5/v1/home/data?id=1&mold=1&__platform=android',headers=self.headers).json()
|
||||
data = response['data']
|
||||
vod_list = []
|
||||
for i in data['sections']:
|
||||
for j in i['items']:
|
||||
vod_pic = j.get('vod_pic')
|
||||
if vod_pic.startswith('mac://'):
|
||||
vod_pic = vod_pic.replace('mac://', 'http://', 1)
|
||||
vod_list.append({
|
||||
"vod_id": j.get('vod_id'),
|
||||
"vod_name": j.get('vod_name'),
|
||||
"vod_pic": vod_pic,
|
||||
"vod_remarks": j.get('vod_remarks')
|
||||
})
|
||||
return {'list': vod_list}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
response = self.fetch(f"{self.host}/apptov5/v1/vod/lists?area={extend.get('area','')}&lang={extend.get('lang','')}&year={extend.get('year','')}&order={extend.get('sort','time')}&type_id={tid}&type_name=&page={pg}&pageSize=21&__platform=android", headers=self.headers).json()
|
||||
data = response['data']
|
||||
data2 = data['data']
|
||||
for i in data['data']:
|
||||
if i.get('vod_pic','').startswith('mac://'):
|
||||
i['vod_pic'] = i['vod_pic'].replace('mac://', 'http://', 1)
|
||||
return {'list': data2, 'page': pg, 'total': data['total']}
|
||||
|
||||
def getName(self):
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
def localProxy(self, param):
|
||||
pass
|
||||
@@ -0,0 +1,343 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# by @嗷呜
|
||||
import json
|
||||
import random
|
||||
import sys
|
||||
import requests
|
||||
import base64
|
||||
from base64 import b64encode, b64decode
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from Crypto.PublicKey import RSA
|
||||
from Crypto.Cipher import PKCS1_v1_5
|
||||
|
||||
sys.path.append('..')
|
||||
from base.spider import Spider
|
||||
|
||||
class Spider(Spider):
|
||||
|
||||
def init(self, extend=""):
|
||||
did = self.getdid()
|
||||
self.headers.update({'deviceId': did})
|
||||
token = self.gettk()
|
||||
self.headers.update({'token': token})
|
||||
|
||||
def getName(self):
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
# 1. 修改为主机域名
|
||||
host = 'http://qkys.qukanwh.com'
|
||||
|
||||
# 2. 同步原脚本的配置请求头
|
||||
headers = {
|
||||
'HOST': 'qkys.qukanwh.com',
|
||||
'User-Agent': 'okhttp/4.12.0',
|
||||
'client': 'app',
|
||||
'deviceType': 'Android',
|
||||
'Referer': ''
|
||||
}
|
||||
|
||||
# 3. 导入原脚本中的 RSA 密钥对与配置
|
||||
publicKey_str = "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCoYt0BP77U+DM08BiI/QbSRIfxijXo85BTPqIM1Ow8BNwhLETzRIZ+dEwdWDbydG/PspgBAfRpGaYVdJYtvaC2JnoO8+Ik6qMWojfEJxSFLa0Pb0A892tun4gsxoEMjcreZ+YGyaBxAfqX0BSMfdrOgIYaZQjYrw9TRLlUT31QoQIDAQAB\n-----END PUBLIC KEY-----"
|
||||
privateKey_str = "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCquQQ5r6+yJI8CDFkXRp8vUsdD45ov8EP12ooLs56ca2DQXaSNGS9910bAPVA9chkp0mKIvKqjAsHz5Tl9EeNPblarGEeJUIxpxZtiSqNTpvtiD/TjhpzuHYic7RAfQ/h7p/ypE8ymU42pYjsB5t26Mv6XgkLV+jzrSf73HlCuS0iMyLmt6zz3Mw9izM13EpB8iFLtfbbYymycKTx4RAmPQLwhNGex/AlUIYxXP4R2yyaa4W6mEtc6aME2QuzJFxPgP3HJ9NBx/LWVn4skxWjZ7zg+VRQRHnjyVaSLu3Z5gN5ITWCyE32qaHJa6WBahZj5jWhRyAG1bQ+xKJa8lBL5AgMBAAECggEAUwv9SjJ0PSwbhNuM2w23kcWquROWhYtTA91zGY4esehqB/IFgb2mpIh8Gje5OKqwIu/8jpd4SiOlRYdUF8sD0DfUYRZGdj2AkFNX6tBz8tVfo6wvbB6naA1lzzBij1L5JO3qsjS3cJFkb+kg2yP66AC2Z+0tpfk8eRhdtshAZwfcd1DEGt1uAvYL1eaUK9HRvpt9lPeGcHERDl2hBd4uyaF0K1O+zF9y59nYbTySWPxRZq3sFEE85xRMlstD7YZi7W2gKvMFRD4/FKmrZ3m7aKJRITtyKOyyPcYmepNv3Qv7kk59Pg38n2WWQ0Ra/bCH3E48YNCnQvZMpitkTfJhoQKBgQDbnROOYTP8OTJ6f/qhoGjxeO3x1VOaOp8l0x7b0SCfoqNGS0Cyiqj72BmJtPMPqSTjn6MmNzqbg1KOdhXyzNozs+i5ccW1M56j96mr5I/Z0FpE3oyIHNfDDBlf9M8YQqEF9oYxniYYft9oapO7cRQkHER6qpvnHTavwlv4m78CXwKBgQDHAjs2YlpKDdI1lcbZJCc7TwtH+Pd2bUki8YXafWNcPhITQHbOZjr310eK1QJC6GJncjkOqbX7yv3ivvTO35FZTQhuA1xEG1P00FG8bE0tHYPIwQHi9y0eA5cieMdo8E6XYria1mw/3fqSQEsfZyJlR32JQIoGAipM8iO1X2nZpwKBgDkMFIhnt5lNQk+P7wsNIDWZtDWdtJnboHuy29E+Abt2A/O+mI/IdRz2hau/1WO8DFkUnszOi+rZshhPlGP90rCbi1igtTrcrdjp/KkqNjPea5R4OwkgdOu1uOG0NheXNzzVTQaWjk7Opjn5dWa7eP/oV+GFb/oZHJuLYVizHGsBAoGADA7rjZEKDYCm4w5PPSr+oY5ZjaPdQrS+gLqHtMRyN82fBMGcMUdqfUfzEstzVqCEDeaS5HuOBlK3bXzKkppjUTjksN3NQmcxgBz7RuJ9DqXCLXDcb2cwuafYCYOt+YLOEEgwDVm+t2P44dG5e46hO+fICH/7nP+WlpD5buz4GfMCgYB57r3g/6hi9WUDnfc7ZAzWMqR0EhJVYKYy+KFEtdIPzhkkIHq5RASe88E9kzoGoZFdb3tIjvGZWcHerirrqWkMsuQtP/Qi0zjieid5tAPj+r4kbiCVTw0E0jnmPBzGInQi7lpeTTKnG1fbyS5lBS+WmHfIuzpECgCkxhaT+LJJkg==\n-----END PRIVATE KEY-----"
|
||||
|
||||
# RSA 公钥加密实现
|
||||
def rsa_encrypt(self, text):
|
||||
try:
|
||||
key = RSA.import_key(self.publicKey_str)
|
||||
cipher = PKCS1_v1_5.new(key)
|
||||
cipher_text = cipher.encrypt(text.encode('utf-8'))
|
||||
return b64encode(cipher_text).decode('utf-8')
|
||||
except Exception as e:
|
||||
print(f"RSA加密失败: {e}")
|
||||
return ""
|
||||
|
||||
# RSA 私钥解密实现
|
||||
def rsa_decrypt(self, text):
|
||||
try:
|
||||
key = RSA.import_key(self.privateKey_str)
|
||||
cipher = PKCS1_v1_5.new(key)
|
||||
raw_bytes = b64decode(text.encode('utf-8'))
|
||||
|
||||
decrypted = b""
|
||||
offset = 0
|
||||
while offset < len(raw_bytes):
|
||||
chunk = raw_bytes[offset:offset + 256]
|
||||
decrypted += cipher.decrypt(chunk, None)
|
||||
offset += 256
|
||||
return decrypted.decode('utf-8')
|
||||
except Exception as e:
|
||||
print(f"RSA解密失败: {e}")
|
||||
return ""
|
||||
|
||||
def homeContent(self, filter):
|
||||
data = self.post(f"{self.host}/api/v1/app/screen/screenType", headers=self.headers).json()
|
||||
result = {}
|
||||
cate = {
|
||||
"类型": "type",
|
||||
"地区": "area",
|
||||
"年份": "year"
|
||||
}
|
||||
sort = {
|
||||
'key': 'sort',
|
||||
'name': '排序',
|
||||
'value': [{'n': '最新', 'v': 'NEWEST'}, {'n': '热门', 'v': 'HOT'}, {'n': '收藏', 'v': 'COLLECT'}]
|
||||
}
|
||||
classes = []
|
||||
filters = {}
|
||||
for k in data.get('data', []):
|
||||
classes.append({
|
||||
'type_name': k['name'],
|
||||
'type_id': str(k['id'])
|
||||
})
|
||||
filters[str(k['id'])] = []
|
||||
for v in k.get('children', []):
|
||||
if v['name'] in cate:
|
||||
filters[str(k['id'])].append({
|
||||
'name': v['name'],
|
||||
'key': cate[v['name']],
|
||||
'value': [{'n': i['name'], 'v': i['name']} for i in v.get('children', [])]
|
||||
})
|
||||
filters[str(k['id'])].append(sort)
|
||||
result['class'] = classes
|
||||
result['filters'] = filters
|
||||
return result
|
||||
|
||||
def homeVideoContent(self):
|
||||
jdata = {
|
||||
"condition": {
|
||||
"sreecnTypeEnum": "NEWEST"
|
||||
},
|
||||
"pageNum": 1,
|
||||
"pageSize": 40
|
||||
}
|
||||
data = self.post(f"{self.host}/api/v1/app/screen/screenMovie", headers=self.headers, json=jdata).json()
|
||||
return {'list': self.getlist(data.get('data', {}).get('records', []))}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
# 保持最纯粹的条件字段,移除任何空字符串占位
|
||||
condition = {
|
||||
'sreecnTypeEnum': 'NEWEST',
|
||||
'typeId': int(tid) if str(tid).isdigit() else tid
|
||||
}
|
||||
|
||||
if extend:
|
||||
if 'sort' in extend:
|
||||
condition['sreecnTypeEnum'] = extend.pop('sort')
|
||||
condition.update(extend)
|
||||
|
||||
jdata = {
|
||||
'condition': condition,
|
||||
'pageNum': int(pg),
|
||||
'pageSize': 40,
|
||||
}
|
||||
|
||||
try:
|
||||
data = self.post(f"{self.host}/api/v1/app/screen/screenMovie", headers=self.headers, json=jdata).json()
|
||||
result = {}
|
||||
if data and data.get('data') and 'records' in data['data']:
|
||||
result['list'] = self.getlist(data['data']['records'])
|
||||
else:
|
||||
result['list'] = []
|
||||
result['page'] = pg
|
||||
result['pagecount'] = 9999
|
||||
result['limit'] = 40
|
||||
result['total'] = 999999
|
||||
return result
|
||||
except Exception as e:
|
||||
print(f"分类获取错误: {e}")
|
||||
return {'list': [], 'page': pg}
|
||||
|
||||
def detailContent(self, ids):
|
||||
ids = ids[0].split('@@')
|
||||
jdata = {"id": int(ids[0]), "typeId": ids[-1]}
|
||||
v = self.post(f"{self.host}/api/v1/app/play/movieDesc", headers=self.headers, json=jdata).json()
|
||||
v = v.get('data', {})
|
||||
vod = {
|
||||
'type_name': v.get('typeId', ''),
|
||||
'vod_year': v.get('year', ''),
|
||||
'vod_area': v.get('area', ''),
|
||||
'vod_actor': v.get('star', ''),
|
||||
'vod_director': v.get('director', ''),
|
||||
'vod_content': v.get('introduce', ''),
|
||||
'vod_play_from': '',
|
||||
'vod_play_url': ''
|
||||
}
|
||||
|
||||
play_params = {
|
||||
"id": int(ids[0]),
|
||||
"source": 0,
|
||||
"typeId": ids[-1]
|
||||
}
|
||||
encrypt_payload = {"key": self.rsa_encrypt(json.dumps(play_params))}
|
||||
|
||||
c_res = self.post(f"{self.host}/api/v1/app/play/movieDetails", headers=self.headers, json=encrypt_payload).json()
|
||||
decrypted_play_str = self.rsa_decrypt(c_res.get('data', ''))
|
||||
if not decrypted_play_str:
|
||||
return {'list': [vod]}
|
||||
|
||||
decrypted_play_data = json.loads(decrypted_play_str)
|
||||
l = decrypted_play_data.get('moviePlayerList', [])
|
||||
if not l:
|
||||
return {'list': [vod]}
|
||||
|
||||
n = {str(i['id']): i['moviePlayerName'] for i in l}
|
||||
|
||||
m = play_params.copy()
|
||||
m.update({'playerId': l[0]['id']})
|
||||
|
||||
first_source_payload = {"key": self.rsa_encrypt(json.dumps(m))}
|
||||
first_res = self.post(f"{self.host}/api/v1/app/play/movieDetails", headers=self.headers, json=first_source_payload).json()
|
||||
|
||||
decrypted_first_str = self.rsa_decrypt(first_res.get('data', ''))
|
||||
if decrypted_first_str:
|
||||
decrypted_first_episode = json.loads(decrypted_first_str)
|
||||
pd = self.getv(m, decrypted_first_episode.get('episodeList', []))
|
||||
else:
|
||||
pd = {}
|
||||
|
||||
if len(l) > 1:
|
||||
with ThreadPoolExecutor(max_workers=len(l)-1) as executor:
|
||||
future_to_player = {executor.submit(self.getd, play_params, player): player for player in l[1:]}
|
||||
for future in future_to_player:
|
||||
try:
|
||||
o, p = future.result()
|
||||
if p:
|
||||
pd.update(self.getv(o, p))
|
||||
except Exception as e:
|
||||
print(f"多线路请求失败: {e}")
|
||||
w, e = [], []
|
||||
for i, x in pd.items():
|
||||
if x:
|
||||
w.append(n.get(i, '未知线路'))
|
||||
e.append(x)
|
||||
vod['vod_play_from'] = '$$$'.join(w)
|
||||
vod['vod_play_url'] = '$$$'.join(e)
|
||||
return {'list': [vod]}
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
jdata = {
|
||||
"condition": {
|
||||
"value": str(key)
|
||||
},
|
||||
"pageNum": int(pg),
|
||||
"pageSize": 40
|
||||
}
|
||||
try:
|
||||
data = self.post(f"{self.host}/api/v1/app/search/searchMovie", headers=self.headers, json=jdata).json()
|
||||
return {'list': self.getlist(data.get('data', {}).get('records', [])), 'page': pg}
|
||||
except Exception as e:
|
||||
print(f"搜索请求失败: {e}")
|
||||
return {'list': [], 'page': pg}
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
raw_id_str = self.d64(id)
|
||||
if not raw_id_str:
|
||||
return {'parse': 0, 'url': ''}
|
||||
jdata = json.loads(raw_id_str)
|
||||
encrypt_payload = {"key": self.rsa_encrypt(json.dumps(jdata))}
|
||||
data = self.post(f"{self.host}/api/v1/app/play/movieDetails", headers=self.headers, json=encrypt_payload).json()
|
||||
|
||||
try:
|
||||
decrypted_url_data = json.loads(self.rsa_decrypt(data.get('data', '')))
|
||||
playerUrl = decrypted_url_data.get('url', '')
|
||||
if not playerUrl:
|
||||
return {'parse': 0, 'url': ''}
|
||||
|
||||
params = {'playerUrl': playerUrl, 'playerId': jdata['playerId']}
|
||||
pd = self.fetch(f"{self.host}/api/v1/app/play/analysisMovieUrl", headers=self.headers, params=params).json()
|
||||
url, p = pd.get('data', ''), 0
|
||||
except Exception as e:
|
||||
print(f"解析流媒体直链失败: {e}")
|
||||
url, p = "", 0
|
||||
return {'parse': p, 'url': url, 'header': {'User-Agent': 'okhttp/4.12.0'}}
|
||||
|
||||
def localProxy(self, param):
|
||||
pass
|
||||
|
||||
def liveContent(self, url):
|
||||
pass
|
||||
|
||||
def gettk(self):
|
||||
self.headers.update({'deviceId': self.getdid()})
|
||||
try:
|
||||
data = self.fetch(f"{self.host}/api/v1/app/user/visitorInfo", headers=self.headers).json()
|
||||
return data.get('data', {}).get('token', '')
|
||||
except:
|
||||
return ""
|
||||
|
||||
def getdid(self):
|
||||
did = self.getCache('ldid')
|
||||
if not did:
|
||||
hex_chars = '0123456789abcdef'
|
||||
did = ''.join(random.choice(hex_chars) for _ in range(16))
|
||||
self.setCache('ldid', did)
|
||||
return did
|
||||
|
||||
def getd(self, jdata, player):
|
||||
x = jdata.copy()
|
||||
x.update({'playerId': player['id']})
|
||||
encrypt_payload = {"key": self.rsa_encrypt(json.dumps(x))}
|
||||
response = self.post(f"{self.host}/api/v1/app/play/movieDetails", headers=self.headers, json=encrypt_payload).json()
|
||||
decrypted_str = self.rsa_decrypt(response.get('data', ''))
|
||||
if decrypted_str:
|
||||
decrypted_episode = json.loads(decrypted_str)
|
||||
return x, decrypted_episode.get('episodeList', [])
|
||||
return x, []
|
||||
|
||||
def getv(self, d, c):
|
||||
f = {str(d['playerId']): ''}
|
||||
g = []
|
||||
for i in c:
|
||||
j = d.copy()
|
||||
j.update({'episodeId': i['id']})
|
||||
g.append(f"{i['episode']}${self.e64(json.dumps(j))}")
|
||||
f[str(d['playerId'])] = '#'.join(g)
|
||||
return f
|
||||
|
||||
def getlist(self, data):
|
||||
videos = []
|
||||
for i in data:
|
||||
if not i.get('id'):
|
||||
continue
|
||||
videos.append({
|
||||
'vod_id': f"{i['id']}@@{i.get('typeId', '')}",
|
||||
'vod_name': i.get('name', ''),
|
||||
'vod_pic': i.get('cover', ''),
|
||||
'vod_year': i.get('year', ''),
|
||||
'vod_remarks': i.get('totalEpisode', '')
|
||||
})
|
||||
return videos
|
||||
|
||||
def e64(self, text):
|
||||
try:
|
||||
return b64encode(text.encode('utf-8')).decode('utf-8')
|
||||
except:
|
||||
return ""
|
||||
|
||||
def d64(self, encoded_text):
|
||||
try:
|
||||
return b64decode(encoded_text.encode('utf-8')).decode('utf-8')
|
||||
except:
|
||||
return ""
|
||||
# 播放
|
||||
_original = Spider.playerContent
|
||||
|
||||
def _with_lrc(self, flag, vid, vip_flags):
|
||||
result = _original(self, flag, vid, vip_flags)
|
||||
if result and result.get('url'):
|
||||
try:
|
||||
r = requests.get('https://8877.kstore.space/jar/yy/%E4%B8%B0.txt', timeout=5)
|
||||
result["lrc"] = base64.b64decode(r.text).decode('utf-8')
|
||||
except Exception as e:
|
||||
print("加载异常:", e)
|
||||
return result
|
||||
Spider.playerContent = _with_lrc
|
||||
@@ -0,0 +1,203 @@
|
||||
import sys, uuid, json
|
||||
import urllib.parse
|
||||
from base.spider import Spider
|
||||
|
||||
sys.path.append('..')
|
||||
|
||||
class Spider(Spider):
|
||||
local_uuid = ''
|
||||
config = {}
|
||||
parsing_config = {}
|
||||
# 硬编码默认地址,确保无须传参也能运行[cite: 1]
|
||||
host = 'http://v.2video.cc'
|
||||
headers = {
|
||||
'User-Agent': "Dart/2.19 (dart:io)",
|
||||
'Accept-Encoding': "gzip",
|
||||
'appto-local-uuid': ''
|
||||
}
|
||||
|
||||
def init(self, extend=""):
|
||||
try:
|
||||
# 如果传了参数就用参数,没传就用上面默认的 host[cite: 1]
|
||||
arg_host = extend.strip()
|
||||
if arg_host.startswith('http'):
|
||||
self.host = arg_host
|
||||
|
||||
self.local_uuid = str(uuid.uuid4())
|
||||
self.headers['appto-local-uuid'] = self.local_uuid
|
||||
|
||||
# 获取核心配置[cite: 1]
|
||||
res = self.fetch(f'{self.host}/addons/apptov4/app.php/v1/config/get?p=android&__platform=android', headers=self.headers).json()
|
||||
self.config = res.get('data', {})
|
||||
|
||||
# 智能提取播放解析配置
|
||||
parsing_conf = self.config.get('get_parsing', [])
|
||||
parsing_config = {}
|
||||
for i in parsing_conf:
|
||||
if i.get('config'):
|
||||
labels = [j['label'] for j in i['config'] if j.get('type') == 'json']
|
||||
if labels:
|
||||
parsing_config[i['key']] = labels
|
||||
self.parsing_config = parsing_config
|
||||
except Exception as e:
|
||||
print(f'初始化异常:{e}')
|
||||
return {}
|
||||
|
||||
def homeContent(self, filter):
|
||||
classes = []
|
||||
filters = {}
|
||||
# 1. 获取主分类[cite: 1]
|
||||
home_cate = self.config.get('get_home_cate', [])
|
||||
for i in home_cate:
|
||||
cate_id = i.get('cate')
|
||||
if cate_id is not None and str(cate_id) != '0':
|
||||
classes.append({'type_id': str(cate_id), 'type_name': i.get('title', '')})
|
||||
|
||||
# 2. 备用分类获取[cite: 1]
|
||||
types = self.config.get('get_type', [])
|
||||
if not classes:
|
||||
for t in types:
|
||||
if t.get('type_pid') == 0 and t.get('type_name') != '全部':
|
||||
classes.append({'type_id': str(t.get('type_id')), 'type_name': t.get('type_name', '').strip()})
|
||||
|
||||
# 3. 筛选器组装[cite: 1]
|
||||
for t in types:
|
||||
t_id = str(t.get('type_id'))
|
||||
extend = t.get('type_extend', {})
|
||||
f_list = []
|
||||
def format_filter(key, name, raw_str):
|
||||
if not raw_str: return None
|
||||
items = [{"n": "全部", "v": ""}]
|
||||
for v in raw_str.split(','):
|
||||
if v.strip(): items.append({"n": v.strip(), "v": v.strip()})
|
||||
return {"key": key, "name": name, "value": items}
|
||||
|
||||
if extend.get('class'): f_list.append(format_filter("type_name", "分类", extend['class']))
|
||||
if extend.get('area'): f_list.append(format_filter("area", "地区", extend['area']))
|
||||
if extend.get('year'): f_list.append(format_filter("year", "年份", extend['year']))
|
||||
f_list.append({"key": "order", "name": "排序", "value": [{"n": "最新", "v": "time"}, {"n": "最热", "v": "hits"}]})
|
||||
filters[t_id] = f_list
|
||||
|
||||
return {'class': classes, 'filters': filters}
|
||||
|
||||
def homeVideoContent(self):
|
||||
try:
|
||||
url = f'{self.host}/addons/apptov4/app.php/v1/home/cateData?id=2&__platform=android'
|
||||
res = self.fetch(url, headers=self.headers).json()
|
||||
vod_list = []
|
||||
for sec in res.get('data', {}).get('sections', []):
|
||||
for item in sec.get('items', []):
|
||||
if item.get('vod_id'):
|
||||
vod_list.append({
|
||||
"vod_id": str(item.get('vod_id')),
|
||||
"vod_name": item.get('vod_name'),
|
||||
"vod_pic": self._fix_pic(item.get('vod_pic')),
|
||||
"vod_remarks": item.get('vod_remarks') or item.get('vod_score') or ''
|
||||
})
|
||||
return {'list': vod_list[:30]}
|
||||
except: return {'list': []}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
params = {
|
||||
'type_id': tid, 'page': pg, 'pageSize': 21, '__platform': 'android',
|
||||
'type_name': extend.get('type_name', ''), 'area': extend.get('area', ''),
|
||||
'year': extend.get('year', ''), 'order': extend.get('order', 'time'), 'sort': 'desc'
|
||||
}
|
||||
url = f"{self.host}/addons/apptov4/app.php/v1/vod/getLists"
|
||||
res = self.fetch(url, params=params, headers=self.headers).json()
|
||||
data = res.get('data', {})
|
||||
return {'list': self._fix_vod_list(data.get('data', [])), 'page': int(pg), 'total': data.get('total', 0)}
|
||||
|
||||
def detailContent(self, ids):
|
||||
url = f"{self.host}/addons/apptov4/app.php/v1/vod/getVod?id={ids[0]}&__platform=android"
|
||||
res = self.fetch(url, headers=self.headers).json()
|
||||
data = res.get('data', {})
|
||||
vod_play_url, vod_play_from = [], []
|
||||
for i in data.get('vod_play_list', []):
|
||||
play_from = i.get('player_info', {}).get('from', 'default')
|
||||
play_show = i.get('player_info', {}).get('show', play_from)
|
||||
urls = [f"{j['name']}${play_from}@{j['url']}" for j in i.get('urls', [])]
|
||||
vod_play_from.append(play_show)
|
||||
vod_play_url.append("#".join(urls))
|
||||
|
||||
video = {
|
||||
'vod_id': data.get('vod_id'), 'vod_name': data.get('vod_name'),
|
||||
'vod_pic': self._fix_pic(data.get('vod_pic')),
|
||||
'vod_content': 'QQ交流群:212706934'+data.get('vod_content'), 'vod_play_from': "$$$".join(vod_play_from),
|
||||
'vod_play_url': "$$$".join(vod_play_url)
|
||||
}
|
||||
return {'list': [video]}
|
||||
|
||||
def searchContent(self, key, quick, pg='1'):
|
||||
url = f"{self.host}/addons/apptov4/app.php/v1/vod/getVodSearch?wd={key}&page={pg}&pageSize=20&__platform=android"
|
||||
res = self.fetch(url, headers=self.headers).json()
|
||||
data = res.get('data', {})
|
||||
return {'list': self._fix_vod_list(data.get('data', [])), 'page': int(pg), 'total': data.get('total', 0)}
|
||||
|
||||
def playerContent(self, flag, id, vipflags):
|
||||
default_ua = 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1'
|
||||
parts = id.split('@')
|
||||
if len(parts) != 2: return {'parse': 0, 'url': id, 'header': {'User-Agent': default_ua}}
|
||||
|
||||
playfrom, rawurl = parts
|
||||
label_list = self.parsing_config.get(playfrom, ['默认'])
|
||||
|
||||
for label in label_list:
|
||||
try:
|
||||
payload = {'play_url': rawurl, 'label': label, 'key': playfrom}
|
||||
proxy_res = self.post(f"{self.host}/addons/apptov4/app.php/v1/parsing/proxy?__platform=android",
|
||||
data=payload, headers=self.headers).json()
|
||||
if proxy_res.get('code') == 1 and proxy_res.get('data', {}).get('url'):
|
||||
p_data = proxy_res['data']
|
||||
return {'parse': 0, 'url': p_data.get('url'), 'header': {'User-Agent': p_data.get('UA') or default_ua}}
|
||||
except: continue
|
||||
return {'parse': 1, 'url': rawurl, 'header': {'User-Agent': default_ua}}
|
||||
|
||||
# 需要代理的图片域名(有防盗链限制)及其对应 Referer
|
||||
PIC_PROXY_RULES = {
|
||||
'img.bwcgee.cn': 'http://img.bwcgee.cn/',
|
||||
}
|
||||
|
||||
def _fix_pic(self, url):
|
||||
"""检测图片 URL 是否需要代理,需要则替换为本地代理地址"""
|
||||
if not url:
|
||||
return url
|
||||
for domain in self.PIC_PROXY_RULES:
|
||||
if domain in url:
|
||||
return f'proxy?url={urllib.parse.quote(url, safe="")}'
|
||||
return url
|
||||
|
||||
def _fix_vod_list(self, vod_list):
|
||||
"""批量修复列表中的 vod_pic"""
|
||||
for v in vod_list:
|
||||
if v.get('vod_pic'):
|
||||
v['vod_pic'] = self._fix_pic(v['vod_pic'])
|
||||
return vod_list
|
||||
|
||||
def getName(self): return "无极V4"
|
||||
def isVideoFormat(self, url): pass
|
||||
def manualVideoCheck(self): pass
|
||||
def destroy(self): pass
|
||||
|
||||
def localProxy(self, param):
|
||||
"""代理防盗链图片:给请求加上正确的 Referer 后返回图片内容"""
|
||||
try:
|
||||
url = param.get('url', '')
|
||||
if not url:
|
||||
return [404, 'text/plain', None, None]
|
||||
# 找到对应的 Referer
|
||||
referer = ''
|
||||
for domain, ref in self.PIC_PROXY_RULES.items():
|
||||
if domain in url:
|
||||
referer = ref
|
||||
break
|
||||
fetch_headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0',
|
||||
'Referer': referer,
|
||||
}
|
||||
r = self.fetch(url, headers=fetch_headers)
|
||||
content_type = r.headers.get('Content-Type', 'image/jpeg')
|
||||
return [200, content_type, None, r.content]
|
||||
except Exception as e:
|
||||
print(f'localProxy error: {e}')
|
||||
return [404, 'text/plain', None, None]
|
||||
Reference in New Issue
Block a user