Sync all projects

This commit is contained in:
github-actions[bot]
2026-07-21 12:12:30 +00:00
parent fe63b60457
commit 5ff66d7af7
17 changed files with 8157 additions and 7154 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+348
View File
@@ -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
+187
View File
@@ -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
+343
View File
@@ -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
+203
View File
@@ -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]