Sync all projects
This commit is contained in:
@@ -0,0 +1,263 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# by @嗷呜
|
||||
# 基于原作者 @嗷呜 版本修改,仅可用于个人学习用途
|
||||
|
||||
from base.spider import Spider
|
||||
from urllib.parse import urlparse, urlencode
|
||||
import re,sys,time,json,urllib3,hashlib,datetime
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
sys.path.append('..')
|
||||
|
||||
class Spider(Spider):
|
||||
headers,api,apisignkey,datasignkey = {'User-Agent': 'okhttp/4.12.0',},'', '' , ''
|
||||
|
||||
def init(self, extend=""):
|
||||
ext = extend.rstrip()
|
||||
if ext.startswith('http'):
|
||||
self.api = ext.rstrip('/')
|
||||
else:
|
||||
arr = json.loads(ext)
|
||||
self.api = arr['api'].rstrip('/')
|
||||
self.apisignkey = arr.get('apisignkey', '')
|
||||
if self.apisignkey:
|
||||
self.datasignkey = arr.get('datasignkey', '6QQNUsP3PkD2ajJCPCY8')
|
||||
|
||||
def homeContent(self, filter):
|
||||
if self.api.endswith('v1.vod'):
|
||||
path = '/types'
|
||||
if self.apisignkey and self.datasignkey:
|
||||
path = self.datasign(path)
|
||||
data = self.fetch(f"{self.api}{path}", headers=self.headers, verify=False).json()
|
||||
data = data['data']
|
||||
else:
|
||||
data = self.fetch(f"{self.api}/nav?token=", headers=self.headers, verify=False).json()
|
||||
keys = ["class", "area", "lang", "year", "letter", "by", "sort"]
|
||||
filters = {}
|
||||
classes = []
|
||||
for item in data.get('list',data.get('data',[])):
|
||||
has_non_empty_field = False
|
||||
jsontype_extend = item["type_extend"]
|
||||
classes.append({"type_name": item["type_name"], "type_id": item["type_id"]})
|
||||
for key in keys:
|
||||
if key in jsontype_extend and jsontype_extend[key].strip() != "":
|
||||
has_non_empty_field = True
|
||||
break
|
||||
if has_non_empty_field:
|
||||
filters[str(item["type_id"])] = []
|
||||
for dkey in jsontype_extend:
|
||||
if dkey in keys and jsontype_extend[dkey].strip() != "":
|
||||
values = jsontype_extend[dkey].split(",")
|
||||
value_array = [{"n": value.strip(), "v": value.strip()} for value in values if
|
||||
value.strip() != ""]
|
||||
filters[str(item["type_id"])].append({"key": dkey, "name": dkey, "value": value_array})
|
||||
result = {"class": classes, "filters": filters}
|
||||
return result
|
||||
|
||||
def homeVideoContent(self):
|
||||
if self.api.endswith('v1.vod'):
|
||||
path = '/vodPhbAll'
|
||||
if self.apisignkey and self.datasignkey:
|
||||
keytime = self.keytime()
|
||||
path += self.datasign(f'?apikey={self.apikey()}&keytime={keytime}',keytime)
|
||||
data = self.fetch(f"{self.api}{path}", headers=self.headers, verify=False).json()
|
||||
data = data['data']
|
||||
else:
|
||||
data = self.fetch(f"{self.api}/index_video?token=", headers=self.headers, verify=False).json()
|
||||
videos = []
|
||||
if self.api.endswith('v1.vod'):
|
||||
for item in data['list']: videos.extend(item['vod_list'])
|
||||
elif 'list' in data:
|
||||
for item in data['list']: videos.extend(item['vlist'])
|
||||
elif 'data' in data:
|
||||
for item in data['data']: videos.extend(item['vlist'])
|
||||
return {'list': videos}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
if self.api.endswith('v1.vod'):
|
||||
path = f"?type={tid}&class={extend.get('class', '')}&lang={extend.get('lang', '')}&area={extend.get('area', '')}&year={extend.get('year', '')}&by=&page={pg}&limit=9"
|
||||
if self.apisignkey and self.datasignkey:
|
||||
keytime = self.keytime()
|
||||
path = self.datasign(f'{path}&apikey={self.apikey()}&keytime={keytime}' ,keytime)
|
||||
data = self.fetch(f"{self.api}{path}", headers=self.headers, verify=False).json()
|
||||
data = data['data']
|
||||
else:
|
||||
params = {'tid': tid, 'class': extend.get('class', ''), 'area': extend.get('area', ''), 'lang': extend.get('lang', ''), 'year': extend.get('year', ''), 'limit': '18', 'pg': pg}
|
||||
data = self.fetch(f"{self.api}/video", params=params, headers=self.headers, verify=False).json()
|
||||
if 'data' in data:
|
||||
data = {'list':data['data']}
|
||||
return data
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
if self.api.endswith('v1.vod'):
|
||||
path = f"?page={pg}&limit=10&wd={key}"
|
||||
if self.apisignkey and self.datasignkey:
|
||||
keytime = self.keytime()
|
||||
path = self.datasign(f'{path}&apikey={self.apikey()}&keytime={keytime}',keytime)
|
||||
else:
|
||||
path = f"/search?text={key}&pg={pg}"
|
||||
data = self.fetch(f"{self.api}{path}", headers=self.headers, verify=False).json()
|
||||
data2 = data.get('list',data.get('data',[]))
|
||||
if 'type' in data2:
|
||||
for item in data2:
|
||||
item.pop('type', None)
|
||||
if not 'list' in data2:
|
||||
data2 = {'list': data2, 'page': pg}
|
||||
return data2
|
||||
|
||||
def detailContent(self, ids):
|
||||
if self.api.endswith('v1.vod'):
|
||||
path = f'/detail?vod_id={ids[0]}&rel_limit=10'
|
||||
if self.apisignkey and self.datasignkey:
|
||||
keytime = self.keytime()
|
||||
path = self.datasign(f'{path}&apikey={self.apikey()}&keytime={keytime}',keytime)
|
||||
data = self.fetch(f"{self.api}{path}", headers=self.headers, verify=False).json()
|
||||
else:
|
||||
data = self.fetch(f"{self.api}/video_detail?id={ids[0]}", headers=self.headers, verify=False).json()
|
||||
data = data['data']
|
||||
if 'vod_info' in data:
|
||||
data = data['vod_info']
|
||||
show = ''
|
||||
vod_play_url = ''
|
||||
if 'vod_url_with_player' in data:
|
||||
for i in data['vod_url_with_player']:
|
||||
show += i.get('name', '') + '$$$'
|
||||
parse_api = i.get('parse_api','')
|
||||
if parse_api and parse_api.startswith('http'):
|
||||
url = i.get('url','')
|
||||
if url:
|
||||
url2 = '#'.join([i+ '@' + parse_api for i in url.split('#')])
|
||||
vod_play_url += url2 + '$$$'
|
||||
else:
|
||||
vod_play_url += i.get('url','') + '$$$'
|
||||
data.pop('vod_url_with_player')
|
||||
if 'vod_play_list' in data:
|
||||
for i in data['vod_play_list']:
|
||||
parses = ''
|
||||
player_info = i['player_info']
|
||||
show += f"{player_info['show']}({i['from']})$$$"
|
||||
parse = player_info.get('parse','')
|
||||
parse2 = player_info.get('parse2','')
|
||||
if 'parse' in player_info and parse.startswith('http'):
|
||||
parses += parse + ','
|
||||
if 'parse2' in player_info and parse2.startswith('http') and parse2 != parse:
|
||||
parses += parse2
|
||||
parses = parses.rstrip(',')
|
||||
url = ''
|
||||
for j in i['urls']:
|
||||
if parse:
|
||||
url += f"{j['name']}${j['url']}@{parses}#"
|
||||
else:
|
||||
url += f"{j['name']}${j['url']}#"
|
||||
url = url.rstrip('#')
|
||||
vod_play_url += url + '$$$'
|
||||
if 'vod_play_list' in data:
|
||||
data.pop('vod_play_list')
|
||||
if 'rel_vods' in data:
|
||||
data.pop('rel_vods')
|
||||
if 'type' in data:
|
||||
data.pop('type')
|
||||
data['vod_play_from'] = show.rstrip('$$$')
|
||||
data['vod_play_url'] = vod_play_url.rstrip('$$$')
|
||||
return {'list': [data]}
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
video_pattern = re.compile(r'https?:\/\/.*\.(?:m3u8|mp4|flv)')
|
||||
jx, url, ua = 0, '', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36'
|
||||
if '@' in id:
|
||||
rawurl, jxapi = id.split('@', 1)
|
||||
if ',' in jxapi:
|
||||
jxapis = jxapi.split(',', 1)
|
||||
else:
|
||||
jxapis = [jxapi]
|
||||
for jxapi_ in jxapis:
|
||||
try:
|
||||
res = self.fetch(f"{jxapi_}{rawurl}", headers=self.headers, timeout=10, verify=False).json()
|
||||
url = res.get('url', '')
|
||||
if url.startswith('http'):
|
||||
jxua = res.get('ua')
|
||||
if jxua:
|
||||
ua = jxua
|
||||
except Exception:
|
||||
url = ''
|
||||
continue
|
||||
if url.startswith('http'):
|
||||
jx = 0
|
||||
else:
|
||||
url = rawurl
|
||||
jx = 0 if video_pattern.match(rawurl) else 1
|
||||
else:
|
||||
url = id
|
||||
jx = 0 if video_pattern.match(id) else 1
|
||||
if url.startswith('NBY'):
|
||||
jx, url = 0, ''
|
||||
return {'jx': jx, 'parse': 0, 'url': url, 'header': {'User-Agent': ua}}
|
||||
|
||||
def keytime(self):
|
||||
return str(int(datetime.datetime.now().timestamp()))
|
||||
|
||||
def md5(self, str):
|
||||
hash_obj = hashlib.md5()
|
||||
hash_obj.update(str.encode('utf-8'))
|
||||
return hash_obj.hexdigest()
|
||||
|
||||
def apikey(self):
|
||||
date = datetime.datetime.now()
|
||||
year = str(date.year)
|
||||
hour = str(date.hour)
|
||||
minute = str(date.minute)
|
||||
|
||||
if len(hour) < 2:
|
||||
hour = "0" + hour
|
||||
if len(minute) < 2:
|
||||
minute = "0" + minute
|
||||
|
||||
str_value = self.apisignkey
|
||||
sign_str = f"{year}:{hour}:{year}:{minute}:{str_value}"
|
||||
md5_hash = self.md5(sign_str)
|
||||
return md5_hash
|
||||
|
||||
def datasign(self, url='', timestamp=''):
|
||||
parsed_url = urlparse(url)
|
||||
query_params = self._parse_query_params(parsed_url.query)
|
||||
if not timestamp:
|
||||
timestamp = str(time.time())
|
||||
query_params["timestamp"] = timestamp
|
||||
sorted_params = sorted(query_params.items(), key=lambda x: x[0])
|
||||
sign = self._generate_signature(sorted_params)
|
||||
query_params["datasign"] = sign
|
||||
new_query = urlencode(query_params)
|
||||
new_url = parsed_url._replace(query=new_query).geturl()
|
||||
return new_url
|
||||
|
||||
def _parse_query_params(self, query_str):
|
||||
params = {}
|
||||
if not query_str:
|
||||
return params
|
||||
for param in query_str.split('&'):
|
||||
if '=' not in param:
|
||||
continue
|
||||
key, value = param.split('=', 1)
|
||||
if value:
|
||||
params[key] = value
|
||||
return params
|
||||
|
||||
def _generate_signature(self, sorted_params):
|
||||
param_str = '&'.join([f"{k}={v}" for k, v in sorted_params])
|
||||
raw_sign_str = f"{param_str}{self.datasignkey}"
|
||||
md5_hash = hashlib.md5(raw_sign_str.encode('utf-8')).hexdigest()
|
||||
return md5_hash
|
||||
|
||||
def localProxy(self, param):
|
||||
pass
|
||||
|
||||
def getName(self):
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
@@ -0,0 +1,254 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# by @嗷呜
|
||||
import base64
|
||||
import json
|
||||
import sys
|
||||
import time
|
||||
from base64 import b64decode, b64encode
|
||||
from Crypto.Cipher import AES, PKCS1_v1_5
|
||||
from Crypto.Hash import MD5
|
||||
from Crypto.PublicKey import RSA
|
||||
from Crypto.Util.Padding import unpad, pad
|
||||
sys.path.append('..')
|
||||
from base.spider import Spider
|
||||
|
||||
|
||||
class Spider(Spider):
|
||||
|
||||
def init(self, extend=""):
|
||||
self.did=self.getdid()
|
||||
pass
|
||||
|
||||
def getName(self):
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
host='http://60.204.242.79:8091'
|
||||
|
||||
def homeContent(self, filter):
|
||||
res = self.fetch(f'{self.host}/app/channel?top-level=true', headers=self.getheaders()).text
|
||||
data = self.getdata(res)
|
||||
result = {}
|
||||
classes = []
|
||||
filters = {}
|
||||
sortsn = ['最新','最热','高分']
|
||||
for k in data['data']:
|
||||
classes.append({
|
||||
'type_name': k['name'],
|
||||
'type_id': k['id']
|
||||
})
|
||||
filters[k['id']] = []
|
||||
k['sorts']=['addtime','hits','gold']
|
||||
for key,value in k.items():
|
||||
if type(value) == list:
|
||||
filters[k['id']].append({
|
||||
'name': key,
|
||||
'key': key,
|
||||
'value': [{'v': x,'n': x if key !='sorts' else sortsn[i]} for i,x in enumerate(value) if x]
|
||||
})
|
||||
result['class'] = classes
|
||||
result['filters'] = filters
|
||||
return result
|
||||
|
||||
def homeVideoContent(self):
|
||||
res=self.fetch(f'{self.host}/app/banners/0',headers=self.getheaders()).text
|
||||
data=self.getdata(res)
|
||||
videos=[]
|
||||
for i in data['data']:
|
||||
videos.append({
|
||||
'vod_id': i['vid'],
|
||||
'vod_name': i['vname'],
|
||||
'vod_pic': i['img'],
|
||||
'vod_remarks': i['continu']
|
||||
})
|
||||
return {'list':videos}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
params={'channel':tid,'type':extend.get('types',''),'area':extend.get('areas',''),'year':extend.get('years',''),'sort':extend.get('sorts','addtime'),'limit':'30','page':pg}
|
||||
data=self.fetch(f'{self.host}/app/video/list',params=params,headers=self.getheaders()).text
|
||||
data=self.getdata(data)
|
||||
videos=[]
|
||||
for i in data['data']['items']:
|
||||
videos.append({
|
||||
'vod_id': i.get('id'),
|
||||
'vod_name': i.get('name'),
|
||||
'vod_pic': i.get('pic'),
|
||||
'vod_year': i.get('year'),
|
||||
'vod_remarks': i.get('continu')
|
||||
})
|
||||
result = {}
|
||||
result['list'] = videos
|
||||
result['page'] = pg
|
||||
result['pagecount'] = 9999
|
||||
result['limit'] = 90
|
||||
result['total'] = 999999
|
||||
return result
|
||||
|
||||
def detailContent(self, ids):
|
||||
data=self.fetch(f'{self.host}/app/video/detail?id={ids[0]}',headers=self.getheaders()).text
|
||||
data=self.getdata(data)
|
||||
v=data['data']
|
||||
vod = {
|
||||
'type_name': v.get('type'),
|
||||
'vod_year': v.get('year'),
|
||||
'vod_area': v.get('area'),
|
||||
'vod_remarks': v.get('continu'),
|
||||
'vod_actor': v.get('actor'),
|
||||
'vod_director': v.get('director'),
|
||||
'vod_content': v.get('content'),
|
||||
'vod_play_from': '',
|
||||
'vod_play_url': ''
|
||||
}
|
||||
parts,names = [],[]
|
||||
for i in v['parts']:
|
||||
names.append(i['play_zh'])
|
||||
p=[]
|
||||
for j,x in enumerate(i['part']):
|
||||
params={'id':ids[0],'play':i['play'],'part':x}
|
||||
p.append(f'{x}${self.e64(json.dumps(params))}')
|
||||
parts.append('#'.join(p))
|
||||
vod['vod_play_from'] = '$$$'.join(names)
|
||||
vod['vod_play_url'] = '$$$'.join(parts)
|
||||
return {'list':[vod]}
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
params={'key':key,'limit':'25','page':pg}
|
||||
data=self.fetch(f'{self.host}/app/video/search',params=params,headers=self.getheaders()).text
|
||||
data=self.getdata(data)
|
||||
videos = []
|
||||
for i in data['data']['items']:
|
||||
videos.append({
|
||||
'vod_id': i.get('id'),
|
||||
'vod_name': i.get('name'),
|
||||
'vod_pic': i.get('pic'),
|
||||
'vod_year': i.get('year'),
|
||||
'vod_remarks': i.get('continu')
|
||||
})
|
||||
return {'list':videos,'page':pg}
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
params= json.loads(self.d64(id))
|
||||
data=self.fetch(f'{self.host}/app/video/play',params=params,headers=self.getheaders()).text
|
||||
data=self.getdata(data)
|
||||
urls=[]
|
||||
for i in data['data']:
|
||||
if i.get('url'):urls.extend([i['resolution'],i['url']])
|
||||
return {'parse': 0, 'url': urls, 'header': {'User-Agent': 'Dart/3.6 (dart:io)'}}
|
||||
|
||||
def liveContent(self, url):
|
||||
pass
|
||||
|
||||
def localProxy(self, param):
|
||||
pass
|
||||
|
||||
def getheaders(self):
|
||||
t=str(int(time.time() * 1000))
|
||||
stinf=f"3.0.0.2-{t}-Android-1.0.4.5-{self.did}"
|
||||
authentication=self.aes_encrypt(self.e64(stinf))
|
||||
headers = {
|
||||
'User-Agent': 'Dart/3.6 (dart:io)',
|
||||
'x-version': '2020-09-17',
|
||||
'appid': '4150439554430614',
|
||||
'ts': t,
|
||||
'authentication': authentication,
|
||||
'content-type': 'application/json; charset=utf-8',
|
||||
}
|
||||
return headers
|
||||
|
||||
def aes_encrypt(self, text):
|
||||
key = b'ziISjqkXPsGUMRNGyWigxDGtJbfTdcGv'
|
||||
iv = b'WonrnVkxeIxDcFbv'
|
||||
cipher = AES.new(key, AES.MODE_CBC, iv)
|
||||
ct_bytes = cipher.encrypt(pad(text.encode("utf-8"), AES.block_size))
|
||||
ct = b64encode(ct_bytes).decode("utf-8")
|
||||
return ct
|
||||
|
||||
def aes_decrypt(self, key,text):
|
||||
iv=key[::-1].encode("utf-8")
|
||||
key=key.encode("utf-8")
|
||||
cipher = AES.new(key, AES.MODE_CBC, iv)
|
||||
pt = unpad(cipher.decrypt(b64decode(text)), AES.block_size)
|
||||
return json.loads(pt.decode("utf-8"))
|
||||
|
||||
def rsa_decrypt(self, encrypted_data):
|
||||
try:
|
||||
private_key_string = '''-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEA5xpfniKIMYdjTytUBu5rsLbMtcCRW9B9DB78QEdf4wW5jO8r
|
||||
Mw7j+/mYk3ghi0xrxpjtHm1R2KgNT1b0akJCExTH7gBVcjVywpmXdNXbcuCGfVCK
|
||||
S6vYfMypmj5lNBgalCHe5AVc0ghhP3FG5j8Q5B7q00+tk4nT9nFsTmTeNcAKSH9h
|
||||
aM6a0fbiJ3eXbxEr2o8raAjck10act35t/MIUOkcrQjHx5E9Yvqgs3qbq4yDakaG
|
||||
4qfMAV4DAkkmdZ8N3fdEQ+rFJ67Spd4zzowj81+YO9wMUP2hNgfXmLOGLS5Lyi+x
|
||||
vrwwWZXAIRUkhdQEAYQlhGs8wV9P4bJnTzplewIDAQABAoIBAEnRzNUwZpybiIdT
|
||||
acXFBrUtzvoHhubzE955T04g/mn//CMeiogGq6BjO+9vIhfi01Jequ9bMBeqpoW/
|
||||
WtdOTtjVfH9zr9eJZxzt/skdPrnVKmCBB4vgWoiSv2I7qAwZ3vOOVioz5FBayOWB
|
||||
A4qsfnK/xXa2LtW/4usHk/b+lVRJZhHl3eKio2CnVBrgRb2DTx1GAwpvaRXp0oHm
|
||||
LXDEtngxN4/rh2irPKgaG/lgrCBISKUHtwtgytcpltsHMASMXIKAjZjNgCA98fA3
|
||||
te96U58wGHzQBQ5XtwTf0PiFEfJ7yOhgNRgCtiwsjGOhJFJFiiXYKzTef1GnVxPa
|
||||
wuPc0TECgYEA+KCts3ArkWLqWbi4bVDpekP71geEnQIklSAk3RRZ0eiC1pmmkuTh
|
||||
+q/4jOfoQHGuYCc8GvJqxQ8Y+aspPptbsAeRMSVovjQUvpRMqD0SWT8o3W2xGfqd
|
||||
0W4p14CIF7oXjMqQVeY468AYzxUdNsaulrp9Wnpa5njzE5D5WGDu0IcCgYEA7fSq
|
||||
kvz1oXjlljlskBwJ8gDB8j53PhuqV6Ori71G/qIGpYuOVjHSfPD/04a9T3M9olpk
|
||||
vlLOLn7GS7xa4pjugmp0EDdxBIJJtTHbbi4NL4ZoYg+vHkiemkjGLis4x5qRKjg6
|
||||
jNUEhnpksm68IUMSyO2toasfR0nVUmkb+ylKhG0CgYEAqNDZAJSyUHZcb21YdIlS
|
||||
7rzIe2wBZGZ3FnaL8T0HO9rnM/WCQA1/Tys61doFPfSylQEu85EUZBc7OxM33xW3
|
||||
7M9Gi5s+Ap/0Ue76GeXV1plnEuqPLPeZPwHREU1pmsq1gNhtppW6ooB9l+ZbPr0r
|
||||
AJdB1DRuEj2ftvJiC9tNbHMCgYEAvHaliply6hrYq6x7gX/TmKpk8bnrs3Mx7Qui
|
||||
WKDm09H8Na1cZIQ9U9uEo0H6OizpyeaSF/N5fXXHFEDwMrwxW3V4y0c96fZO7oW4
|
||||
Z4FtzBBGKDSH3BJkG4o7/GEbLWwMQUYbiWNFnETf8DqoIif/fshQVtUzhsDBhe3d
|
||||
zYUckdkCgYAJlTYhJz0qXcO8a5KsQ20/hEGRtOcq+mfPOdGYBOv6LB2ThuDKunbY
|
||||
WsmAvqSo1qoJONnhQVMSpzKWEjCYV6hcifV9aeFofD4kNmG1gWC18QIYfrihLyOU
|
||||
E4GDW7QN8HO2YiQpopGP/muKsIlCmxKP6DasgCCO36xs87Wi8gu1DA==
|
||||
-----END RSA PRIVATE KEY-----'''
|
||||
private_key = RSA.import_key(private_key_string)
|
||||
cipher = PKCS1_v1_5.new(private_key)
|
||||
encrypted_bytes = base64.b64decode(encrypted_data)
|
||||
decrypted_bytes = cipher.decrypt(encrypted_bytes, None)
|
||||
return decrypted_bytes.decode('utf-8')
|
||||
except:
|
||||
return ""
|
||||
|
||||
def getdata(self, data):
|
||||
ds=data.split('.')
|
||||
key=self.rsa_decrypt(ds[0])
|
||||
result=self.aes_decrypt(key,ds[1])
|
||||
return result
|
||||
|
||||
def getdid(self):
|
||||
did=self.getCache('did')
|
||||
if not did:
|
||||
t = str(int(time.time()))
|
||||
did = self.md5(t)
|
||||
self.setCache('did', did)
|
||||
return did
|
||||
|
||||
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 md5(self, text):
|
||||
h = MD5.new()
|
||||
h.update(text.encode('utf-8'))
|
||||
return h.hexdigest()
|
||||
|
||||
@@ -0,0 +1,249 @@
|
||||
import re
|
||||
import sys
|
||||
import threading
|
||||
import requests
|
||||
from Crypto.Hash import MD5
|
||||
sys.path.append("..")
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Util.Padding import pad, unpad
|
||||
from urllib.parse import quote, urlparse
|
||||
from base64 import b64encode, b64decode
|
||||
import json
|
||||
import time
|
||||
from base.spider import Spider
|
||||
|
||||
|
||||
class Spider(Spider):
|
||||
|
||||
def init(self, extend=""):
|
||||
self.host = self.gethost()
|
||||
self.did=self.getdid()
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def action(self, action):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
def homeContent(self, filter):
|
||||
data = self.getdata("/api.php/getappapi.index/initV119")
|
||||
dy = {"class": "类型", "area": "地区", "lang": "语言", "year": "年份", "letter": "字母", "by": "排序",
|
||||
"sort": "排序"}
|
||||
filters = {}
|
||||
classes = []
|
||||
json_data = data["type_list"]
|
||||
homedata = data["banner_list"][8:]
|
||||
for item in json_data:
|
||||
if item["type_name"] == "全部":
|
||||
continue
|
||||
has_non_empty_field = False
|
||||
jsontype_extend = json.loads(item["type_extend"])
|
||||
homedata.extend(item["recommend_list"])
|
||||
jsontype_extend["sort"] = "最新,最热,最赞"
|
||||
classes.append({"type_name": item["type_name"], "type_id": item["type_id"]})
|
||||
for key in dy:
|
||||
if key in jsontype_extend and jsontype_extend[key].strip() != "":
|
||||
has_non_empty_field = True
|
||||
break
|
||||
if has_non_empty_field:
|
||||
filters[str(item["type_id"])] = []
|
||||
for dkey in jsontype_extend:
|
||||
if dkey in dy and jsontype_extend[dkey].strip() != "":
|
||||
values = jsontype_extend[dkey].split(",")
|
||||
value_array = [{"n": value.strip(), "v": value.strip()} for value in values if
|
||||
value.strip() != ""]
|
||||
filters[str(item["type_id"])].append({"key": dkey, "name": dy[dkey], "value": value_array})
|
||||
result = {}
|
||||
result["class"] = classes
|
||||
result["filters"] = filters
|
||||
result["list"] = homedata[1:]
|
||||
return result
|
||||
|
||||
def homeVideoContent(self):
|
||||
pass
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
body = {"area": extend.get('area', '全部'), "year": extend.get('year', '全部'), "type_id": tid, "page": pg,
|
||||
"sort": extend.get('sort', '最新'), "lang": extend.get('lang', '全部'),
|
||||
"class": extend.get('class', '全部')}
|
||||
result = {}
|
||||
data = self.getdata("/api.php/getappapi.index/typeFilterVodList", body)
|
||||
result["list"] = data["recommend_list"]
|
||||
result["page"] = pg
|
||||
result["pagecount"] = 9999
|
||||
result["limit"] = 90
|
||||
result["total"] = 999999
|
||||
return result
|
||||
|
||||
def detailContent(self, ids):
|
||||
body = f"vod_id={ids[0]}"
|
||||
data = self.getdata("/api.php/getappapi.index/vodDetail", body)
|
||||
vod = data["vod"]
|
||||
play = []
|
||||
names = []
|
||||
for itt in data["vod_play_list"]:
|
||||
a = []
|
||||
names.append(itt["player_info"]["show"])
|
||||
for it in itt['urls']:
|
||||
it['user_agent'] = itt["player_info"].get("user_agent")
|
||||
it["parse"] = itt["player_info"].get("parse")
|
||||
a.append(f"{it['name']}${self.e64(json.dumps(it))}")
|
||||
play.append("#".join(a))
|
||||
vod["vod_play_from"] = "$$$".join(names)
|
||||
vod["vod_play_url"] = "$$$".join(play)
|
||||
result = {"list": [vod]}
|
||||
return result
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
body = f"keywords={key}&type_id=0&page={pg}"
|
||||
data = self.getdata("/api.php/getappapi.index/searchList", body)
|
||||
result = {"list": data["search_list"], "page": pg}
|
||||
return result
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
ids = json.loads(self.d64(id))
|
||||
h = {"User-Agent": (ids['user_agent'] or "okhttp/3.14.9")}
|
||||
try:
|
||||
if re.search(r'url=', ids['parse_api_url']):
|
||||
data = self.fetch(ids['parse_api_url'], headers=h, timeout=10).json()
|
||||
url = data.get('url') or data['data'].get('url')
|
||||
else:
|
||||
body = f"parse_api={ids.get('parse') or ids['parse_api_url'].replace(ids['url'], '')}&url={quote(self.aes(ids['url'], True))}&token={ids.get('token')}"
|
||||
b = self.getdata("/api.php/getappapi.index/vodParse", body)['json']
|
||||
url = json.loads(b)['url']
|
||||
if 'error' in url: raise ValueError(f"解析失败: {url}")
|
||||
p = 0
|
||||
except Exception as e:
|
||||
print('错误信息:', e)
|
||||
url, p = ids['url'], 1
|
||||
|
||||
if re.search(r'\.jpg|\.png|\.jpeg', url):
|
||||
url = self.Mproxy(url)
|
||||
result = {}
|
||||
result["parse"] = p
|
||||
result["url"] = url
|
||||
result["header"] = h
|
||||
return result
|
||||
|
||||
def localProxy(self, param):
|
||||
return self.Mlocal(param)
|
||||
|
||||
def gethost(self):
|
||||
headers = {
|
||||
'User-Agent': 'okhttp/3.14.9'
|
||||
}
|
||||
response = self.fetch('https://miget-1313189639.cos.ap-guangzhou.myqcloud.com/mifun.txt',headers=headers).text
|
||||
return self.host_late(response.split('\n'))
|
||||
|
||||
def host_late(self, url_list):
|
||||
if isinstance(url_list, str):
|
||||
urls = [u.strip() for u in url_list.split(',')]
|
||||
else:
|
||||
urls = url_list
|
||||
if len(urls) <= 1:
|
||||
return urls[0] if urls else ''
|
||||
|
||||
results = {}
|
||||
threads = []
|
||||
|
||||
def test_host(url):
|
||||
try:
|
||||
url = url.strip()
|
||||
start_time = time.time()
|
||||
response = requests.head(url, timeout=1.0, allow_redirects=False)
|
||||
delay = (time.time() - start_time) * 1000
|
||||
results[url] = delay
|
||||
except Exception as e:
|
||||
results[url] = float('inf')
|
||||
for url in urls:
|
||||
t = threading.Thread(target=test_host, args=(url,))
|
||||
threads.append(t)
|
||||
t.start()
|
||||
for t in threads:
|
||||
t.join()
|
||||
return min(results.items(), key=lambda x: x[1])[0]
|
||||
|
||||
def getdid(self):
|
||||
did=self.getCache('did')
|
||||
if not did:
|
||||
t = str(int(time.time()))
|
||||
did = self.md5(t)
|
||||
self.setCache('did', did)
|
||||
return did
|
||||
|
||||
def aes(self, text, b=None):
|
||||
key = b"GETMIFUNGEIMIFUN"
|
||||
cipher = AES.new(key, AES.MODE_CBC, key)
|
||||
if b:
|
||||
ct_bytes = cipher.encrypt(pad(text.encode("utf-8"), AES.block_size))
|
||||
ct = b64encode(ct_bytes).decode("utf-8")
|
||||
return ct
|
||||
else:
|
||||
pt = unpad(cipher.decrypt(b64decode(text)), AES.block_size)
|
||||
return pt.decode("utf-8")
|
||||
|
||||
def header(self):
|
||||
t = str(int(time.time()))
|
||||
header = {"Referer": self.host,
|
||||
"User-Agent": "okhttp/3.14.9", "app-version-code": "516", "app-ui-mode": "light",
|
||||
"app-api-verify-time": t, "app-user-device-id": self.did,
|
||||
"app-api-verify-sign": self.aes(t, True),
|
||||
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"}
|
||||
return header
|
||||
|
||||
def getdata(self, path, data=None):
|
||||
vdata = self.post(f"{self.host}{path}", headers=self.header(), data=data, timeout=10).json()['data']
|
||||
data1 = self.aes(vdata)
|
||||
return json.loads(data1)
|
||||
|
||||
def Mproxy(self, url):
|
||||
return f"{self.getProxyUrl()}&url={self.e64(url)}&type=m3u8"
|
||||
|
||||
def Mlocal(self, param, header=None):
|
||||
url = self.d64(param["url"])
|
||||
ydata = self.fetch(url, headers=header, allow_redirects=False)
|
||||
data = ydata.content.decode('utf-8')
|
||||
if ydata.headers.get('Location'):
|
||||
url = ydata.headers['Location']
|
||||
data = self.fetch(url, headers=header).content.decode('utf-8')
|
||||
parsed_url = urlparse(url)
|
||||
durl = parsed_url.scheme + "://" + parsed_url.netloc
|
||||
lines = data.strip().split('\n')
|
||||
for index, string in enumerate(lines):
|
||||
if '#EXT' not in string and 'http' not in string:
|
||||
last_slash_index = string.rfind('/')
|
||||
lpath = string[:last_slash_index + 1]
|
||||
lines[index] = durl + ('' if lpath.startswith('/') else '/') + lpath
|
||||
data = '\n'.join(lines)
|
||||
return [200, "application/vnd.apple.mpegur", data]
|
||||
|
||||
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 md5(self, text):
|
||||
h = MD5.new()
|
||||
h.update(text.encode('utf-8'))
|
||||
return h.hexdigest()
|
||||
|
||||
@@ -0,0 +1,263 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
|
||||
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
|
||||
|
||||
import re,sys,json,urllib3
|
||||
from base.spider import Spider
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
sys.path.append('..')
|
||||
|
||||
class Spider(Spider):
|
||||
headers,timeout,ver,uas,parses,custom_parses,host,froms,detail,custom_first,category,cms = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
|
||||
'Connection': 'Keep-Alive',
|
||||
'Accept-Encoding': 'gzip',
|
||||
'Accept-Language': 'zh-CN,zh;q=0.8',
|
||||
'Cache-Control': 'no-cache'
|
||||
},5,1,{},{},{},'','','','','',''
|
||||
|
||||
def init(self, extend=''):
|
||||
ext = extend.strip()
|
||||
if ext.startswith('http'):
|
||||
host = ext
|
||||
else:
|
||||
arr = json.loads(ext)
|
||||
host = arr['host']
|
||||
self.ver = 2 if arr.get('ver') == 2 else self.ver
|
||||
cms = arr.get('cms', '').rstrip('/')
|
||||
if re.match(r'^https?:\/\/.*\/vod', cms):
|
||||
if '?' in cms:
|
||||
cms += '&'
|
||||
else:
|
||||
cms += '?'
|
||||
self.cms = cms
|
||||
self.froms = arr.get('from', '')
|
||||
self.custom_parses = arr.get('parse', {})
|
||||
self.custom_first = arr.get('custom_first', 0)
|
||||
self.category = arr.get('category', 1)
|
||||
ua = arr.get('ua')
|
||||
if ua:
|
||||
if isinstance(ua,str):
|
||||
self.headers['User-Agent'] = ua
|
||||
elif isinstance(ua,dict):
|
||||
self.uas = {'host': ua.get('host'), 'config': ua.get('config'), 'home': ua.get('home'),
|
||||
'category': ua.get('category'),'search': ua.get('search'), 'parse': ua.get('parse'),
|
||||
'player': ua.get('player')}
|
||||
self.timeout = arr.get('timeout', 5)
|
||||
if not re.match(r'^https?:\/\/[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*(:\d+)?(\/)?$', host):
|
||||
headers = self.headers.copy()
|
||||
custom_ua = self.uas.get('host')
|
||||
if custom_ua: headers['User-Agent'] = custom_ua
|
||||
host_ = self.fetch(host, headers=headers, verify=False, timeout=self.timeout).text
|
||||
try:
|
||||
host_ = json.loads(host_)['apiDomain']
|
||||
except json.JSONDecodeError:
|
||||
host_ = host_.strip()
|
||||
if host_.startswith('http'): host = host_
|
||||
self.host = host.rstrip('/')
|
||||
|
||||
def homeContent(self, filter):
|
||||
if not self.host or self.category == 0: return None
|
||||
headers = self.headers.copy()
|
||||
custom_ua = self.uas.get('home')
|
||||
if custom_ua: headers['User-Agent'] = custom_ua
|
||||
if self.cms and not self.category == 2:
|
||||
class_url = self.cms.rstrip('&')
|
||||
class_url = class_url.replace('&ac=videolist','').replace('ac=videolist&','').replace('ac=videolist','')
|
||||
class_url = class_url.replace('ac=detail', 'ac=list')
|
||||
classes = self.fetch(f"{class_url}", headers=headers, verify=False, timeout=self.timeout).json()['class']
|
||||
data = self.fetch(f"{self.cms.strip('&')}", headers=headers, verify=False, timeout=self.timeout).json()
|
||||
data['class'] = classes
|
||||
if self.category == 2:
|
||||
for i in data.get('list',[]):
|
||||
i['vod_id'] = f"msearch:{i['vod_id']}"
|
||||
else:
|
||||
response = self.fetch(f'{self.host}/api.php/Appfox/init', headers=headers, verify=False, timeout=self.timeout).json()
|
||||
classes = []
|
||||
for i in response['data']['type_list']:
|
||||
classes.append({'type_id': i['type_id'],'type_name': i['type_name']})
|
||||
data = {'class': classes}
|
||||
return data
|
||||
|
||||
def homeVideoContent(self):
|
||||
if not self.host or self.category == 0: return None
|
||||
if self.cms and not self.category == 2: return None
|
||||
headers = self.headers.copy()
|
||||
custom_ua = self.uas.get('homeVideo',self.uas.get('home'))
|
||||
if custom_ua: headers['User-Agent'] = custom_ua
|
||||
if self.ver == 2:
|
||||
response = self.fetch(f'{self.host}/api.php/appfox/nav', headers=headers, verify=False, timeout=self.timeout).json()
|
||||
navigationId = ''
|
||||
for i in response['data']:
|
||||
if isinstance(i,dict):
|
||||
navigationId = i['navigationId']
|
||||
break
|
||||
if not navigationId: return None
|
||||
path = f'nav_video?id={navigationId}'
|
||||
else:
|
||||
path = 'index'
|
||||
response = self.fetch(f'{self.host}/api.php/Appfox/{path}', headers=headers, verify=False, timeout=self.timeout).json()
|
||||
data = response['data']
|
||||
videos = []
|
||||
for i in data:
|
||||
for j in i.get('banner', []):
|
||||
videos.append(j)
|
||||
for k in i.get('categories', []):
|
||||
for l in k.get('videos',[]):
|
||||
videos.append(l)
|
||||
if videos and self.category == 2:
|
||||
for i in videos:
|
||||
i['vod_id'] = f"msearch:{i['vod_id']}"
|
||||
return {'list': videos}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
if not self.host: return None
|
||||
headers = self.headers.copy()
|
||||
category_ua = self.uas.get('category')
|
||||
if category_ua: headers['User-Agent'] = category_ua
|
||||
if self.cms and not self.category == 2:
|
||||
data = self.fetch(f'{self.cms}pg={pg}&t={tid}', headers=headers, verify=False, timeout=self.timeout).json()
|
||||
else:
|
||||
response = self.fetch(f"{self.host}/api.php/Appfox/vodList?type_id={tid}&class=全部&area=全部&lang=全部&year=全部&sort=最新&page={pg}", headers=headers, verify=False, timeout=self.timeout).json()
|
||||
videos = []
|
||||
for i in response['data']['recommend_list']:
|
||||
videos.append(i)
|
||||
data = {'list': videos}
|
||||
if self.category == 2:
|
||||
if isinstance(data, dict):
|
||||
for i in data.get('list'):
|
||||
i['vod_id'] = f"msearch:{i['vod_id']}"
|
||||
return data
|
||||
|
||||
def searchContent(self, key, quick, pg='1'):
|
||||
if not self.host: return None
|
||||
headers = self.headers.copy()
|
||||
search_ua = self.uas.get('search')
|
||||
if search_ua: headers['User-Agent'] = search_ua
|
||||
if self.cms:
|
||||
cms = self.cms
|
||||
if '?' in cms: cms = cms.split('?')[0] + '?'
|
||||
response = self.fetch(f'{cms}ac=detail&wd={key}', headers=headers, verify=False, timeout=self.timeout).json()
|
||||
self.detail = response['list']
|
||||
else:
|
||||
path = f"{self.host}/api.php/Appfox/vod?ac=detail&wd={key}"
|
||||
if self.froms: path += '&from=' + self.froms
|
||||
response = self.fetch(path, headers=headers, verify=False, timeout=self.timeout).json()
|
||||
self.detail = response['list']
|
||||
return response
|
||||
|
||||
def detailContent(self, ids):
|
||||
headers = self.headers.copy()
|
||||
detail_ua = self.uas.get('detail',self.uas.get('search'))
|
||||
if detail_ua:
|
||||
headers['User-Agent'] = detail_ua
|
||||
video = next((i.copy() for i in self.detail if str(i['vod_id']) == str(ids[0])), None)
|
||||
if not video:
|
||||
if self.cms:
|
||||
cms = self.cms
|
||||
if '?' in cms: cms = cms.split('?')[0] + '?'
|
||||
response = self.fetch(f'{cms}ac=detail&ids={ids[0]}', headers=headers, verify=False, timeout=self.timeout).json()
|
||||
video = response.get('list')[0]
|
||||
else:
|
||||
detail_response = self.fetch(f"{self.host}/api.php/Appfox/vod?ac=detail&ids={ids[0]}", headers=headers, verify=False, timeout=self.timeout).json()
|
||||
video = detail_response.get('list')[0]
|
||||
if not video: return {'list': []}
|
||||
play_from = video['vod_play_from'].split('$$$')
|
||||
play_urls = video['vod_play_url'].split('$$$')
|
||||
try:
|
||||
headers = self.headers.copy()
|
||||
custom_ua = self.uas.get('config')
|
||||
if custom_ua: headers['User-Agent'] = custom_ua
|
||||
config_response = self.fetch(f"{self.host}/api.php/Appfox/config", headers=headers, verify=False, timeout=self.timeout).json()
|
||||
player_list = config_response.get('data', {}).get('playerList', [])
|
||||
jiexi_data_list = config_response.get('data', {}).get('jiexiDataList', [])
|
||||
except Exception:
|
||||
return {'list': [video]}
|
||||
player_map = {player['playerCode']: player for player in player_list}
|
||||
processed_play_urls = []
|
||||
for idx, play_code in enumerate(play_from):
|
||||
if play_code in player_map:
|
||||
player_info = player_map[play_code]
|
||||
if player_info['playerCode'] != player_info['playerName']:
|
||||
play_from[idx] = f"{player_info['playerName']}\u2005({play_code})"
|
||||
if idx < len(play_urls):
|
||||
urls = play_urls[idx].split('#')
|
||||
processed_urls = []
|
||||
for url in urls:
|
||||
parts = url.split('$')
|
||||
if len(parts) >= 2:
|
||||
parts[1] = f"{play_code}@{parts[1]}"
|
||||
processed_urls.append('$'.join(parts))
|
||||
else:
|
||||
processed_urls.append(url)
|
||||
processed_play_urls.append('#'.join(processed_urls))
|
||||
video['vod_play_from'] = '$$$'.join(play_from)
|
||||
video['vod_play_url'] = '$$$'.join(processed_play_urls)
|
||||
self.parses = {p['playerCode']: p['url'] for p in jiexi_data_list if p.get('url', '').startswith('http')}
|
||||
return {'list': [video]}
|
||||
|
||||
def playerContent(self, flag, id, vipflags):
|
||||
play_from, raw_url = id.split('@', 1)
|
||||
jx, parse, parsed = 0, 0, 0
|
||||
headers = self.headers.copy()
|
||||
parse_ua = self.uas.get('parse')
|
||||
if parse_ua: headers['User-Agent'] = parse_ua
|
||||
player_ua = self.uas.get('player','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')
|
||||
url = raw_url
|
||||
parses_main = []
|
||||
if self.custom_first == 1:
|
||||
parses_main.append(self.custom_parses)
|
||||
parses_main.append(self.parses)
|
||||
else:
|
||||
parses_main.append(self.parses)
|
||||
parses_main.append(self.custom_parses)
|
||||
for parses2 in parses_main:
|
||||
if not parsed and not re.match(r'https?://.*\.(m3u8|mp4|flv|mkv)', url):
|
||||
for key, parsers in parses2.items():
|
||||
if play_from not in key:
|
||||
continue
|
||||
if isinstance(parsers,list):
|
||||
for parser in parsers:
|
||||
if parser.startswith('parse:'):
|
||||
url,jx,parse = parser.split('parse:')[1] + raw_url,0,1
|
||||
break
|
||||
try:
|
||||
response = self.fetch(f"{parser}{raw_url}", headers=headers, verify=False, timeout=self.timeout).json()
|
||||
if response.get('url', '').startswith('http'):
|
||||
url, parsed = response['url'], 1
|
||||
break
|
||||
except Exception:
|
||||
continue
|
||||
else:
|
||||
if parsers.startswith('parse:'):
|
||||
url,jx,parse = parsers.split('parse:')[1] + raw_url,0,1
|
||||
break
|
||||
try:
|
||||
response = self.fetch(f"{parsers}{raw_url}", headers=headers, verify=False, timeout=self.timeout).json()
|
||||
if response.get('url', '').startswith('http'):
|
||||
url, parsed = response['url'], 1
|
||||
break
|
||||
except Exception:
|
||||
continue
|
||||
if parsed or parse:
|
||||
break
|
||||
if parsed or parse:
|
||||
break
|
||||
if not(re.match(r'https?:\/\/.*\.(m3u8|mp4|flv|mkv)', url) or parsed == 1):
|
||||
jx = 1
|
||||
return { 'jx': jx, 'parse': parse, 'url': url, 'header': {'User-Agent': player_ua}}
|
||||
|
||||
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,313 @@
|
||||
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
|
||||
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
|
||||
|
||||
"""
|
||||
示例
|
||||
{
|
||||
"key": "key",
|
||||
"name": "name",
|
||||
"type": 3,
|
||||
"api": "./AppMuou.py",
|
||||
"ext": {
|
||||
"host": "https://muouapp.oss-cn-hangzhou.domain.com/xxx/xxx.txt", 应用域名(支持txt或域名)
|
||||
"name": "xxx", 应用名称
|
||||
"version": "4.2.0" 应用版本号
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
from base.spider import Spider
|
||||
from Crypto.Util.Padding import unpad
|
||||
import re,sys,time,json,base64,hashlib,urllib3
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
sys.path.append('..')
|
||||
|
||||
class Spider(Spider):
|
||||
host,name,version,data_key,data_iv,cms_host,jx_api,playerinfo,= '', '', '', '', '', '', '',[]
|
||||
headers = {
|
||||
'User-Agent': "Mozilla/5.0 (Linux; Android 12) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.105 MUOUAPP/10.8.4506.400",
|
||||
'Accept-Encoding': "gzip",
|
||||
'brand-model': "xiaomi",
|
||||
'app-device': "nodata",
|
||||
'app-time': "",
|
||||
'sys-version': "12",
|
||||
'device': "831395239bddf2e6",
|
||||
'os': "Android",
|
||||
'app-version': version
|
||||
}
|
||||
|
||||
def init(self, extend=""):
|
||||
try:
|
||||
config = json.loads(extend)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
config = {}
|
||||
name = config.get("name", "muou")
|
||||
self.headers['app-version'] = config.get("version", "4.2.0")
|
||||
self.host = config['host']
|
||||
if not re.match(r'^https?:\/\/[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*(:\d+)?(\/)?$', self.host):
|
||||
self.host = self.fetch(self.host, headers=self.headers, timeout=10, verify=False).text.rstrip('/')
|
||||
timestamp = int(time.time())
|
||||
self.headers['app-time'] = str(timestamp)
|
||||
inner_sha1 = hashlib.sha1(f"{timestamp}{name}".encode('utf-8')).hexdigest()
|
||||
outer_sha1 = hashlib.sha1(f"{timestamp}{inner_sha1}muouapp".encode('utf-8')).hexdigest()
|
||||
payload = { 't': timestamp, 'n': inner_sha1, 'm': outer_sha1 }
|
||||
try:
|
||||
response = self.post(f'{self.host}/app_info.php', data=payload)
|
||||
if response.status_code != 200:
|
||||
return '-2 数据获取失败'
|
||||
dat = response.text
|
||||
except:
|
||||
return '-2 数据获取失败'
|
||||
try:
|
||||
dat2 = json.loads(dat)
|
||||
except:
|
||||
return '-2 数据获取失败'
|
||||
data = dat2.get('data', '')
|
||||
a = dat2.get('a', '')
|
||||
e = dat2.get('e', '')
|
||||
s = dat2.get('s', '')
|
||||
if not a or not e or not s:
|
||||
return '-3 参数获取失败'
|
||||
data2 = self.t(data, s, e)
|
||||
key = hashlib.md5(a.encode('utf-8')).hexdigest()[:16]
|
||||
iv = hashlib.md5(outer_sha1.encode('utf-8')).hexdigest()[:16]
|
||||
result = self.decrypt(data2, key, iv)
|
||||
if not result:
|
||||
return '-4 解密失败'
|
||||
try:
|
||||
dat3 = json.loads(result)
|
||||
except:
|
||||
return '-5 解密失败'
|
||||
key2 = dat3['key']
|
||||
iv2 = dat3['iv']
|
||||
self.data_key = hashlib.md5(key2.encode('utf-8')).hexdigest()[:16]
|
||||
self.data_iv = hashlib.md5(iv2.encode('utf-8')).hexdigest()[:16]
|
||||
self.cms_host = dat3['HBqq']
|
||||
jx_api = dat3.get('HBrjjg','')
|
||||
if jx_api.startswith('http'):
|
||||
self.jx_api = jx_api
|
||||
return None
|
||||
|
||||
def homeContent(self, filter):
|
||||
if not self.cms_host:
|
||||
return {'list': []}
|
||||
self.headers['app-time'] = str(int(time.time()))
|
||||
try:
|
||||
response = self.fetch(f'{self.cms_host}/api.php/v1.vod/types', headers=self.headers).text
|
||||
except Exception as e:
|
||||
return {"class": [], "filters": {}}
|
||||
|
||||
try:
|
||||
data = json.loads(response) or {}
|
||||
except json.JSONDecodeError:
|
||||
try:
|
||||
data = json.loads(self.decrypt(response))
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
return {"class": [], "filters": {}}
|
||||
filter_keys = {"class", "area", "lang", "year", "letter", "by", "sort"}
|
||||
filters = {}
|
||||
classes = []
|
||||
typelist = data.get('data', {}).get('typelist', [])
|
||||
for item in typelist:
|
||||
type_id = str(item["type_id"])
|
||||
classes.append({"type_name": item["type_name"], "type_id": type_id})
|
||||
extend = item.get("type_extend", {})
|
||||
type_filters = []
|
||||
for key, value_str in extend.items():
|
||||
if key not in filter_keys:
|
||||
continue
|
||||
stripped = value_str.strip()
|
||||
if not stripped:
|
||||
continue
|
||||
values = [v.strip() for v in stripped.split(",") if v.strip()]
|
||||
if not values:
|
||||
continue
|
||||
type_filters.append({
|
||||
"key": key,
|
||||
"name": key,
|
||||
"value": [{"n": v, "v": v} for v in values]
|
||||
})
|
||||
if type_filters:
|
||||
filters[type_id] = type_filters
|
||||
return {"class": classes, "filters": filters}
|
||||
|
||||
def homeVideoContent(self):
|
||||
if not self.cms_host:
|
||||
return {'list': []}
|
||||
self.headers['app-time'] = str(int(time.time()))
|
||||
print(f'{self.cms_host}/api.php/v1.vod/HomeIndex?page=&limit=6')
|
||||
response = self.fetch(f'{self.cms_host}/api.php/v1.vod/HomeIndex?page=&limit=6', headers=self.headers).text
|
||||
try:
|
||||
data = json.loads(response)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
data_ = self.decrypt(response)
|
||||
data = json.loads(data_)
|
||||
videos = []
|
||||
for i in data['data']:
|
||||
if i.get('vod_list'):
|
||||
vod_list = i['vod_list']
|
||||
for j in vod_list:
|
||||
pic = j.get('vod_pic')
|
||||
if pic:
|
||||
if not pic.startswith('http'):
|
||||
j['vod_pic'] = self.cms_host + pic
|
||||
videos.extend(vod_list)
|
||||
return {'list': videos}
|
||||
|
||||
def detailContent(self, ids):
|
||||
self.headers['app-time'] = str(int(time.time()))
|
||||
response = self.fetch(f'{self.cms_host}/api.php/v1.vod/detail?vod_id={ids[0]}', headers=self.headers).text
|
||||
try:
|
||||
data = json.loads(response)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
data_ = self.decrypt(response)
|
||||
data = json.loads(data_)
|
||||
data = data['data']
|
||||
if data == '':
|
||||
return {'list': []}
|
||||
vod_play_url = ''
|
||||
show = ''
|
||||
for i,j in data['vod_play_list'].items():
|
||||
show += j['player_info']['show'] + '$$$'
|
||||
urls = j.get('urls',{})
|
||||
play_url = ''
|
||||
if isinstance(urls, dict):
|
||||
for i2,j2 in urls.items():
|
||||
play_url += f"{j2['name']}${j2['from']}@{j2['url']}#"
|
||||
play_url = play_url.rstrip('#')
|
||||
vod_play_url += play_url + '$$$'
|
||||
data['vod_play_from'] = show.rstrip('$$$')
|
||||
data['vod_play_url'] = vod_play_url.rstrip('$$$')
|
||||
data['vod_play_note'] = '$$$'
|
||||
data.pop('vod_play_list')
|
||||
data.pop('type')
|
||||
return {'list': [data]}
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
if not self.cms_host:
|
||||
return {'list': []}
|
||||
self.headers['app-time'] = str(int(time.time()))
|
||||
response = self.fetch(f'{self.cms_host}/api.php/v1.vod?wd={key}&limit=18&page={pg}', headers=self.headers).text
|
||||
try:
|
||||
data = json.loads(response)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
data_ = self.decrypt(response)
|
||||
data = json.loads(data_)
|
||||
videos = data['data']['list']
|
||||
for item in data['data']['list']:
|
||||
item.pop('type', None)
|
||||
return {'list': videos, 'page': pg}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
if not self.cms_host:
|
||||
return {'list': []}
|
||||
self.headers['app-time'] = str(int(time.time()))
|
||||
response = self.fetch(
|
||||
f"{self.cms_host}/api.php/v1.vod?type={tid}&class={extend.get('class', '')}&area={extend.get('area', '')}&year={extend.get('year', '')}&by=time&page={pg}&limit=18",
|
||||
headers=self.headers).text
|
||||
try:
|
||||
data = json.loads(response)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
data_ = self.decrypt(response)
|
||||
data = json.loads(data_)
|
||||
videos = data['data']['list']
|
||||
for item in data['data']['list']:
|
||||
pic = item.get('vod_pic', '')
|
||||
if pic:
|
||||
if not pic.startswith('http'):
|
||||
item['vod_pic'] = self.cms_host + pic
|
||||
item.pop('type', None)
|
||||
print(videos)
|
||||
return {'list': videos, 'page': pg}
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
play_from, raw_url = id.split('@')
|
||||
jx,url,playurl, = 1,raw_url,''
|
||||
try:
|
||||
if not self.playerinfo:
|
||||
res = self.fetch(f'{self.host}/api.php?action=playerinfo',headers=self.headers).text
|
||||
data = self.decrypt(res)
|
||||
playerinfo =json.loads(data).get('data',{}).get('playerinfo',[])
|
||||
if len(playerinfo) > 1:
|
||||
self.playerinfo = playerinfo
|
||||
if self.playerinfo:
|
||||
for i in self.playerinfo:
|
||||
play_jx = i.get('playerjiekou','')
|
||||
if i.get('playername') == play_from and play_jx.startswith('http'):
|
||||
response = self.fetch(f'{play_jx}{raw_url}&playerkey={play_from}',headers=self.headers,verify=False).text
|
||||
try:
|
||||
data = json.loads(response)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
data_ = self.decrypt(response)
|
||||
data = json.loads(data_)
|
||||
if str(data.get('code','')) == '403':
|
||||
playurl = ''
|
||||
else:
|
||||
playurl = data['url']
|
||||
jx = 0
|
||||
except Exception:
|
||||
playurl = ''
|
||||
|
||||
if playurl.startswith('http'):
|
||||
url = playurl
|
||||
else:
|
||||
if re.search(r'^https?[^\s]*\.(m3u8|mp4|flv)', raw_url, re.I):
|
||||
url = raw_url
|
||||
jx = 0
|
||||
else:
|
||||
try:
|
||||
response = self.fetch(self.jx_api + raw_url,headers=self.headers,verify=False).text
|
||||
try:
|
||||
data = json.loads(response)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
data_ = self.decrypt(response)
|
||||
data = json.loads(data_)
|
||||
playurl = data.get('url','')
|
||||
if playurl.startswith('http'):
|
||||
jx,url = 0,playurl
|
||||
else:
|
||||
jx,url = 1,raw_url
|
||||
except Exception as e:
|
||||
jx,url = 1,raw_url
|
||||
if url.startswith('NBY-'):
|
||||
jx,url = 0,''
|
||||
return {'jx': jx, 'parse': 0, 'url': url,'header': {'User-Agent': 'Mozilla/5.0 (Linux; Android 12) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.105 MUOUAPP/10.8.4506.400'}}
|
||||
|
||||
def decrypt(self,data, key='', iv=''):
|
||||
if not(key or iv):
|
||||
key = self.data_key
|
||||
iv = self.data_iv
|
||||
key_bytes = key.encode('utf-8')
|
||||
iv_bytes = iv.encode('utf-8')
|
||||
encrypted_data = base64.b64decode(data)
|
||||
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 t(self, s, v, v1):
|
||||
if s is not None and s != '':
|
||||
n = len(s)
|
||||
if v < 0 or v1 < 0:
|
||||
raise ValueError("参数不能为负数")
|
||||
if v + v1 <= n:
|
||||
return s[v:n - v1]
|
||||
else:
|
||||
return ''
|
||||
return s
|
||||
|
||||
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,337 @@
|
||||
import re
|
||||
import sys
|
||||
import uuid
|
||||
import json
|
||||
import base64
|
||||
from Crypto.Cipher import AES
|
||||
from base.spider import Spider
|
||||
from Crypto.Util.Padding import pad
|
||||
from Crypto.Util.Padding import unpad
|
||||
sys.path.append('..')
|
||||
|
||||
headerx = {
|
||||
'User-Agent': 'okhttp/3.10.0' # okhttp/3.14.9
|
||||
}
|
||||
|
||||
class Spider(Spider):
|
||||
global headerx
|
||||
init_data = ''
|
||||
search_verify = ''
|
||||
|
||||
def getName(self):
|
||||
return "首页"
|
||||
|
||||
def init(self, extend):
|
||||
js1=json.loads(extend)
|
||||
host = js1['host']
|
||||
if re.match(r'^https:\/\/.*\.(txt|json)$',host):
|
||||
host = self.fetch(host, headers=headerx, timeout=10).text.rstrip('/')
|
||||
self.xurl = host + js1.get('api','/api.php/getappapi')
|
||||
self.key = js1['datakey']
|
||||
self.iv = js1.get('dataiv',self.key)
|
||||
|
||||
res = self.fetch(self.xurl + '.index/initV119', headers=headerx).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=headerx,data=payload).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=headerx, data=payload)
|
||||
|
||||
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 = ''
|
||||
|
||||
actor = kjson['vod']['vod_actor']
|
||||
director = kjson['vod'].get('vod_director', '')
|
||||
area = kjson['vod']['vod_area']
|
||||
name = kjson['vod']['vod_name']
|
||||
year = kjson['vod']['vod_year']
|
||||
content = kjson['vod']['vod_content']
|
||||
subtitle = kjson['vod']['vod_remarks']
|
||||
desc = kjson['vod']['vod_lang']
|
||||
remark = '时间:' + subtitle + ' 语言:' + desc
|
||||
for line in kjson['vod_play_list']:
|
||||
keywords = ['防走丢', '群', '防失群', 'Q','juo.one',]
|
||||
if any(keyword in line['player_info']['show'] for keyword in keywords):
|
||||
continue
|
||||
play_form += line['player_info']['show'] + '$$$'
|
||||
parse = line['player_info']['parse']
|
||||
player_parse_type = line['player_info']['player_parse_type']
|
||||
kurls = ""
|
||||
for vod in line['urls']:
|
||||
kurl = vod['url']
|
||||
if '.m3u8' in kurl:
|
||||
kurls += str(vod['name']) + '$' + vod['url'] + '#'
|
||||
else:
|
||||
if 'm3u8' not in kurl:
|
||||
token = 'token+' + vod['token']
|
||||
kurls += str(vod['name']) + '$' + parse + ',' + vod[
|
||||
'url'] + ',' + token + ',' + player_parse_type + '#'
|
||||
kurls = kurls.rstrip('#')
|
||||
play_url += kurls + '$$$'
|
||||
play_form = play_form.rstrip('$$$')
|
||||
play_url = play_url.rstrip('$$$')
|
||||
videos.append({
|
||||
"vod_id": did,
|
||||
"vod_name": name,
|
||||
"vod_actor": actor.replace('演员', ''),
|
||||
"vod_director": director.replace('导演', ''),
|
||||
"vod_content": content,
|
||||
"vod_remarks": remark,
|
||||
"vod_year": year + '年',
|
||||
"vod_area": area,
|
||||
"vod_play_from": play_form,
|
||||
"vod_play_url": play_url
|
||||
})
|
||||
return {'list': videos}
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
url = ''
|
||||
if re.search(r'https?:\/\/.*\.(m3u8|mp4|flv)', id):
|
||||
url = id
|
||||
elif 'url=' in id:
|
||||
aid = id.split(',')
|
||||
uid = aid[0]
|
||||
kurl = aid[1]
|
||||
kjson = uid + kurl
|
||||
url2 = f"{kjson}"
|
||||
response = self.fetch(url=url2)
|
||||
if response.status_code == 200:
|
||||
kjson1 = response.json()
|
||||
url = kjson1['url']
|
||||
else:
|
||||
aid = id.split(',')
|
||||
bid = aid[-1]
|
||||
uid = aid[0]
|
||||
kurl = aid[1]
|
||||
token = aid[2].replace('token+', '')
|
||||
id1 = self.decrypt_wb(kurl)
|
||||
payload = {
|
||||
'parse_api': uid,
|
||||
'url': id1,
|
||||
'player_parse_type': bid,
|
||||
'token': token
|
||||
}
|
||||
url1 = f"{self.xurl}.index/vodParse"
|
||||
response = self.post(url=url1, headers=headerx, data=payload)
|
||||
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']
|
||||
return {"parse": 0, "playUrl": '', "url": url, "header": {'User-Agent': 'Dalvik/2.1.0 (Linux; U; Android 14; 23113RK12C Build/SKQ1.231004.001)'}}
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
videos = []
|
||||
# 使用urllib.parse.quote确保关键词被正确编码
|
||||
import urllib.parse
|
||||
encoded_key = urllib.parse.quote(key)
|
||||
|
||||
# 构建POST请求的表单数据,使用编码后的关键词
|
||||
payload = {
|
||||
'keywords': encoded_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'
|
||||
# 发送POST请求并携带表单数据
|
||||
res = self.post(url=url, data=payload, headers=headerx).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']
|
||||
|
||||
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 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_text = self.key
|
||||
iv_text = self.iv
|
||||
key_bytes = key_text.encode('utf-8')
|
||||
iv_bytes = iv_text.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 decrypt_wb(self, sencrypted_data):
|
||||
key_text = self.key
|
||||
iv_text = self.iv
|
||||
key_bytes = key_text.encode('utf-8')
|
||||
iv_bytes = iv_text.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=headerx).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=headerx).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 = replace_code(code)
|
||||
if not (len(code) == 4 and code.isdigit()):
|
||||
return None
|
||||
return {'uuid': random_uuid, 'code': code}
|
||||
|
||||
def replace_code(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('066', '1666')
|
||||
text = text.replace('566', '5066')
|
||||
return ''.join(replacements.get(c, c) for c in text)
|
||||
@@ -0,0 +1,343 @@
|
||||
import re
|
||||
import sys
|
||||
import uuid
|
||||
import json
|
||||
import base64
|
||||
from Crypto.Cipher import AES
|
||||
from base.spider import Spider
|
||||
from Crypto.Util.Padding import pad
|
||||
from Crypto.Util.Padding import unpad
|
||||
# 移除了urlencode导入,将使用自定义方法
|
||||
|
||||
sys.path.append('..')
|
||||
|
||||
headerx = {
|
||||
'User-Agent': 'okhttp/3.10.0' # okhttp/3.14.9
|
||||
}
|
||||
|
||||
class Spider(Spider):
|
||||
global headerx
|
||||
init_data = ''
|
||||
search_verify = ''
|
||||
|
||||
def getName(self):
|
||||
return "首页"
|
||||
|
||||
def init(self, extend):
|
||||
js1=json.loads(extend)
|
||||
host = js1['host']
|
||||
if re.match(r'^https:\/\/.*\.(txt|json)$',host):
|
||||
host = self.fetch(host, headers=headerx, timeout=10).text.rstrip('/')
|
||||
self.xurl = host + js1.get('api','/api.php/getappapi')
|
||||
self.key = js1['datakey']
|
||||
self.iv = js1.get('dataiv',self.key)
|
||||
|
||||
res = self.fetch(self.xurl + '.index/initV119', headers=headerx).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=headerx,data=payload).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=headerx, data=payload)
|
||||
|
||||
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 = ''
|
||||
|
||||
actor = kjson['vod']['vod_actor']
|
||||
director = kjson['vod'].get('vod_director', '')
|
||||
area = kjson['vod']['vod_area']
|
||||
name = kjson['vod']['vod_name']
|
||||
year = kjson['vod']['vod_year']
|
||||
content = kjson['vod']['vod_content']
|
||||
subtitle = kjson['vod']['vod_remarks']
|
||||
desc = kjson['vod']['vod_lang']
|
||||
remark = '时间:' + subtitle + ' 语言:' + desc
|
||||
for line in kjson['vod_play_list']:
|
||||
keywords = ['防走丢', '群', '防失群', 'Q','juo.one',]
|
||||
if any(keyword in line['player_info']['show'] for keyword in keywords):
|
||||
continue
|
||||
play_form += line['player_info']['show'] + '$$$'
|
||||
parse = line['player_info']['parse']
|
||||
player_parse_type = line['player_info']['player_parse_type']
|
||||
kurls = ""
|
||||
for vod in line['urls']:
|
||||
kurl = vod['url']
|
||||
if '.m3u8' in kurl:
|
||||
kurls += str(vod['name']) + '$' + vod['url'] + '#'
|
||||
else:
|
||||
if 'm3u8' not in kurl:
|
||||
token = 'token+' + vod['token']
|
||||
kurls += str(vod['name']) + '$' + parse + ',' + vod[
|
||||
'url'] + ',' + token + ',' + player_parse_type + '#'
|
||||
kurls = kurls.rstrip('#')
|
||||
play_url += kurls + '$$$'
|
||||
play_form = play_form.rstrip('$$$')
|
||||
play_url = play_url.rstrip('$$$')
|
||||
videos.append({
|
||||
"vod_id": did,
|
||||
"vod_name": name,
|
||||
"vod_actor": actor.replace('演员', ''),
|
||||
"vod_director": director.replace('导演', ''),
|
||||
"vod_content": content,
|
||||
"vod_remarks": remark,
|
||||
"vod_year": year + '年',
|
||||
"vod_area": area,
|
||||
"vod_play_from": play_form,
|
||||
"vod_play_url": play_url
|
||||
})
|
||||
return {'list': videos}
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
url = ''
|
||||
if re.search(r'https?:\/\/.*\.(m3u8|mp4|flv)', id):
|
||||
url = id
|
||||
elif 'url=' in id:
|
||||
aid = id.split(',')
|
||||
uid = aid[0]
|
||||
kurl = aid[1]
|
||||
kjson = uid + kurl
|
||||
url2 = f"{kjson}"
|
||||
response = self.fetch(url=url2)
|
||||
if response.status_code == 200:
|
||||
kjson1 = response.json()
|
||||
url = kjson1['url']
|
||||
else:
|
||||
aid = id.split(',')
|
||||
bid = aid[-1]
|
||||
uid = aid[0]
|
||||
kurl = aid[1]
|
||||
token = aid[2].replace('token+', '')
|
||||
id1 = self.decrypt_wb(kurl)
|
||||
payload = {
|
||||
'parse_api': uid,
|
||||
'url': id1,
|
||||
'player_parse_type': bid,
|
||||
'token': token
|
||||
}
|
||||
url1 = f"{self.xurl}.index/vodParse"
|
||||
response = self.post(url=url1, headers=headerx, data=payload)
|
||||
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']
|
||||
return {"parse": 0, "playUrl": '', "url": url, "header": {'User-Agent': 'Dalvik/2.1.0 (Linux; U; Android 14; 23113RK12C Build/SKQ1.231004.001)'}}
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
videos = []
|
||||
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']
|
||||
|
||||
# 替换urlencode的实现
|
||||
data_str = '&'.join([f"{k}={v}" for k, v in payload.items()])
|
||||
|
||||
# 设置请求头
|
||||
headers = {
|
||||
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||
**headerx # 保留原来的 headerx 内容
|
||||
}
|
||||
|
||||
url = f'{self.xurl}.index/searchList'
|
||||
res = self.post(url=url, data=data_str, headers=headers).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']:
|
||||
video = {
|
||||
"vod_id": i['vod_id'],
|
||||
"vod_name": i['vod_name'],
|
||||
"vod_pic": i['vod_pic'],
|
||||
"vod_remarks": f"{i.get('vod_year', '')} {i.get('vod_class', '')}".strip()
|
||||
}
|
||||
videos.append(video)
|
||||
|
||||
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_text = self.key
|
||||
iv_text = self.iv
|
||||
key_bytes = key_text.encode('utf-8')
|
||||
iv_bytes = iv_text.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 decrypt_wb(self, sencrypted_data):
|
||||
key_text = self.key
|
||||
iv_text = self.iv
|
||||
key_bytes = key_text.encode('utf-8')
|
||||
iv_bytes = iv_text.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=headerx).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=headerx).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}
|
||||
|
||||
@staticmethod # 添加静态方法装饰器
|
||||
def replace_code(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('066', '1666')
|
||||
text = text.replace('566', '5066')
|
||||
return ''.join(replacements.get(c, c) for c in text)
|
||||
@@ -0,0 +1,343 @@
|
||||
import re
|
||||
import sys
|
||||
import uuid
|
||||
import json
|
||||
import base64
|
||||
from Crypto.Cipher import AES
|
||||
from base.spider import Spider
|
||||
from Crypto.Util.Padding import pad
|
||||
from Crypto.Util.Padding import unpad
|
||||
# 移除了urlencode导入,将使用自定义方法
|
||||
|
||||
sys.path.append('..')
|
||||
|
||||
headerx = {
|
||||
'User-Agent': 'okhttp/3.10.0' # okhttp/3.14.9
|
||||
}
|
||||
|
||||
class Spider(Spider):
|
||||
global headerx
|
||||
init_data = ''
|
||||
search_verify = ''
|
||||
|
||||
def getName(self):
|
||||
return "首页"
|
||||
|
||||
def init(self, extend):
|
||||
js1=json.loads(extend)
|
||||
host = js1['host']
|
||||
if re.match(r'^https?:\/\/.*\.(txt|json)$',host):
|
||||
host = self.fetch(host, headers=headerx, timeout=10).text.rstrip('/')
|
||||
self.xurl = host + js1.get('api','/api.php/getappapi')
|
||||
self.key = js1['datakey']
|
||||
self.iv = js1.get('dataiv',self.key)
|
||||
|
||||
res = self.fetch(self.xurl + '.index/initV119', headers=headerx).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=headerx,data=payload).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=headerx, data=payload)
|
||||
|
||||
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 = ''
|
||||
|
||||
actor = kjson['vod']['vod_actor']
|
||||
director = kjson['vod'].get('vod_director', '')
|
||||
area = kjson['vod']['vod_area']
|
||||
name = kjson['vod']['vod_name']
|
||||
year = kjson['vod']['vod_year']
|
||||
content = kjson['vod']['vod_content']
|
||||
subtitle = kjson['vod']['vod_remarks']
|
||||
desc = kjson['vod']['vod_lang']
|
||||
remark = '时间:' + subtitle + ' 语言:' + desc
|
||||
for line in kjson['vod_play_list']:
|
||||
keywords = ['防走丢', '群', '防失群', 'Q','juo.one',]
|
||||
if any(keyword in line['player_info']['show'] for keyword in keywords):
|
||||
continue
|
||||
play_form += line['player_info']['show'] + '$$$'
|
||||
parse = line['player_info']['parse']
|
||||
player_parse_type = line['player_info']['player_parse_type']
|
||||
kurls = ""
|
||||
for vod in line['urls']:
|
||||
kurl = vod['url']
|
||||
if '.m3u8' in kurl:
|
||||
kurls += str(vod['name']) + '$' + vod['url'] + '#'
|
||||
else:
|
||||
if 'm3u8' not in kurl:
|
||||
token = 'token+' + vod['token']
|
||||
kurls += str(vod['name']) + '$' + parse + ',' + vod[
|
||||
'url'] + ',' + token + ',' + player_parse_type + '#'
|
||||
kurls = kurls.rstrip('#')
|
||||
play_url += kurls + '$$$'
|
||||
play_form = play_form.rstrip('$$$')
|
||||
play_url = play_url.rstrip('$$$')
|
||||
videos.append({
|
||||
"vod_id": did,
|
||||
"vod_name": name,
|
||||
"vod_actor": actor.replace('演员', ''),
|
||||
"vod_director": director.replace('导演', ''),
|
||||
"vod_content": content,
|
||||
"vod_remarks": remark,
|
||||
"vod_year": year + '年',
|
||||
"vod_area": area,
|
||||
"vod_play_from": play_form,
|
||||
"vod_play_url": play_url
|
||||
})
|
||||
return {'list': videos}
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
url = ''
|
||||
if re.search(r'https?:\/\/.*\.(m3u8|mp4|flv)', id):
|
||||
url = id
|
||||
elif 'url=' in id:
|
||||
aid = id.split(',')
|
||||
uid = aid[0]
|
||||
kurl = aid[1]
|
||||
kjson = uid + kurl
|
||||
url2 = f"{kjson}"
|
||||
response = self.fetch(url=url2)
|
||||
if response.status_code == 200:
|
||||
kjson1 = response.json()
|
||||
url = kjson1['url']
|
||||
else:
|
||||
aid = id.split(',')
|
||||
bid = aid[-1]
|
||||
uid = aid[0]
|
||||
kurl = aid[1]
|
||||
token = aid[2].replace('token+', '')
|
||||
id1 = self.decrypt_wb(kurl)
|
||||
payload = {
|
||||
'parse_api': uid,
|
||||
'url': id1,
|
||||
'player_parse_type': bid,
|
||||
'token': token
|
||||
}
|
||||
url1 = f"{self.xurl}.index/vodParse"
|
||||
response = self.post(url=url1, headers=headerx, data=payload)
|
||||
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']
|
||||
return {"parse": 0, "playUrl": '', "url": url, "header": {'User-Agent': 'Dalvik/2.1.0 (Linux; U; Android 14; 23113RK12C Build/SKQ1.231004.001)'}}
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
videos = []
|
||||
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']
|
||||
|
||||
# 替换urlencode的实现
|
||||
data_str = '&'.join([f"{k}={v}" for k, v in payload.items()])
|
||||
|
||||
# 设置请求头
|
||||
headers = {
|
||||
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||
**headerx # 保留原来的 headerx 内容
|
||||
}
|
||||
|
||||
url = f'{self.xurl}.index/searchList'
|
||||
res = self.post(url=url, data=data_str, headers=headers).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']:
|
||||
video = {
|
||||
"vod_id": i['vod_id'],
|
||||
"vod_name": i['vod_name'],
|
||||
"vod_pic": i['vod_pic'],
|
||||
"vod_remarks": f"{i.get('vod_year', '')} {i.get('vod_class', '')}".strip()
|
||||
}
|
||||
videos.append(video)
|
||||
|
||||
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_text = self.key
|
||||
iv_text = self.iv
|
||||
key_bytes = key_text.encode('utf-8')
|
||||
iv_bytes = iv_text.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 decrypt_wb(self, sencrypted_data):
|
||||
key_text = self.key
|
||||
iv_text = self.iv
|
||||
key_bytes = key_text.encode('utf-8')
|
||||
iv_bytes = iv_text.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=headerx).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=headerx).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}
|
||||
|
||||
@staticmethod # 添加静态方法装饰器
|
||||
def replace_code(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('066', '1666')
|
||||
text = text.replace('566', '5066')
|
||||
return ''.join(replacements.get(c, c) for c in text)
|
||||
@@ -0,0 +1,146 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# by @嗷呜
|
||||
import sys
|
||||
sys.path.append('..')
|
||||
from base.spider import Spider
|
||||
import requests
|
||||
|
||||
|
||||
class Spider(Spider):
|
||||
|
||||
def init(self, extend=""):
|
||||
pass
|
||||
|
||||
def getName(self):
|
||||
return "hitv"
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
def homeContent(self, filter):
|
||||
result = {}
|
||||
cateManual = {
|
||||
# "直播": "live",
|
||||
'排行榜': 'rank',
|
||||
"电影": "1",
|
||||
"剧集": "2",
|
||||
"综艺": "3",
|
||||
"动画": "4",
|
||||
"短片": "5"
|
||||
}
|
||||
classes = []
|
||||
for k in cateManual:
|
||||
classes.append({
|
||||
'type_name': k,
|
||||
'type_id': cateManual[k]
|
||||
})
|
||||
result['class'] = classes
|
||||
return result
|
||||
|
||||
host = "https://wys.upfuhn.com"
|
||||
headers = {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) "
|
||||
"Chrome/80.0.3987.149 Safari/537.36"
|
||||
}
|
||||
|
||||
def list(self, list):
|
||||
videos = []
|
||||
for it in list:
|
||||
videos.append({
|
||||
"vod_id": it['video_site_id'],
|
||||
"vod_name": it['video_name'],
|
||||
"vod_pic": it['video_horizontal_url'] or it['video_vertical_url'],
|
||||
"vod_remarks": it['newest_series_num'],
|
||||
"vod_year": it['years'],
|
||||
})
|
||||
return videos
|
||||
|
||||
def homeVideoContent(self):
|
||||
url = f'{self.host}/v1/ys_video_sites/hot?t=1'
|
||||
data = requests.get(url, headers=self.headers).json()
|
||||
videos = self.list(data['data']['data'])
|
||||
result = {'list': videos}
|
||||
return result
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
path = f'/v1/ys_video_sites?t={tid}&s_t=0&a&y&o=0&ps=21&pn={pg}'
|
||||
rank = False
|
||||
if tid == 'rank':
|
||||
if pg == 1:
|
||||
path = f'/v1/ys_video_sites/ranking'
|
||||
rank = True
|
||||
else:
|
||||
path = ''
|
||||
# elif tid == 'live' and pg == 1:
|
||||
# path = f'/v1/ys_live_tvs'
|
||||
videos = []
|
||||
result = {}
|
||||
try:
|
||||
data = requests.get(self.host + path, headers=self.headers).json()
|
||||
if rank:
|
||||
for video in data['data']:
|
||||
videos.extend(data['data'][video])
|
||||
else:
|
||||
videos = data['data']['data']
|
||||
result = {}
|
||||
result['list'] = self.list(videos)
|
||||
result['page'] = pg
|
||||
result['pagecount'] = 9999
|
||||
result['limit'] = 90
|
||||
result['total'] = 999999
|
||||
except:
|
||||
result['list'] = []
|
||||
return result
|
||||
|
||||
def detailContent(self, ids):
|
||||
tid = ids[0]
|
||||
url = f'{self.host}/v1/ys_video_series/by_vid/{tid}'
|
||||
data = requests.get(url, headers=self.headers).json()
|
||||
data1 = data['data']['ys_video_site']
|
||||
urls = []
|
||||
for it in data['data']['data']:
|
||||
urls.append(it['series_num'] + '$' + it['video_url'])
|
||||
vod = {
|
||||
'vod_name': data1['video_name'],
|
||||
'type_name': data1['tag'],
|
||||
'vod_year': data1['years'],
|
||||
'vod_area': data1['area'],
|
||||
'vod_director': data1['main_actor'],
|
||||
'vod_content': data1['video_desc'],
|
||||
'vod_play_from': '嗷呜在线',
|
||||
'vod_play_url': '#'.join(urls),
|
||||
}
|
||||
result = {
|
||||
'list': [
|
||||
vod
|
||||
]
|
||||
}
|
||||
return result
|
||||
|
||||
def searchContent(self, key, quick, pg=1):
|
||||
url = f'{self.host}/v1/ys_video_sites/search?s={key}&o=0&ps=200&pn={pg}'
|
||||
data = requests.get(url, headers=self.headers).json()
|
||||
videos = data['data']['video_sites']
|
||||
if data['data']['first_video_series'] is not None:
|
||||
videos = [data['data']['first_video_series']] + videos
|
||||
result = {}
|
||||
result['list'] = self.list(videos)
|
||||
result['page'] = pg
|
||||
return result
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
result = {
|
||||
'url': id,
|
||||
'parse': 0,
|
||||
'header': self.headers
|
||||
}
|
||||
return result
|
||||
|
||||
def localProxy(self, param):
|
||||
pass
|
||||
@@ -0,0 +1,337 @@
|
||||
import re
|
||||
import sys
|
||||
import uuid
|
||||
import json
|
||||
import base64
|
||||
from Crypto.Cipher import AES
|
||||
from base.spider import Spider
|
||||
from Crypto.Util.Padding import pad
|
||||
from Crypto.Util.Padding import unpad
|
||||
sys.path.append('..')
|
||||
|
||||
headerx = {
|
||||
'User-Agent': 'okhttp/3.10.0' # okhttp/3.14.9
|
||||
}
|
||||
|
||||
class Spider(Spider):
|
||||
global headerx
|
||||
init_data = ''
|
||||
search_verify = ''
|
||||
|
||||
def getName(self):
|
||||
return "首页"
|
||||
|
||||
def init(self, extend):
|
||||
js1=json.loads(extend)
|
||||
host = js1['host']
|
||||
if re.match(r'^https?:\/\/.*\.(txt|json)$',host):
|
||||
host = self.fetch(host, headers=headerx, timeout=10).text.rstrip('/')
|
||||
self.xurl = host + js1.get('api','/api.php/getappapi')
|
||||
self.key = js1['datakey']
|
||||
self.iv = js1.get('dataiv',self.key)
|
||||
|
||||
res = self.fetch(self.xurl + '.index/initV119', headers=headerx).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=headerx,data=payload).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=headerx, data=payload)
|
||||
|
||||
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 = ''
|
||||
|
||||
actor = kjson['vod']['vod_actor']
|
||||
director = kjson['vod'].get('vod_director', '')
|
||||
area = kjson['vod']['vod_area']
|
||||
name = kjson['vod']['vod_name']
|
||||
year = kjson['vod']['vod_year']
|
||||
content = kjson['vod']['vod_content']
|
||||
subtitle = kjson['vod']['vod_remarks']
|
||||
desc = kjson['vod']['vod_lang']
|
||||
remark = '时间:' + subtitle + ' 语言:' + desc
|
||||
for line in kjson['vod_play_list']:
|
||||
keywords = ['防走丢', '群', '防失群', 'Q','juo.one',]
|
||||
if any(keyword in line['player_info']['show'] for keyword in keywords):
|
||||
continue
|
||||
play_form += line['player_info']['show'] + '$$$'
|
||||
parse = line['player_info']['parse']
|
||||
player_parse_type = line['player_info']['player_parse_type']
|
||||
kurls = ""
|
||||
for vod in line['urls']:
|
||||
kurl = vod['url']
|
||||
if '.m3u8' in kurl:
|
||||
kurls += str(vod['name']) + '$' + vod['url'] + '#'
|
||||
else:
|
||||
if 'm3u8' not in kurl:
|
||||
token = 'token+' + vod['token']
|
||||
kurls += str(vod['name']) + '$' + parse + ',' + vod[
|
||||
'url'] + ',' + token + ',' + player_parse_type + '#'
|
||||
kurls = kurls.rstrip('#')
|
||||
play_url += kurls + '$$$'
|
||||
play_form = play_form.rstrip('$$$')
|
||||
play_url = play_url.rstrip('$$$')
|
||||
videos.append({
|
||||
"vod_id": did,
|
||||
"vod_name": name,
|
||||
"vod_actor": actor.replace('演员', ''),
|
||||
"vod_director": director.replace('导演', ''),
|
||||
"vod_content": content,
|
||||
"vod_remarks": remark,
|
||||
"vod_year": year + '年',
|
||||
"vod_area": area,
|
||||
"vod_play_from": play_form,
|
||||
"vod_play_url": play_url
|
||||
})
|
||||
return {'list': videos}
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
url = ''
|
||||
if re.search(r'https?:\/\/.*\.(m3u8|mp4|flv)', id):
|
||||
url = id
|
||||
elif 'url=' in id:
|
||||
aid = id.split(',')
|
||||
uid = aid[0]
|
||||
kurl = aid[1]
|
||||
kjson = uid + kurl
|
||||
url2 = f"{kjson}"
|
||||
response = self.fetch(url=url2)
|
||||
if response.status_code == 200:
|
||||
kjson1 = response.json()
|
||||
url = kjson1['url']
|
||||
else:
|
||||
aid = id.split(',')
|
||||
bid = aid[-1]
|
||||
uid = aid[0]
|
||||
kurl = aid[1]
|
||||
token = aid[2].replace('token+', '')
|
||||
id1 = self.decrypt_wb(kurl)
|
||||
payload = {
|
||||
'parse_api': uid,
|
||||
'url': id1,
|
||||
'player_parse_type': bid,
|
||||
'token': token
|
||||
}
|
||||
url1 = f"{self.xurl}.index/vodParse"
|
||||
response = self.post(url=url1, headers=headerx, data=payload)
|
||||
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']
|
||||
return {"parse": 0, "playUrl": '', "url": url, "header": {'User-Agent': 'Dalvik/2.1.0 (Linux; U; Android 14; 23113RK12C Build/SKQ1.231004.001)'}}
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
videos = []
|
||||
# 使用urllib.parse.quote确保关键词被正确编码
|
||||
import urllib.parse
|
||||
encoded_key = urllib.parse.quote(key)
|
||||
|
||||
# 构建POST请求的表单数据,使用编码后的关键词
|
||||
payload = {
|
||||
'keywords': encoded_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'
|
||||
# 发送POST请求并携带表单数据
|
||||
res = self.post(url=url, data=payload, headers=headerx).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']
|
||||
|
||||
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 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_text = self.key
|
||||
iv_text = self.iv
|
||||
key_bytes = key_text.encode('utf-8')
|
||||
iv_bytes = iv_text.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 decrypt_wb(self, sencrypted_data):
|
||||
key_text = self.key
|
||||
iv_text = self.iv
|
||||
key_bytes = key_text.encode('utf-8')
|
||||
iv_bytes = iv_text.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=headerx).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=headerx).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 = replace_code(code)
|
||||
if not (len(code) == 4 and code.isdigit()):
|
||||
return None
|
||||
return {'uuid': random_uuid, 'code': code}
|
||||
|
||||
def replace_code(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('066', '1666')
|
||||
text = text.replace('566', '5066')
|
||||
return ''.join(replacements.get(c, c) for c in text)
|
||||
@@ -0,0 +1,175 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# by @嗷呜
|
||||
import json
|
||||
import sys
|
||||
from base64 import b64decode, b64encode
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Util.Padding import unpad, pad
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
sys.path.append('..')
|
||||
from base.spider import Spider
|
||||
|
||||
|
||||
class Spider(Spider):
|
||||
|
||||
def init(self, extend=""):
|
||||
self.host = self.gethost()
|
||||
self.hkey,self.playerinfos=self.getinfo()
|
||||
pass
|
||||
|
||||
def getName(self):
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
headers = {
|
||||
'User-Agent': 'Dalvik/1.4.0 (Linux; U; Android 11; Redmi Build/M2012K10C)',
|
||||
'version': '1.4.0'
|
||||
}
|
||||
|
||||
keys=['rectangleadsadxa','aassddwwxxllsx1x']
|
||||
|
||||
def homeContent(self, filter):
|
||||
cdata=self.getdata('/api.php/v1.home/types')
|
||||
result = {}
|
||||
classes = []
|
||||
filters = {}
|
||||
for i in cdata['data']['types'][1:]:
|
||||
classes.append({
|
||||
'type_id': i['type_id'],
|
||||
'type_name': i['type_name']
|
||||
})
|
||||
with ThreadPoolExecutor(max_workers=len(classes)) as executor:
|
||||
futures = [executor.submit(self.getf, i['type_id'])
|
||||
for i in classes]
|
||||
for future in futures:
|
||||
try:
|
||||
type_id, filter_data = future.result()
|
||||
if len(filter_data):filters[type_id] = filter_data
|
||||
except Exception as e:
|
||||
print(f'处理筛选数据失败: {e}')
|
||||
result['class'] = classes
|
||||
result['filters'] = filters
|
||||
return result
|
||||
|
||||
def homeVideoContent(self):
|
||||
data=self.getdata('/api.php/v1.home/data?type_id=26')
|
||||
return {'list':data['data']['banners']}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
json_data = {
|
||||
'area': extend.get('area', '全部地区'),
|
||||
'lang': extend.get('lang', '全部语言'),
|
||||
'rank': extend.get('rank', '最新'),
|
||||
'type': extend.get('type', '全部类型'),
|
||||
'type_id': int(tid),
|
||||
'year': extend.get('year', '全部年代'),
|
||||
}
|
||||
data=self.getdata(f'/api.php/v1.classify/content?page={pg}',method=False,json_data=json_data)
|
||||
result = {}
|
||||
result['list'] = data['data']['video_list']
|
||||
result['page'] = pg
|
||||
result['pagecount'] = 9999
|
||||
result['limit'] = 90
|
||||
result['total'] = 999999
|
||||
return result
|
||||
|
||||
def detailContent(self, ids):
|
||||
data=self.getdata(f'/api.php/v1.player/details?vod_id={ids[0]}')
|
||||
vod = data['data']['detail']
|
||||
plist,names = [],[]
|
||||
for i in vod['play_url_list']:
|
||||
names.append(i['show'])
|
||||
plist.append('#'.join([f"{j['name']}${i['from']}@@{j['url']}" for j in i['urls']]))
|
||||
vod.pop('play_url_list', None)
|
||||
vod.update({'vod_play_from': '$$$'.join(names), 'vod_play_url': '$$$'.join(plist)})
|
||||
return {'list':[vod]}
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
data=self.getdata(f'/api.php/v1.search/data?wd={key}&type_id=0&page={pg}')
|
||||
return {'list': data['data']['search_data'], 'page': pg}
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
ids=id.split('@@')
|
||||
try:
|
||||
body={'parse':self.getparse(ids[0]),'url':ids[-1],'matching':''}
|
||||
data=self.getdata(f'/shark/api.php?action=parsevod',method=False,data=body)
|
||||
url=data.get('url') or data['data'].get('url')
|
||||
if not url:
|
||||
raise ValueError("解析失败")
|
||||
p=0
|
||||
except:
|
||||
p,url = 1,ids[-1]
|
||||
return {'parse': p, 'url': url, 'header': {'User-Agent':'aliplayer(appv=1.4.0&av=6.16.0&av2=6.16.0_40316683&os=android&ov=11&dm=M2012K10C)'}}
|
||||
|
||||
def localProxy(self, param):
|
||||
pass
|
||||
|
||||
def getparse(self,id):
|
||||
for i in self.playerinfos:
|
||||
if i['playername']==id:
|
||||
j= i['playerjiekou']
|
||||
return self.aes(j,self.hkey)
|
||||
return ''
|
||||
|
||||
def gethost(self):
|
||||
headers = {
|
||||
'User-Agent': 'okhttp/4.11.0',
|
||||
'Connection': 'Keep-Alive'
|
||||
}
|
||||
response = self.fetch('https://shopv1.oss-accelerate.aliyuncs.com/api.txt', headers=headers).text
|
||||
host=json.loads(self.aes(response.strip(),self.keys[0]))[0]
|
||||
return host
|
||||
|
||||
def getinfo(self):
|
||||
data=self.post(f'{self.host}/shark/api.php?action=configs',headers=self.headers,data={'username':'','token':''}).text
|
||||
datas=json.loads(self.aes(data))
|
||||
hkey = datas['config']['hulue'].split('&')[0]
|
||||
playerinfos = datas['playerinfos']
|
||||
return hkey,playerinfos
|
||||
|
||||
def getdata(self,parh,method=True,data=None,json_data=None):
|
||||
url = f'{self.host}{parh}'
|
||||
if method:
|
||||
response = self.fetch(url, headers=self.headers).text
|
||||
else:
|
||||
response = self.post(url, headers=self.headers, data=data, json=json_data).text
|
||||
return json.loads(self.aes(response))
|
||||
|
||||
def getf(self, type_id):
|
||||
try:
|
||||
fdata = self.getdata(f'/api.php/v1.classify/types?type_id={type_id}')
|
||||
filter_list = []
|
||||
for key, value in fdata['data'].items():
|
||||
if len(value):
|
||||
filter_list.append({
|
||||
'key': key.split('_')[0],
|
||||
'name': key.split('_')[0],
|
||||
'value': [{'n': j['type_name'], 'v': j['type_name']} for j in value if j.get('type_name')]
|
||||
})
|
||||
return type_id, filter_list
|
||||
except Exception as e:
|
||||
print(f'获取type_id={type_id}的筛选数据失败: {e}')
|
||||
return type_id, []
|
||||
|
||||
def aes(self, word,key=None, b=True):
|
||||
if not key:key=self.keys[1]
|
||||
cipher = AES.new(key.encode(), AES.MODE_ECB)
|
||||
word = word.encode('utf-8-sig').decode('ascii', errors='ignore')
|
||||
if b:
|
||||
word = b64decode(word)
|
||||
decrypted = cipher.decrypt(word)
|
||||
word = unpad(decrypted, AES.block_size).decode()
|
||||
else:
|
||||
padded = pad(word.encode(), AES.block_size)
|
||||
encrypted = cipher.encrypt(padded)
|
||||
word = b64encode(encrypted).decode()
|
||||
return word
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# by @嗷呜
|
||||
import re
|
||||
import sys
|
||||
import threading
|
||||
import requests
|
||||
from Crypto.Hash import MD5
|
||||
sys.path.append("..")
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Util.Padding import pad, unpad
|
||||
from urllib.parse import quote, urlparse
|
||||
from base64 import b64encode, b64decode
|
||||
import json
|
||||
import time
|
||||
from base.spider import Spider
|
||||
|
||||
|
||||
class Spider(Spider):
|
||||
|
||||
def init(self, extend=""):
|
||||
self.host = self.gethost()
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def action(self, action):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
def homeContent(self, filter):
|
||||
data = self.getdata("/api.php/getappapi.index/initV119")
|
||||
dy = {"class": "类型", "area": "地区", "lang": "语言", "year": "年份", "letter": "字母", "by": "排序",
|
||||
"sort": "排序"}
|
||||
filters = {}
|
||||
classes = []
|
||||
json_data = data["type_list"]
|
||||
homedata = data["banner_list"][8:]
|
||||
for item in json_data:
|
||||
if item["type_name"] == "全部":
|
||||
continue
|
||||
has_non_empty_field = False
|
||||
jsontype_extend = json.loads(item["type_extend"])
|
||||
homedata.extend(item["recommend_list"])
|
||||
jsontype_extend["sort"] = "最新,最热,最赞"
|
||||
classes.append({"type_name": item["type_name"], "type_id": item["type_id"]})
|
||||
for key in dy:
|
||||
if key in jsontype_extend and jsontype_extend[key].strip() != "":
|
||||
has_non_empty_field = True
|
||||
break
|
||||
if has_non_empty_field:
|
||||
filters[str(item["type_id"])] = []
|
||||
for dkey in jsontype_extend:
|
||||
if dkey in dy and jsontype_extend[dkey].strip() != "":
|
||||
values = jsontype_extend[dkey].split(",")
|
||||
value_array = [{"n": value.strip(), "v": value.strip()} for value in values if
|
||||
value.strip() != ""]
|
||||
filters[str(item["type_id"])].append({"key": dkey, "name": dy[dkey], "value": value_array})
|
||||
result = {}
|
||||
result["class"] = classes
|
||||
result["filters"] = filters
|
||||
result["list"] = homedata[1:]
|
||||
return result
|
||||
|
||||
def homeVideoContent(self):
|
||||
pass
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
body = {"area": extend.get('area', '全部'), "year": extend.get('year', '全部'), "type_id": tid, "page": pg,
|
||||
"sort": extend.get('sort', '最新'), "lang": extend.get('lang', '全部'),
|
||||
"class": extend.get('class', '全部')}
|
||||
result = {}
|
||||
data = self.getdata("/api.php/getappapi.index/typeFilterVodList", body)
|
||||
result["list"] = data["recommend_list"]
|
||||
result["page"] = pg
|
||||
result["pagecount"] = 9999
|
||||
result["limit"] = 90
|
||||
result["total"] = 999999
|
||||
return result
|
||||
|
||||
def detailContent(self, ids):
|
||||
body = f"vod_id={ids[0]}"
|
||||
data = self.getdata("/api.php/getappapi.index/vodDetail", body)
|
||||
vod = data["vod"]
|
||||
play = []
|
||||
names = []
|
||||
for itt in data["vod_play_list"]:
|
||||
a = []
|
||||
names.append(itt["player_info"]["show"])
|
||||
for it in itt['urls']:
|
||||
it['user_agent'] = itt["player_info"].get("user_agent")
|
||||
it["parse"] = itt["player_info"].get("parse")
|
||||
a.append(f"{it['name']}${self.e64(json.dumps(it))}")
|
||||
play.append("#".join(a))
|
||||
vod["vod_play_from"] = "$$$".join(names)
|
||||
vod["vod_play_url"] = "$$$".join(play)
|
||||
result = {"list": [vod]}
|
||||
return result
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
body = f"keywords={key}&type_id=0&page={pg}"
|
||||
data = self.getdata("/api.php/getappapi.index/searchList", body)
|
||||
result = {"list": data["search_list"], "page": pg}
|
||||
return result
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
ids = json.loads(self.d64(id))
|
||||
h = {"User-Agent": (ids['user_agent'] or "okhttp/3.14.9")}
|
||||
try:
|
||||
if re.search(r'url=', ids['parse_api_url']):
|
||||
data = self.fetch(ids['parse_api_url'], headers=h, timeout=10).json()
|
||||
url = data.get('url') or data['data'].get('url')
|
||||
else:
|
||||
body = f"parse_api={ids.get('parse') or ids['parse_api_url'].replace(ids['url'], '')}&url={quote(self.aes(ids['url'], True))}&token={ids.get('token')}"
|
||||
b = self.getdata("/api.php/getappapi.index/vodParse", body)['json']
|
||||
url = json.loads(b)['url']
|
||||
if 'error' in url: raise ValueError(f"解析失败: {url}")
|
||||
p = 0
|
||||
except Exception as e:
|
||||
print('错误信息:', e)
|
||||
url, p = ids['url'], 1
|
||||
|
||||
if re.search(r'\.jpg|\.png|\.jpeg', url):
|
||||
url = self.Mproxy(url)
|
||||
result = {}
|
||||
result["parse"] = p
|
||||
result["url"] = url
|
||||
result["header"] = h
|
||||
return result
|
||||
|
||||
def localProxy(self, param):
|
||||
return self.Mlocal(param)
|
||||
|
||||
def gethost(self):
|
||||
headers = {
|
||||
'User-Agent': 'okhttp/3.14.9'
|
||||
}
|
||||
response = self.fetch('https://ydysdynamicdomainname.68.gy:10678/c9m2js298x82h6/l9m8bx23j2o2p9q/dynamicdomainname.txt',
|
||||
headers=headers).text
|
||||
return self.host_late(response.split('\n'))
|
||||
|
||||
def host_late(self, url_list):
|
||||
if isinstance(url_list, str):
|
||||
urls = [u.strip() for u in url_list.split(',')]
|
||||
else:
|
||||
urls = url_list
|
||||
|
||||
if len(urls) <= 1:
|
||||
return urls[0] if urls else ''
|
||||
|
||||
results = {}
|
||||
threads = []
|
||||
|
||||
def test_host(url):
|
||||
try:
|
||||
start_time = time.time()
|
||||
response = requests.head(url,timeout=1.0, allow_redirects=False)
|
||||
delay = (time.time() - start_time) * 1000
|
||||
results[url] = delay
|
||||
except Exception as e:
|
||||
results[url] = float('inf')
|
||||
|
||||
for url in urls:
|
||||
t = threading.Thread(target=test_host, args=(url,))
|
||||
threads.append(t)
|
||||
t.start()
|
||||
|
||||
for t in threads:
|
||||
t.join()
|
||||
|
||||
return min(results.items(), key=lambda x: x[1])[0]
|
||||
|
||||
def aes(self, text, b=None):
|
||||
key = b"k9o3p2c8b7m3z0o8"
|
||||
cipher = AES.new(key, AES.MODE_CBC, key)
|
||||
if b:
|
||||
ct_bytes = cipher.encrypt(pad(text.encode("utf-8"), AES.block_size))
|
||||
ct = b64encode(ct_bytes).decode("utf-8")
|
||||
return ct
|
||||
else:
|
||||
pt = unpad(cipher.decrypt(b64decode(text)), AES.block_size)
|
||||
return pt.decode("utf-8")
|
||||
|
||||
def header(self):
|
||||
t = str(int(time.time()))
|
||||
header = {"Referer": self.host,
|
||||
"User-Agent": "okhttp/3.14.9", "app-version-code": "140", "app-ui-mode": "light",
|
||||
"app-api-verify-time": t, "app-user-device-id": self.md5(t),
|
||||
"app-api-verify-sign": self.aes(t, True),
|
||||
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"}
|
||||
return header
|
||||
|
||||
def getdata(self, path, data=None):
|
||||
vdata = self.post(f"{self.host}{path}", headers=self.header(), data=data, timeout=10).json()['data']
|
||||
data1 = self.aes(vdata)
|
||||
return json.loads(data1)
|
||||
|
||||
def Mproxy(self, url):
|
||||
return f"{self.getProxyUrl()}&url={self.e64(url)}&type=m3u8"
|
||||
|
||||
def Mlocal(self, param, header=None):
|
||||
url = self.d64(param["url"])
|
||||
ydata = self.fetch(url, headers=header, allow_redirects=False)
|
||||
data = ydata.content.decode('utf-8')
|
||||
if ydata.headers.get('Location'):
|
||||
url = ydata.headers['Location']
|
||||
data = self.fetch(url, headers=header).content.decode('utf-8')
|
||||
parsed_url = urlparse(url)
|
||||
durl = parsed_url.scheme + "://" + parsed_url.netloc
|
||||
lines = data.strip().split('\n')
|
||||
for index, string in enumerate(lines):
|
||||
if '#EXT' not in string and 'http' not in string:
|
||||
last_slash_index = string.rfind('/')
|
||||
lpath = string[:last_slash_index + 1]
|
||||
lines[index] = durl + ('' if lpath.startswith('/') else '/') + lpath
|
||||
data = '\n'.join(lines)
|
||||
return [200, "application/vnd.apple.mpegur", data]
|
||||
|
||||
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 md5(self, text):
|
||||
h = MD5.new()
|
||||
h.update(text.encode('utf-8'))
|
||||
return h.hexdigest()
|
||||
@@ -0,0 +1,219 @@
|
||||
import re
|
||||
import sys
|
||||
from Crypto.Hash import MD5
|
||||
sys.path.append("..")
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Util.Padding import pad, unpad
|
||||
from urllib.parse import quote, urlparse
|
||||
from base64 import b64encode, b64decode
|
||||
import json
|
||||
import time
|
||||
from base.spider import Spider
|
||||
|
||||
|
||||
class Spider(Spider):
|
||||
|
||||
def init(self, extend=""):
|
||||
self.host = self.gethost()
|
||||
self.did=self.getdid()
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def action(self, action):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
def homeContent(self, filter):
|
||||
data = self.getdata("/api.php/getappapi.index/initV119")
|
||||
dy = {"class": "类型", "area": "地区", "lang": "语言", "year": "年份", "letter": "字母", "by": "排序",
|
||||
"sort": "排序"}
|
||||
filters = {}
|
||||
classes = []
|
||||
json_data = data["type_list"]
|
||||
homedata = data["banner_list"][8:]
|
||||
for item in json_data:
|
||||
if item["type_name"] == "全部":
|
||||
continue
|
||||
has_non_empty_field = False
|
||||
jsontype_extend = json.loads(item["type_extend"])
|
||||
homedata.extend(item["recommend_list"])
|
||||
jsontype_extend["sort"] = "最新,最热,最赞"
|
||||
classes.append({"type_name": item["type_name"], "type_id": item["type_id"]})
|
||||
for key in dy:
|
||||
if key in jsontype_extend and jsontype_extend[key].strip() != "":
|
||||
has_non_empty_field = True
|
||||
break
|
||||
if has_non_empty_field:
|
||||
filters[str(item["type_id"])] = []
|
||||
for dkey in jsontype_extend:
|
||||
if dkey in dy and jsontype_extend[dkey].strip() != "":
|
||||
values = jsontype_extend[dkey].split(",")
|
||||
value_array = [{"n": value.strip(), "v": value.strip()} for value in values if
|
||||
value.strip() != ""]
|
||||
filters[str(item["type_id"])].append({"key": dkey, "name": dy[dkey], "value": value_array})
|
||||
result = {}
|
||||
result["class"] = classes
|
||||
result["filters"] = filters
|
||||
result["list"] = homedata[1:]
|
||||
return result
|
||||
|
||||
def homeVideoContent(self):
|
||||
pass
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
body = {"area": extend.get('area', '全部'), "year": extend.get('year', '全部'), "type_id": tid, "page": pg,
|
||||
"sort": extend.get('sort', '最新'), "lang": extend.get('lang', '全部'),
|
||||
"class": extend.get('class', '全部')}
|
||||
result = {}
|
||||
data = self.getdata("/api.php/getappapi.index/typeFilterVodList", body)
|
||||
result["list"] = data["recommend_list"]
|
||||
result["page"] = pg
|
||||
result["pagecount"] = 9999
|
||||
result["limit"] = 90
|
||||
result["total"] = 999999
|
||||
return result
|
||||
|
||||
def detailContent(self, ids):
|
||||
body = f"vod_id={ids[0]}"
|
||||
data = self.getdata("/api.php/getappapi.index/vodDetail", body)
|
||||
vod = data["vod"]
|
||||
play = []
|
||||
names = []
|
||||
for itt in data["vod_play_list"]:
|
||||
a = []
|
||||
names.append(itt["player_info"]["show"])
|
||||
for it in itt['urls']:
|
||||
it['user_agent'] = itt["player_info"].get("user_agent")
|
||||
it["parse"] = itt["player_info"].get("parse")
|
||||
a.append(f"{it['name']}${self.e64(json.dumps(it))}")
|
||||
play.append("#".join(a))
|
||||
vod["vod_play_from"] = "$$$".join(names)
|
||||
vod["vod_play_url"] = "$$$".join(play)
|
||||
result = {"list": [vod]}
|
||||
return result
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
body = f"keywords={key}&type_id=0&page={pg}"
|
||||
data = self.getdata("/api.php/getappapi.index/searchList", body)
|
||||
result = {"list": data["search_list"], "page": pg}
|
||||
return result
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
ids = json.loads(self.d64(id))
|
||||
h = {"User-Agent": (ids['user_agent'] or "okhttp/3.14.9")}
|
||||
try:
|
||||
if re.search(r'url=', ids['parse_api_url']):
|
||||
data = self.fetch(ids['parse_api_url'], headers=h, timeout=10).json()
|
||||
url = data.get('url') or data['data'].get('url')
|
||||
else:
|
||||
body = f"parse_api={ids.get('parse') or ids['parse_api_url'].replace(ids['url'], '')}&url={quote(self.aes(ids['url'], True))}&token={ids.get('token')}"
|
||||
b = self.getdata("/api.php/getappapi.index/vodParse", body)['json']
|
||||
url = json.loads(b)['url']
|
||||
if 'error' in url: raise ValueError(f"解析失败: {url}")
|
||||
p = 0
|
||||
except Exception as e:
|
||||
print('错误信息:', e)
|
||||
url, p = ids['url'], 1
|
||||
|
||||
if re.search(r'\.jpg|\.png|\.jpeg', url):
|
||||
url = self.Mproxy(url)
|
||||
result = {}
|
||||
result["parse"] = p
|
||||
result["url"] = url
|
||||
result["header"] = h
|
||||
return result
|
||||
|
||||
def localProxy(self, param):
|
||||
return self.Mlocal(param)
|
||||
|
||||
def gethost(self):
|
||||
headers = {
|
||||
'User-Agent': 'okhttp/3.14.9'
|
||||
}
|
||||
response = self.fetch('https://jingyu-1312635929.cos.ap-nanjing.myqcloud.com/1.json',headers=headers).text
|
||||
return response.strip()
|
||||
|
||||
def getdid(self):
|
||||
did=self.getCache('did')
|
||||
if not did:
|
||||
t = str(int(time.time()))
|
||||
did = self.md5(t)
|
||||
self.setCache('did', did)
|
||||
return did
|
||||
|
||||
def aes(self, text, b=None):
|
||||
key = b"4d83b87c4c5ea111"
|
||||
cipher = AES.new(key, AES.MODE_CBC, key)
|
||||
if b:
|
||||
ct_bytes = cipher.encrypt(pad(text.encode("utf-8"), AES.block_size))
|
||||
ct = b64encode(ct_bytes).decode("utf-8")
|
||||
return ct
|
||||
else:
|
||||
pt = unpad(cipher.decrypt(b64decode(text)), AES.block_size)
|
||||
return pt.decode("utf-8")
|
||||
|
||||
def header(self):
|
||||
t = str(int(time.time()))
|
||||
header = {"Referer": self.host,
|
||||
"User-Agent": "okhttp/3.14.9", "app-version-code": "300", "app-ui-mode": "light",
|
||||
"app-api-verify-time": t, "app-user-device-id": self.did,
|
||||
"app-api-verify-sign": self.aes(t, True),
|
||||
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"}
|
||||
return header
|
||||
|
||||
def getdata(self, path, data=None):
|
||||
vdata = self.post(f"{self.host}{path}", headers=self.header(), data=data, timeout=10).json()['data']
|
||||
data1 = self.aes(vdata)
|
||||
return json.loads(data1)
|
||||
|
||||
def Mproxy(self, url):
|
||||
return f"{self.getProxyUrl()}&url={self.e64(url)}&type=m3u8"
|
||||
|
||||
def Mlocal(self, param, header=None):
|
||||
url = self.d64(param["url"])
|
||||
ydata = self.fetch(url, headers=header, allow_redirects=False)
|
||||
data = ydata.content.decode('utf-8')
|
||||
if ydata.headers.get('Location'):
|
||||
url = ydata.headers['Location']
|
||||
data = self.fetch(url, headers=header).content.decode('utf-8')
|
||||
parsed_url = urlparse(url)
|
||||
durl = parsed_url.scheme + "://" + parsed_url.netloc
|
||||
lines = data.strip().split('\n')
|
||||
for index, string in enumerate(lines):
|
||||
if '#EXT' not in string and 'http' not in string:
|
||||
last_slash_index = string.rfind('/')
|
||||
lpath = string[:last_slash_index + 1]
|
||||
lines[index] = durl + ('' if lpath.startswith('/') else '/') + lpath
|
||||
data = '\n'.join(lines)
|
||||
return [200, "application/vnd.apple.mpegur", data]
|
||||
|
||||
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 md5(self, text):
|
||||
h = MD5.new()
|
||||
h.update(text.encode('utf-8'))
|
||||
return h.hexdigest()
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# by @嗷呜
|
||||
import re
|
||||
import sys
|
||||
from Crypto.Hash import MD5
|
||||
sys.path.append('..')
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Util.Padding import pad, unpad
|
||||
from urllib.parse import quote, urlparse
|
||||
from base64 import b64encode, b64decode
|
||||
import json
|
||||
import time
|
||||
from base.spider import Spider
|
||||
|
||||
class Spider(Spider):
|
||||
|
||||
def init(self, extend=""):
|
||||
self.host = self.gethost()
|
||||
pass
|
||||
|
||||
def getName(self):
|
||||
pass
|
||||
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def action(self, action):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
def homeContent(self, filter):
|
||||
data = self.getdata("/api.php/getappapi.index/initV119")
|
||||
dy = {"class": "类型", "area": "地区", "lang": "语言", "year": "年份", "letter": "字母", "by": "排序",
|
||||
"sort": "排序"}
|
||||
filters = {}
|
||||
classes = []
|
||||
json_data = data["type_list"]
|
||||
homedata = data["banner_list"][8:]
|
||||
for item in json_data:
|
||||
if item["type_name"] == "全部":
|
||||
continue
|
||||
has_non_empty_field = False
|
||||
jsontype_extend = json.loads(item["type_extend"])
|
||||
homedata.extend(item["recommend_list"])
|
||||
jsontype_extend["sort"] = "最新,最热,最赞"
|
||||
classes.append({"type_name": item["type_name"], "type_id": item["type_id"]})
|
||||
for key in dy:
|
||||
if key in jsontype_extend and jsontype_extend[key].strip() != "":
|
||||
has_non_empty_field = True
|
||||
break
|
||||
if has_non_empty_field:
|
||||
filters[str(item["type_id"])] = []
|
||||
for dkey in jsontype_extend:
|
||||
if dkey in dy and jsontype_extend[dkey].strip() != "":
|
||||
values = jsontype_extend[dkey].split(",")
|
||||
value_array = [{"n": value.strip(), "v": value.strip()} for value in values if
|
||||
value.strip() != ""]
|
||||
filters[str(item["type_id"])].append({"key": dkey, "name": dy[dkey], "value": value_array})
|
||||
result = {}
|
||||
result["class"] = classes
|
||||
result["filters"] = filters
|
||||
result["list"] = homedata[1:]
|
||||
return result
|
||||
|
||||
def homeVideoContent(self):
|
||||
pass
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
body = {"area": extend.get('area', '全部'), "year": extend.get('year', '全部'), "type_id": tid, "page": pg,
|
||||
"sort": extend.get('sort', '最新'), "lang": extend.get('lang', '全部'),
|
||||
"class": extend.get('class', '全部')}
|
||||
result = {}
|
||||
data = self.getdata("/api.php/getappapi.index/typeFilterVodList", body)
|
||||
result["list"] = data["recommend_list"]
|
||||
result["page"] = pg
|
||||
result["pagecount"] = 9999
|
||||
result["limit"] = 90
|
||||
result["total"] = 999999
|
||||
return result
|
||||
|
||||
def detailContent(self, ids):
|
||||
body = f"vod_id={ids[0]}"
|
||||
data = self.getdata("/api.php/getappapi.index/vodDetail", body)
|
||||
vod = data["vod"]
|
||||
play = []
|
||||
names = []
|
||||
for itt in data["vod_play_list"]:
|
||||
a = []
|
||||
names.append(itt["player_info"]["show"])
|
||||
for it in itt['urls']:
|
||||
it['user_agent']=itt["player_info"].get("user_agent")
|
||||
it["parse"]=itt["player_info"].get("parse")
|
||||
a.append(f"{it['name']}${self.e64(json.dumps(it))}")
|
||||
play.append("#".join(a))
|
||||
vod["vod_play_from"] = "$$$".join(names)
|
||||
vod["vod_play_url"] = "$$$".join(play)
|
||||
result = {"list": [vod]}
|
||||
return result
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
body = f"keywords={key}&type_id=0&page={pg}"
|
||||
data = self.getdata("/api.php/getappapi.index/searchList", body)
|
||||
result = {"list": data["search_list"], "page": pg}
|
||||
return result
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
ids = json.loads(self.d64(id))
|
||||
h = {"User-Agent": (ids['user_agent'] or "okhttp/3.14.9")}
|
||||
try:
|
||||
if re.search(r'url=', ids['parse_api_url']):
|
||||
data = self.fetch(ids['parse_api_url'], headers=h, timeout=10).json()
|
||||
url = data.get('url') or data['data'].get('url')
|
||||
else:
|
||||
body = f"parse_api={ids.get('parse') or ids['parse_api_url'].replace(ids['url'], '')}&url={quote(self.aes(ids['url'], True))}&token={ids.get('token')}"
|
||||
b = self.getdata("/api.php/getappapi.index/vodParse", body)['json']
|
||||
url = json.loads(b)['url']
|
||||
if 'error' in url: raise ValueError(f"解析失败: {url}")
|
||||
p = 0
|
||||
except Exception as e:
|
||||
print('错误信息:', e)
|
||||
url, p = ids['url'], 1
|
||||
|
||||
if re.search(r'\.jpg|\.png|\.jpeg', url):
|
||||
url = self.Mproxy(url)
|
||||
result = {}
|
||||
result["parse"] = p
|
||||
result["url"] = url
|
||||
result["header"] = h
|
||||
return result
|
||||
|
||||
def localProxy(self, param):
|
||||
return self.Mlocal(param)
|
||||
|
||||
def gethost(self):
|
||||
headers = {
|
||||
'User-Agent': 'okhttp/3.14.9'
|
||||
}
|
||||
host = self.fetch('https://jingyu-1312635929.cos.ap-nanjing.myqcloud.com/1.json',
|
||||
headers=headers).text.strip()
|
||||
return host
|
||||
|
||||
phend = {
|
||||
'User-Agent': 'Dalvik/2.1.0 (Linux; U; Android 11; M2012K10C Build/RP1A.200720.011)',
|
||||
'allowCrossProtocolRedirects': 'true'
|
||||
}
|
||||
|
||||
def aes(self, operation, text):
|
||||
key = "4d83b87c4c5ea111".encode("utf-8")
|
||||
iv = key
|
||||
if operation == "encrypt":
|
||||
cipher = AES.new(key, AES.MODE_CBC, iv)
|
||||
ct_bytes = cipher.encrypt(pad(text.encode("utf-8"), AES.block_size))
|
||||
ct = b64encode(ct_bytes).decode("utf-8")
|
||||
return ct
|
||||
elif operation == "decrypt":
|
||||
cipher = AES.new(key, AES.MODE_CBC, iv)
|
||||
pt = unpad(cipher.decrypt(b64decode(text)), AES.block_size)
|
||||
return pt.decode("utf-8")
|
||||
|
||||
def header(self):
|
||||
t = str(int(time.time()))
|
||||
header = {"Referer":self.host,
|
||||
"User-Agent": "okhttp/3.14.9", "app-version-code": "300", "app-ui-mode": "light",
|
||||
"app-api-verify-time": t, "app-user-device-id": self.md5(t),
|
||||
"app-api-verify-sign": self.aes("encrypt", t),
|
||||
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"}
|
||||
return header
|
||||
|
||||
def getdata(self, path, data=None):
|
||||
vdata = self.post(f"{self.host}{path}", headers=self.header(), data=data, timeout=10).json()['data']
|
||||
data1 = self.aes("decrypt", vdata)
|
||||
return json.loads(data1)
|
||||
|
||||
def Mproxy(self, url):
|
||||
return self.getProxyUrl() + "&url=" + b64encode(url.encode('utf-8')).decode('utf-8') + "&type=m3u8"
|
||||
|
||||
def Mlocal(self, param,header=None):
|
||||
url = self.d64(param["url"])
|
||||
ydata = self.fetch(url, headers=header, allow_redirects=False)
|
||||
data = ydata.content.decode('utf-8')
|
||||
if ydata.headers.get('Location'):
|
||||
url = ydata.headers['Location']
|
||||
data = self.fetch(url, headers=header).content.decode('utf-8')
|
||||
parsed_url = urlparse(url)
|
||||
durl = parsed_url.scheme + "://" + parsed_url.netloc
|
||||
lines = data.strip().split('\n')
|
||||
for index, string in enumerate(lines):
|
||||
if '#EXT' not in string and 'http' not in string:
|
||||
last_slash_index = string.rfind('/')
|
||||
lpath = string[:last_slash_index + 1]
|
||||
lines[index] = durl + ('' if lpath.startswith('/') else '/') + lpath
|
||||
data = '\n'.join(lines)
|
||||
return [200, "application/vnd.apple.mpegur", data]
|
||||
|
||||
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 md5(self, text):
|
||||
h = MD5.new()
|
||||
h.update(text.encode('utf-8'))
|
||||
return h.hexdigest()
|
||||
@@ -0,0 +1,313 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# by @嗷呜
|
||||
import base64
|
||||
import binascii
|
||||
import json
|
||||
import random
|
||||
import sys
|
||||
import time
|
||||
import uuid
|
||||
from base64 import b64decode, b64encode
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Hash import MD5
|
||||
from Crypto.Util.Padding import unpad, pad
|
||||
sys.path.append('..')
|
||||
from base.spider import Spider
|
||||
|
||||
|
||||
class Spider(Spider):
|
||||
|
||||
def init(self, extend=""):
|
||||
self.ut = False
|
||||
# self.did, self.ntid =self.getdid()
|
||||
self.did, self.ntid = 'e59eb2465f61b9ca','65a0de19b3a2ec93fa479ad6'
|
||||
self.token, self.uid = self.gettoken()
|
||||
self.phost, self.phz,self.mphost=self.getpic()
|
||||
# self.phost, self.phz,self.mphost = ('https://dbtp.tgydy.com','.log','https://dplay.nbzsmc.com')
|
||||
pass
|
||||
|
||||
def getName(self):
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
host='http://192.151.245.34:8089'
|
||||
|
||||
def md5(self, text):
|
||||
h = MD5.new()
|
||||
h.update(text.encode('utf-8'))
|
||||
return h.hexdigest()
|
||||
|
||||
def uuid(self):
|
||||
return str(uuid.uuid4())
|
||||
|
||||
def getdid(self):
|
||||
did = self.random_str(16)
|
||||
ntid = self.random_str(24)
|
||||
return did, ntid
|
||||
# try:
|
||||
# if self.getCache('did'):
|
||||
# return self.getCache('did'), self.getCache('ntid')
|
||||
# else:
|
||||
# self.setCache('did', did)
|
||||
# self.setCache('ntid', ntid)
|
||||
# return did, ntid
|
||||
# except Exception as e:
|
||||
# self.setCache('did', did)
|
||||
# self.setCache('ntid', ntid)
|
||||
# return did, ntid
|
||||
|
||||
def aes(self, text, bool=True):
|
||||
key = b64decode('c0k4N1RfKTY1U1cjJERFRA==')
|
||||
iv = b64decode('VzIjQWRDVkdZSGFzSEdEVA==')
|
||||
if bool:
|
||||
cipher = AES.new(key, AES.MODE_CBC, iv)
|
||||
ct_bytes = cipher.encrypt(pad(text.encode("utf-8"), AES.block_size))
|
||||
ct = b64encode(ct_bytes).decode("utf-8")
|
||||
return ct
|
||||
else:
|
||||
cipher = AES.new(key, AES.MODE_CBC, iv)
|
||||
pt = unpad(cipher.decrypt(b64decode(text)), AES.block_size)
|
||||
ptt=json.loads(pt.decode("utf-8"))
|
||||
return ptt
|
||||
|
||||
def random_str(self,length=24):
|
||||
hex_chars = '0123456789abcdef'
|
||||
return ''.join(random.choice(hex_chars) for _ in range(length))
|
||||
|
||||
def gettoken(self):
|
||||
params={"deviceId":self.did,"deviceModel":"8848钛晶手机","devicePlatform":"1","tenantId":self.ntid}
|
||||
data=self.getdata('/supports/anonyLogin',params)
|
||||
self.ut=True
|
||||
return data['data']['token'], data['data']['userId']
|
||||
|
||||
def getdata(self,path,params=None):
|
||||
t = int(time.time()*1000)
|
||||
n=self.md5(f'{self.uuid()}{t}')
|
||||
if params:
|
||||
ct=self.aes(json.dumps(params))
|
||||
else:
|
||||
ct=f'{t}{n}'
|
||||
s=self.md5(f'{ct}[email protected]')
|
||||
headers = {
|
||||
'User-Agent': 'okhttp-okgo/jeasonlzy',
|
||||
'Connection': 'Keep-Alive',
|
||||
'Accept-Language': 'zh-CN,zh;q=0.8',
|
||||
'tenantId': self.ntid,
|
||||
'n': n,
|
||||
't': str(int(t/1000)),
|
||||
's': s,
|
||||
}
|
||||
if self.ut:
|
||||
headers['ta-token'] = self.token
|
||||
headers['userId'] = self.uid
|
||||
if params:
|
||||
params={'ct':ct}
|
||||
response = self.post(f'{self.host}{path}', headers=headers, json=params).text
|
||||
else:
|
||||
response = self.fetch(f'{self.host}{path}', headers=headers).text
|
||||
data=self.aes(response[1:-1],False)
|
||||
return data
|
||||
|
||||
def getpic(self):
|
||||
try:
|
||||
at = int(time.time() * 1000)
|
||||
t=str(int(at/ 1000))
|
||||
n = self.md5(f'{self.uuid()}{at}')
|
||||
headers = {
|
||||
'Host': '192.151.245.34:8089',
|
||||
'User-Agent': 'okhttp-okgo/jeasonlzy',
|
||||
'Connection': 'Keep-Alive',
|
||||
'Accept-Language': 'zh-CN,zh;q=0.8',
|
||||
'tenantId': self.ntid,
|
||||
'userId': self.uid,
|
||||
'ta-token': self.token,
|
||||
'n': n,
|
||||
't': t,
|
||||
's': self.md5(f'{t}{n}[email protected]')
|
||||
}
|
||||
params = {
|
||||
'tenantId': self.ntid,
|
||||
}
|
||||
response = self.fetch(f'{self.host}/supports/configs', params=params, headers=headers).text
|
||||
data=self.aes(response[1:-1],False)
|
||||
config = {
|
||||
'image_cdn': '',
|
||||
'image_cdn_path': '',
|
||||
'cdn-domain': ''
|
||||
}
|
||||
for item in data.get('data', []):
|
||||
name = item.get('name')
|
||||
records = item.get('records', [])
|
||||
|
||||
if name in config and records:
|
||||
value = records[0].get('value', '')
|
||||
if name == 'cdn-domain':
|
||||
value = value.split('#')[0]
|
||||
config[name] = value
|
||||
|
||||
return config['image_cdn'], config['image_cdn_path'], config['cdn-domain']
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error in getpic: {e}")
|
||||
return 'https://dbtp.tgydy.com', '.log', 'https://dplay.nbzsmc.com'
|
||||
|
||||
def getlist(self,data):
|
||||
vod=[]
|
||||
for i in data:
|
||||
vod.append({
|
||||
'vod_id': f'{i.get("movieId")}@{i.get("entryNum")}',
|
||||
'vod_name': i.get('title'),
|
||||
'vod_pic': f'{self.getProxyUrl()}&path={i.get("thumbnail")}',
|
||||
'vod_year': i.get('score'),
|
||||
'vod_remarks': f'{i.get("entryNum")}集'
|
||||
})
|
||||
return vod
|
||||
|
||||
def homeContent(self, filter):
|
||||
data=self.getdata('/movies/classifies')
|
||||
result = {}
|
||||
cateManual = {
|
||||
"榜单": "ranking/getTodayHotRank",
|
||||
"专辑": "getTMovieFolderPage",
|
||||
"剧场": "getClassMoviePage2",
|
||||
"演员": "follow/getRecommendActorPage",
|
||||
}
|
||||
classes = []
|
||||
for k in cateManual:
|
||||
classes.append({
|
||||
'type_name': k,
|
||||
'type_id': cateManual[k]
|
||||
})
|
||||
filters = {}
|
||||
if data.get('data'):
|
||||
filters["getClassMoviePage2"] = [
|
||||
{
|
||||
"key": "type",
|
||||
"name": "分类",
|
||||
"value": [
|
||||
{"n": item["name"], "v": item["classifyId"]}
|
||||
for item in data["data"]
|
||||
]
|
||||
}
|
||||
]
|
||||
filters["ranking/getTodayHotRank"] = [
|
||||
{
|
||||
"key": "type",
|
||||
"name": "榜单",
|
||||
"value": [
|
||||
{"n": "播放榜", "v": "getWeekHotPlayRank"},
|
||||
{"n": "高赞榜", "v": "getWeekStarRank"},
|
||||
{"n": "追剧榜", "v": "getSubTMoviePage"},
|
||||
{"n": "高分榜", "v": "ranking/getScoreRank"}
|
||||
]
|
||||
}
|
||||
]
|
||||
filters["follow/getRecommendActorPage"] = [
|
||||
{
|
||||
"key": "type",
|
||||
"name": "性别",
|
||||
"value": [
|
||||
{"n": "男", "v": "0"},
|
||||
{"n": "女", "v": "1"}
|
||||
]
|
||||
}
|
||||
]
|
||||
result['class'] = classes
|
||||
result['filters'] = filters
|
||||
return result
|
||||
|
||||
def homeVideoContent(self):
|
||||
params = {"pageNo":"1","pageSize":"30","platform":"1","deviceId":self.did,"tenantId":self.ntid}
|
||||
data=self.getdata('/news/getRecommendTMoviePage',params)
|
||||
vod=self.getlist(data['data']['records'])
|
||||
return {'list':vod}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
params={}
|
||||
path = f'/news/{tid}'
|
||||
if tid=='getClassMoviePage2':
|
||||
parama={"pageNo":pg,"pageSize":"30","orderFlag":"0","haveActor":"-1","classifyId":extend.get('type','-1'),"tagId":""}
|
||||
elif 'rank' in tid:
|
||||
path=f'/news/{extend.get("type") or tid}'
|
||||
parama={"pageNo":pg,"pageSize":"30"}
|
||||
elif 'follow' in tid:
|
||||
parama={"pageNo":pg,"pageSize":"20"}
|
||||
if extend.get('type'):
|
||||
path=f'/news/getActorPage'
|
||||
parama={"pageNo":pg,"pageSize":"50","sex":extend.get('type')}
|
||||
elif tid=='getTMovieFolderPage':
|
||||
parama={"pageNo":pg,"pageSize":"20"}
|
||||
elif '@' in tid:
|
||||
path='/news/getActorTMoviePage'
|
||||
parama={"id":tid.split('@')[0],"pageNo":pg,"pageSize":"30"}
|
||||
params['platform'] = '1'
|
||||
params['deviceId'] = self.did
|
||||
params['tenantId'] = self.ntid
|
||||
data=self.getdata(path,parama)
|
||||
vods=[]
|
||||
if 'follow' in tid:
|
||||
for i in data['data']['records']:
|
||||
vods.append({
|
||||
'vod_id': f'{i.get("id")}@',
|
||||
'vod_name': i.get('name'),
|
||||
'vod_pic': f"{self.getProxyUrl()}&path={i.get('avatar')}",
|
||||
'vod_tag': 'folder',
|
||||
'vod_remarks': f'作品{i.get("movieNum")}',
|
||||
'style': {"type": "oval"}
|
||||
})
|
||||
else:
|
||||
vdata=data['data']['records']
|
||||
if tid=='getTMovieFolderPage':
|
||||
vdata=[j for i in data['data']['records'] for j in i['movieList']]
|
||||
vods=self.getlist(vdata)
|
||||
result = {}
|
||||
result['list'] = vods
|
||||
result['page'] = pg
|
||||
result['pagecount'] = 9999
|
||||
result['limit'] = 90
|
||||
result['total'] = 999999
|
||||
return result
|
||||
|
||||
def detailContent(self, ids):
|
||||
ids=ids[0].split('@')
|
||||
params = {"pageNo": "1", "pageSize": ids[1], "movieId": ids[0], "platform": "1", "deviceId": self.did, "tenantId": self.ntid}
|
||||
data = self.getdata('/news/getEntryPage', params)
|
||||
print(data)
|
||||
plist=[f'第{i.get("entryNum")}集${i.get("mp4PlayAddress") or i.get("playAddress")}' for i in data['data']['records']]
|
||||
vod = {
|
||||
'vod_play_from': '嗷呜爱看短剧',
|
||||
'vod_play_url': '#'.join(plist),
|
||||
}
|
||||
return {'list':[vod]}
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
params = {"pageNo": pg, "pageSize": "20", "keyWord": key, "orderFlag": "0", "platform": "1", "deviceId": self.did, "tenantId": self.ntid}
|
||||
data = self.getdata('/news/searchTMoviePage', params)
|
||||
vod = self.getlist(data['data']['records'])
|
||||
return {'list':vod,'page':pg}
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
return {'parse': 0, 'url': f'{self.mphost}{id}', 'header': {'User-Agent':'Dalvik/2.1.0 (Linux; U; Android 11; M2012K10C Build/RP1A.200720.011)'}}
|
||||
|
||||
def localProxy(self, param):
|
||||
data=self.fetch(f'{self.phost}{param.get("path")}{self.phz}',headers={'User-Agent':'Dalvik/2.1.0 (Linux; U; Android 11; M2012K10C Build/RP1A.200720.011)'})
|
||||
def decrypt(encrypted_text):
|
||||
try:
|
||||
key = base64.urlsafe_b64decode("iM41VipvCFtToAFFRExEXw==")
|
||||
iv = base64.urlsafe_b64decode("0AXRTXzmMSrlRSemWb4sVQ==")
|
||||
cipher = AES.new(key, AES.MODE_CBC, iv)
|
||||
decrypted_padded = cipher.decrypt(encrypted_text)
|
||||
decrypted_data = unpad(decrypted_padded, AES.block_size)
|
||||
return decrypted_data
|
||||
except (binascii.Error, ValueError):
|
||||
return None
|
||||
return [200, data.headers.get('Content-Type'), decrypt(data.content)]
|
||||
|
||||
@@ -0,0 +1,263 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# by @嗷呜
|
||||
import json
|
||||
import sys
|
||||
import time
|
||||
import uuid
|
||||
from base64 import b64decode, b64encode
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Hash import SHA256, MD5
|
||||
from Crypto.PublicKey import RSA
|
||||
from Crypto.Signature import pkcs1_15
|
||||
from Crypto.Util.Padding import unpad
|
||||
|
||||
sys.path.append('..')
|
||||
from base.spider import Spider
|
||||
|
||||
|
||||
class Spider(Spider):
|
||||
|
||||
def init(self, extend=""):
|
||||
self.host, self.appKey, self.rsakey = self.userinfo()
|
||||
pass
|
||||
|
||||
def getName(self):
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
def homeContent(self, filter):
|
||||
data = self.fetch(f"{self.host}/api.php/zjv6.vod/types", headers=self.getheader()).json()
|
||||
dy = {"class": "类型", "area": "地区", "lang": "语言", "year": "年份", "letter": "字母", "by": "排序", }
|
||||
filters = {}
|
||||
classes = []
|
||||
json_data = data['data']['list']
|
||||
for item in json_data:
|
||||
has_non_empty_field = False
|
||||
jsontype_extend = item["type_extend"]
|
||||
jsontype_extend['by'] = '按更新,按播放,按评分,按收藏'
|
||||
classes.append({"type_name": item["type_name"], "type_id": item["type_id"]})
|
||||
for key in dy:
|
||||
if key in jsontype_extend and jsontype_extend[key].strip() != "":
|
||||
has_non_empty_field = True
|
||||
break
|
||||
if has_non_empty_field:
|
||||
filters[str(item["type_id"])] = []
|
||||
for dkey in jsontype_extend:
|
||||
if dkey in dy and jsontype_extend[dkey].strip() != "":
|
||||
values = jsontype_extend[dkey].split(",")
|
||||
sl = {'按更新': 'time', '按播放': 'hits', '按评分': 'score', '按收藏': 'store_num'}
|
||||
value_array = [
|
||||
{"n": value.strip(), "v": sl[value.strip()] if dkey == "by" else value.strip()}
|
||||
for value in values
|
||||
if value.strip() != ""
|
||||
]
|
||||
filters[str(item["type_id"])].append(
|
||||
{"key": dkey, "name": dy[dkey], "value": value_array}
|
||||
)
|
||||
result = {"class": classes, "filters": filters}
|
||||
return result
|
||||
|
||||
def homeVideoContent(self):
|
||||
data = self.fetch(f"{self.host}/api.php/zjv6.vod/vodPhbAll", headers=self.getheader()).json()
|
||||
return {'list': data['data']['list'][0]['vod_list']}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
params = {
|
||||
"type": tid,
|
||||
"class": extend.get('class', ''),
|
||||
"lang": extend.get('lang', ''),
|
||||
"area": extend.get('area', ''),
|
||||
"year": extend.get('year', ''),
|
||||
"by": extend.get('by', ''),
|
||||
"page": pg,
|
||||
"limit": "12"
|
||||
}
|
||||
data = self.fetch(f"{self.host}/api.php/zjv6.vod", headers=self.getheader(), params=params).json()
|
||||
result = {}
|
||||
result['list'] = data['data']['list']
|
||||
result['page'] = pg
|
||||
result['pagecount'] = 9999
|
||||
result['limit'] = 90
|
||||
result['total'] = 999999
|
||||
return result
|
||||
|
||||
def detailContent(self, ids):
|
||||
data = self.fetch(f"{self.host}/api.php/zjv6.vod/detail?vod_id={ids[0]}&rel_limit=10",
|
||||
headers=self.getheader()).json()
|
||||
vod = data['data']
|
||||
v, np = {'vod_play_from': [], 'vod_play_url': []}, {}
|
||||
for i in vod['vod_play_list']:
|
||||
n = i['player_info']['show']
|
||||
np[n] = []
|
||||
for j in i['urls']:
|
||||
j['parse'] = i['player_info']['parse2']
|
||||
nm = j.pop('name')
|
||||
np[n].append(f"{nm}${self.e64(json.dumps(j))}")
|
||||
for key, value in np.items():
|
||||
v['vod_play_from'].append(key)
|
||||
v['vod_play_url'].append('#'.join(value))
|
||||
v['vod_play_from'] = '$$$'.join(v['vod_play_from'])
|
||||
v['vod_play_url'] = '$$$'.join(v['vod_play_url'])
|
||||
vod.update(v)
|
||||
vod.pop('vod_play_list', None)
|
||||
vod.pop('type', None)
|
||||
return {'list': [vod]}
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
data = self.fetch(f"{self.host}/api.php/zjv6.vod?page={pg}&limit=20&wd={key}", headers=self.getheader()).json()
|
||||
return {'list': data['data']['list'], 'page': pg}
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
ids = json.loads(self.d64(id))
|
||||
target_url = ids['url']
|
||||
try:
|
||||
parse_str = ids.get('parse', '')
|
||||
if parse_str:
|
||||
parse_urls = parse_str.split(',')
|
||||
result_url = self.try_all_parses(parse_urls, target_url)
|
||||
if result_url:
|
||||
return {
|
||||
'parse': 0,
|
||||
'url': result_url,
|
||||
'header': {'User-Agent': 'dart:io'}
|
||||
}
|
||||
return {
|
||||
'parse': 1,
|
||||
'url': target_url,
|
||||
'header': {'User-Agent': 'dart:io'}
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return {
|
||||
'parse': 1,
|
||||
'url': target_url,
|
||||
'header': {'User-Agent': 'dart:io'}
|
||||
}
|
||||
|
||||
def liveContent(self, url):
|
||||
pass
|
||||
|
||||
def localProxy(self, param):
|
||||
pass
|
||||
|
||||
def userinfo(self):
|
||||
t = str(int(time.time() * 1000))
|
||||
uid = self.generate_uid()
|
||||
sign = self.md5(f"appKey=3bbf7348cf314874883a18d6b6fcf67a&uid={uid}&time={t}")
|
||||
headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36',
|
||||
'Connection': 'Keep-Alive',
|
||||
'appKey': '3bbf7348cf314874883a18d6b6fcf67a',
|
||||
'uid': uid,
|
||||
'time': t,
|
||||
'sign': sign,
|
||||
}
|
||||
|
||||
params = {
|
||||
'access_token': '74d5879931b9774be10dee3d8c51008e',
|
||||
}
|
||||
|
||||
response = self.fetch('https://gitee.com/api/v5/repos/aycapp/openapi/contents/wawaconf.txt', params=params,
|
||||
headers=headers).json()
|
||||
data = json.loads(self.decrypt(response['content']))
|
||||
return data['baseUrl'], data['appKey'], data['appSecret']
|
||||
|
||||
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 md5(self, text):
|
||||
h = MD5.new()
|
||||
h.update(text.encode('utf-8'))
|
||||
return h.hexdigest()
|
||||
|
||||
def generate_uid(self):
|
||||
return uuid.uuid4().hex
|
||||
|
||||
def getheader(self):
|
||||
t = str(int(time.time() * 1000))
|
||||
uid = self.generate_uid()
|
||||
sign = self.sign_message(f"appKey={self.appKey}&time={t}&uid={uid}")
|
||||
headers = {
|
||||
'User-Agent': 'okhttp/4.9.3',
|
||||
'Connection': 'Keep-Alive',
|
||||
'uid': uid,
|
||||
'time': t,
|
||||
'appKey': self.appKey,
|
||||
'sign': sign,
|
||||
}
|
||||
return headers
|
||||
|
||||
def decrypt(self, encrypted_data):
|
||||
key = b64decode('Crm4FXWkk5JItpYirFDpqg==')
|
||||
cipher = AES.new(key, AES.MODE_ECB)
|
||||
encrypted = bytes.fromhex(self.d64(encrypted_data))
|
||||
decrypted = cipher.decrypt(encrypted)
|
||||
unpadded = unpad(decrypted, AES.block_size)
|
||||
return unpadded.decode('utf-8')
|
||||
|
||||
def sign_message(self, message):
|
||||
private_key_str = f"-----BEGIN PRIVATE KEY-----\n{self.rsakey}\n-----END PRIVATE KEY-----"
|
||||
private_key = RSA.import_key(private_key_str)
|
||||
message_hash = SHA256.new(message.encode('utf-8'))
|
||||
signature = pkcs1_15.new(private_key).sign(message_hash)
|
||||
signature_b64 = b64encode(signature).decode('utf-8')
|
||||
return signature_b64
|
||||
|
||||
def fetch_url(self, parse_url, target_url):
|
||||
try:
|
||||
response = self.fetch(f"{parse_url.replace('..', '.')}{target_url}",
|
||||
headers={"user-agent": "okhttp/4.1.0/luob.app"}, timeout=5)
|
||||
if response.status_code == 200:
|
||||
try:
|
||||
data = response.json()
|
||||
result_url = data.get('url') or data.get('data', {}).get('url')
|
||||
if result_url:
|
||||
return result_url
|
||||
except:
|
||||
pass
|
||||
return None
|
||||
except:
|
||||
return None
|
||||
|
||||
def try_all_parses(self, parse_urls, target_url):
|
||||
with ThreadPoolExecutor(max_workers=(len(parse_urls))) as executor:
|
||||
future_to_url = {
|
||||
executor.submit(self.fetch_url, parse_url.strip(), target_url): parse_url
|
||||
for parse_url in parse_urls if parse_url.strip()
|
||||
}
|
||||
|
||||
for future in as_completed(future_to_url):
|
||||
try:
|
||||
result = future.result()
|
||||
if result:
|
||||
return result
|
||||
except:
|
||||
continue
|
||||
return None
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
import re
|
||||
import sys
|
||||
from Crypto.Hash import MD5
|
||||
sys.path.append("..")
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Util.Padding import pad, unpad
|
||||
from urllib.parse import quote, urlparse
|
||||
from base64 import b64encode, b64decode
|
||||
import json
|
||||
import time
|
||||
from base.spider import Spider
|
||||
|
||||
|
||||
class Spider(Spider):
|
||||
|
||||
def init(self, extend=""):
|
||||
self.host = 'https://guowaiju.com'
|
||||
self.did=self.getdid()
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def action(self, action):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
def homeContent(self, filter):
|
||||
data = self.getdata("/api.php/getappapi.index/initV119")
|
||||
dy = {"class": "类型", "area": "地区", "lang": "语言", "year": "年份", "letter": "字母", "by": "排序",
|
||||
"sort": "排序"}
|
||||
filters = {}
|
||||
classes = []
|
||||
json_data = data["type_list"]
|
||||
homedata = data["banner_list"][8:]
|
||||
for item in json_data:
|
||||
if item["type_name"] == "全部":
|
||||
continue
|
||||
has_non_empty_field = False
|
||||
jsontype_extend = json.loads(item["type_extend"])
|
||||
homedata.extend(item["recommend_list"])
|
||||
jsontype_extend["sort"] = "最新,最热,最赞"
|
||||
classes.append({"type_name": item["type_name"], "type_id": item["type_id"]})
|
||||
for key in dy:
|
||||
if key in jsontype_extend and jsontype_extend[key].strip() != "":
|
||||
has_non_empty_field = True
|
||||
break
|
||||
if has_non_empty_field:
|
||||
filters[str(item["type_id"])] = []
|
||||
for dkey in jsontype_extend:
|
||||
if dkey in dy and jsontype_extend[dkey].strip() != "":
|
||||
values = jsontype_extend[dkey].split(",")
|
||||
value_array = [{"n": value.strip(), "v": value.strip()} for value in values if
|
||||
value.strip() != ""]
|
||||
filters[str(item["type_id"])].append({"key": dkey, "name": dy[dkey], "value": value_array})
|
||||
result = {}
|
||||
result["class"] = classes
|
||||
result["filters"] = filters
|
||||
result["list"] = homedata[1:]
|
||||
return result
|
||||
|
||||
def homeVideoContent(self):
|
||||
pass
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
body = {"area": extend.get('area', '全部'), "year": extend.get('year', '全部'), "type_id": tid, "page": pg,
|
||||
"sort": extend.get('sort', '最新'), "lang": extend.get('lang', '全部'),
|
||||
"class": extend.get('class', '全部')}
|
||||
result = {}
|
||||
data = self.getdata("/api.php/getappapi.index/typeFilterVodList", body)
|
||||
result["list"] = data["recommend_list"]
|
||||
result["page"] = pg
|
||||
result["pagecount"] = 9999
|
||||
result["limit"] = 90
|
||||
result["total"] = 999999
|
||||
return result
|
||||
|
||||
def detailContent(self, ids):
|
||||
body = f"vod_id={ids[0]}"
|
||||
data = self.getdata("/api.php/getappapi.index/vodDetail", body)
|
||||
vod = data["vod"]
|
||||
play = []
|
||||
names = []
|
||||
for itt in reversed(data["vod_play_list"]):
|
||||
a = []
|
||||
names.append(itt["player_info"]["show"])
|
||||
for it in itt['urls']:
|
||||
it['user_agent'] = itt["player_info"].get("user_agent")
|
||||
it["parse"] = itt["player_info"].get("parse")
|
||||
a.append(f"{it['name']}${self.e64(json.dumps(it))}")
|
||||
play.append("#".join(a))
|
||||
vod["vod_play_from"] = "$$$".join(names)
|
||||
vod["vod_play_url"] = "$$$".join(play)
|
||||
result = {"list": [vod]}
|
||||
return result
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
body = f"keywords={key}&type_id=0&page={pg}"
|
||||
data = self.getdata("/api.php/getappapi.index/searchList", body)
|
||||
result = {"list": data["search_list"], "page": pg}
|
||||
return result
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
ids = json.loads(self.d64(id))
|
||||
h = {"User-Agent": (ids['user_agent'] or "okhttp/3.14.9")}
|
||||
try:
|
||||
if re.search(r'url=', ids['parse_api_url']):
|
||||
data = self.fetch(ids['parse_api_url'], headers=h, timeout=10).json()
|
||||
url = data.get('url') or data['data'].get('url')
|
||||
else:
|
||||
body = f"parse_api={ids.get('parse') or ids['parse_api_url'].replace(ids['url'], '')}&url={quote(self.aes(ids['url'], True))}&token={ids.get('token')}"
|
||||
b = self.getdata("/api.php/getappapi.index/vodParse", body)['json']
|
||||
url = json.loads(b)['url']
|
||||
if 'error' in url: raise ValueError(f"解析失败: {url}")
|
||||
p = 0
|
||||
except Exception as e:
|
||||
print('错误信息:', e)
|
||||
url, p = ids['url'], 1
|
||||
|
||||
if re.search(r'\.jpg|\.png|\.jpeg', url):
|
||||
url = self.Mproxy(url)
|
||||
result = {}
|
||||
result["parse"] = p
|
||||
result["url"] = url
|
||||
result["header"] = h
|
||||
return result
|
||||
|
||||
def localProxy(self, param):
|
||||
headers = {"User-Agent": "okhttp/3.14.9"}
|
||||
url = self.d64(param['url'])
|
||||
ydata = self.fetch(url, headers=headers, allow_redirects=False)
|
||||
data = ydata.content.decode('utf-8')
|
||||
if ydata.headers.get('Location'):
|
||||
url = ydata.headers['Location']
|
||||
data = self.fetch(url, headers=headers).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
|
||||
for index, string in enumerate(lines):
|
||||
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
|
||||
if string.split('.')[-1].split('?')[0] == 'm3u8':
|
||||
string = self.Mproxy(string)
|
||||
lines[index] = string
|
||||
data = '\n'.join(lines)
|
||||
return [200, "application/vnd.apple.mpegur", data]
|
||||
|
||||
def getdid(self):
|
||||
did=self.getCache('did')
|
||||
if not did:
|
||||
t = str(int(time.time()))
|
||||
did = self.md5(t)
|
||||
self.setCache('did', did)
|
||||
return did
|
||||
|
||||
def aes(self, text, b=None):
|
||||
key = b"7xv16h7qgkrs9b1p"
|
||||
cipher = AES.new(key, AES.MODE_CBC, key)
|
||||
if b:
|
||||
ct_bytes = cipher.encrypt(pad(text.encode("utf-8"), AES.block_size))
|
||||
ct = b64encode(ct_bytes).decode("utf-8")
|
||||
return ct
|
||||
else:
|
||||
pt = unpad(cipher.decrypt(b64decode(text)), AES.block_size)
|
||||
return pt.decode("utf-8")
|
||||
|
||||
def header(self):
|
||||
t = str(int(time.time()))
|
||||
header = {
|
||||
"User-Agent": "okhttp/3.14.9", "app-version-code": "110", "app-ui-mode": "light",
|
||||
"app-api-verify-time": t, "app-user-device-id": self.did,
|
||||
"app-api-verify-sign": self.aes(t, True),
|
||||
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"
|
||||
}
|
||||
return header
|
||||
|
||||
def getdata(self, path, data=None):
|
||||
vdata = self.post(f"{self.host}{path}", headers=self.header(), data=data, timeout=10).json()['data']
|
||||
data1 = self.aes(vdata)
|
||||
return json.loads(data1)
|
||||
|
||||
def Mproxy(self, url):
|
||||
return f"{self.getProxyUrl()}&url={self.e64(url)}&type=m3u8"
|
||||
|
||||
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 md5(self, text):
|
||||
h = MD5.new()
|
||||
h.update(text.encode('utf-8'))
|
||||
return h.hexdigest()
|
||||
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
import re
|
||||
import sys
|
||||
from Crypto.Hash import MD5
|
||||
sys.path.append("..")
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Util.Padding import pad, unpad
|
||||
from urllib.parse import quote, urlparse
|
||||
from base64 import b64encode, b64decode
|
||||
import json
|
||||
import time
|
||||
from base.spider import Spider
|
||||
|
||||
|
||||
class Spider(Spider):
|
||||
|
||||
def init(self, extend=""):
|
||||
self.host='https://www.qj1080.top'
|
||||
self.did=self.getdid()
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def action(self, action):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
def homeContent(self, filter):
|
||||
data = self.getdata("/api.php/getappapi.index/initV119")
|
||||
dy = {"class": "类型", "area": "地区", "lang": "语言", "year": "年份", "letter": "字母", "by": "排序",
|
||||
"sort": "排序"}
|
||||
filters = {}
|
||||
classes = []
|
||||
json_data = data["type_list"]
|
||||
homedata = data["banner_list"][8:]
|
||||
for item in json_data:
|
||||
if item["type_name"] == "全部":
|
||||
continue
|
||||
has_non_empty_field = False
|
||||
jsontype_extend = json.loads(item["type_extend"])
|
||||
homedata.extend(item["recommend_list"])
|
||||
jsontype_extend["sort"] = "最新,最热,最赞"
|
||||
classes.append({"type_name": item["type_name"], "type_id": item["type_id"]})
|
||||
for key in dy:
|
||||
if key in jsontype_extend and jsontype_extend[key].strip() != "":
|
||||
has_non_empty_field = True
|
||||
break
|
||||
if has_non_empty_field:
|
||||
filters[str(item["type_id"])] = []
|
||||
for dkey in jsontype_extend:
|
||||
if dkey in dy and jsontype_extend[dkey].strip() != "":
|
||||
values = jsontype_extend[dkey].split(",")
|
||||
value_array = [{"n": value.strip(), "v": value.strip()} for value in values if
|
||||
value.strip() != ""]
|
||||
filters[str(item["type_id"])].append({"key": dkey, "name": dy[dkey], "value": value_array})
|
||||
result = {}
|
||||
result["class"] = classes
|
||||
result["filters"] = filters
|
||||
result["list"] = homedata[1:]
|
||||
return result
|
||||
|
||||
def homeVideoContent(self):
|
||||
pass
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
body = {"area": extend.get('area', '全部'), "year": extend.get('year', '全部'), "type_id": tid, "page": pg,
|
||||
"sort": extend.get('sort', '最新'), "lang": extend.get('lang', '全部'),
|
||||
"class": extend.get('class', '全部')}
|
||||
result = {}
|
||||
data = self.getdata("/api.php/getappapi.index/typeFilterVodList", body)
|
||||
result["list"] = data["recommend_list"]
|
||||
result["page"] = pg
|
||||
result["pagecount"] = 9999
|
||||
result["limit"] = 90
|
||||
result["total"] = 999999
|
||||
return result
|
||||
|
||||
def detailContent(self, ids):
|
||||
body = f"vod_id={ids[0]}"
|
||||
data = self.getdata("/api.php/getappapi.index/vodDetail", body)
|
||||
vod = data["vod"]
|
||||
play = []
|
||||
names = []
|
||||
for itt in data["vod_play_list"]:
|
||||
a = []
|
||||
names.append(itt["player_info"]["show"])
|
||||
for it in itt['urls']:
|
||||
it['user_agent'] = itt["player_info"].get("user_agent")
|
||||
it["parse"] = itt["player_info"].get("parse")
|
||||
a.append(f"{it['name']}${self.e64(json.dumps(it))}")
|
||||
play.append("#".join(a))
|
||||
vod["vod_play_from"] = "$$$".join(names)
|
||||
vod["vod_play_url"] = "$$$".join(play)
|
||||
result = {"list": [vod]}
|
||||
return result
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
body = f"keywords={key}&type_id=0&page={pg}"
|
||||
data = self.getdata("/api.php/getappapi.index/searchList", body)
|
||||
result = {"list": data["search_list"], "page": pg}
|
||||
return result
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
ids = json.loads(self.d64(id))
|
||||
h = {"User-Agent": (ids['user_agent'] or "okhttp/3.14.9")}
|
||||
try:
|
||||
if re.search(r'url=', ids['parse_api_url']):
|
||||
data = self.fetch(ids['parse_api_url'], headers=h, timeout=10).json()
|
||||
url = data.get('url') or data['data'].get('url')
|
||||
else:
|
||||
body = f"parse_api={ids.get('parse') or ids['parse_api_url'].replace(ids['url'], '')}&url={quote(self.aes(ids['url'], True))}&token={ids.get('token')}"
|
||||
b = self.getdata("/api.php/getappapi.index/vodParse", body)['json']
|
||||
url = json.loads(b)['url']
|
||||
if 'error' in url: raise ValueError(f"解析失败: {url}")
|
||||
p = 0
|
||||
except Exception as e:
|
||||
print('错误信息:', e)
|
||||
url, p = ids['url'], 1
|
||||
|
||||
if re.search(r'\.jpg|\.png|\.jpeg', url):
|
||||
url = self.Mproxy(url)
|
||||
result = {}
|
||||
result["parse"] = p
|
||||
result["url"] = url
|
||||
result["header"] = h
|
||||
return result
|
||||
|
||||
def localProxy(self, param):
|
||||
headers = {"User-Agent": "okhttp/3.14.9"}
|
||||
url = self.d64(param['url'])
|
||||
ydata = self.fetch(url, headers=headers, allow_redirects=False)
|
||||
data = ydata.content.decode('utf-8')
|
||||
if ydata.headers.get('Location'):
|
||||
url = ydata.headers['Location']
|
||||
data = self.fetch(url, headers=headers).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
|
||||
for index, string in enumerate(lines):
|
||||
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
|
||||
if string.split('.')[-1].split('?')[0] == 'm3u8':
|
||||
string = self.Mproxy(string)
|
||||
lines[index] = string
|
||||
data = '\n'.join(lines)
|
||||
return [200, "application/vnd.apple.mpegur", data]
|
||||
|
||||
def getdid(self):
|
||||
did=self.getCache('did')
|
||||
if not did:
|
||||
t = str(int(time.time()))
|
||||
did = self.md5(t)
|
||||
self.setCache('did', did)
|
||||
return did
|
||||
|
||||
def aes(self, text, b=None):
|
||||
key = b"8t2L9x5Qz4A7p3y6"
|
||||
cipher = AES.new(key, AES.MODE_CBC, key)
|
||||
if b:
|
||||
ct_bytes = cipher.encrypt(pad(text.encode("utf-8"), AES.block_size))
|
||||
ct = b64encode(ct_bytes).decode("utf-8")
|
||||
return ct
|
||||
else:
|
||||
pt = unpad(cipher.decrypt(b64decode(text)), AES.block_size)
|
||||
return pt.decode("utf-8")
|
||||
|
||||
def header(self):
|
||||
t = str(int(time.time()))
|
||||
header = {
|
||||
"User-Agent": "okhttp/3.14.9", "app-version-code": "666", "app-ui-mode": "light",
|
||||
"app-api-verify-time": t, "app-user-device-id": self.did,
|
||||
"app-api-verify-sign": self.aes(t, True),
|
||||
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"
|
||||
}
|
||||
return header
|
||||
|
||||
def getdata(self, path, data=None):
|
||||
vdata = self.post(f"{self.host}{path}", headers=self.header(), data=data, timeout=10).json()['data']
|
||||
data1 = self.aes(vdata)
|
||||
return json.loads(data1)
|
||||
|
||||
def Mproxy(self, url):
|
||||
return f"{self.getProxyUrl()}&url={self.e64(url)}&type=m3u8"
|
||||
|
||||
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 md5(self, text):
|
||||
h = MD5.new()
|
||||
h.update(text.encode('utf-8'))
|
||||
return h.hexdigest()
|
||||
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# by @嗷呜
|
||||
import sys
|
||||
sys.path.append('..')
|
||||
from base.spider import Spider
|
||||
|
||||
class Spider(Spider):
|
||||
|
||||
def init(self, extend=""):
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
host='http://item.xpgcom.com'
|
||||
|
||||
headers = {
|
||||
"User-Agent": "okhttp/3.12.11"
|
||||
}
|
||||
|
||||
def homeContent(self, filter):
|
||||
data = self.fetch(f"{self.host}/api.php/v2.vod/androidtypes",headers=self.headers,).json()
|
||||
dy = {
|
||||
"classes": "类型",
|
||||
"areas": "地区",
|
||||
"years": "年份",
|
||||
"sortby": "排序",
|
||||
}
|
||||
filters = {}
|
||||
classes = []
|
||||
for item in data['data']:
|
||||
has_non_empty_field = False
|
||||
item['sortby'] = ['updatetime', 'hits', 'score']
|
||||
demos = ['时间', '人气', '评分']
|
||||
classes.append({"type_name": item["type_name"], "type_id": str(item["type_id"])})
|
||||
for key in dy:
|
||||
if key in item and len(item[key]) > 1:
|
||||
has_non_empty_field = True
|
||||
break
|
||||
if has_non_empty_field:
|
||||
filters[str(item["type_id"])] = []
|
||||
for dkey in item:
|
||||
if dkey in dy and len(item[dkey]) > 1:
|
||||
values = item[dkey]
|
||||
value_array = [
|
||||
{"n": demos[idx] if dkey == "sortby" else value.strip(), "v": value.strip()}
|
||||
for idx, value in enumerate(values)
|
||||
if value.strip() != ""
|
||||
]
|
||||
filters[str(item["type_id"])].append(
|
||||
{"key": dkey, "name": dy[dkey], "value": value_array}
|
||||
)
|
||||
result = {}
|
||||
result["class"] = classes
|
||||
result["filters"] = filters
|
||||
return result
|
||||
|
||||
def homeVideoContent(self):
|
||||
rsp = self.fetch(f"{self.host}/api.php/v2.main/androidhome", headers=self.headers).json()
|
||||
videos = []
|
||||
for i in rsp['data']['list']:videos.extend(self.getlist(i['list']))
|
||||
return {'list':videos}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
params = {
|
||||
"page": pg,
|
||||
"type": tid,
|
||||
"area":extend.get('areaes',''),
|
||||
"year":extend.get('yeares',''),
|
||||
"sortby":extend.get('sortby',''),
|
||||
"class":extend.get('classes','')
|
||||
}
|
||||
params={i:v for i,v in params.items() if v}
|
||||
rsp = self.fetch(f'{self.host}/api.php/v2.vod/androidfilter10086', headers=self.headers, params=params).json()
|
||||
result = {}
|
||||
result['list'] = self.getlist(rsp['data'])
|
||||
result['page'] = pg
|
||||
result['pagecount'] = 9999
|
||||
result['limit'] = 90
|
||||
result['total'] = 999999
|
||||
return result
|
||||
|
||||
def detailContent(self, ids):
|
||||
rsp = self.fetch(f'{self.host}/api.php/v3.vod/androiddetail2?vod_id={ids[0]}', headers=self.headers).json()
|
||||
v = rsp['data']
|
||||
vod = {
|
||||
'vod_year':v.get('year'),
|
||||
'vod_area':v.get('area'),
|
||||
'vod_lang':v.get('lang'),
|
||||
'type_name':v.get('className'),
|
||||
'vod_actor':v.get('actor'),
|
||||
'vod_director':v.get('director'),
|
||||
'vod_content':v.get('content'),
|
||||
'vod_play_from': '小苹果',
|
||||
'vod_play_url': '#'.join([f"{i['key']}${i['url']}" for i in v['urls']])
|
||||
}
|
||||
return {'list':[vod]}
|
||||
|
||||
def searchContent(self, key, quick, pg='1'):
|
||||
rsp = self.fetch(f'{self.host}/api.php/v2.vod/androidsearch10086?page={pg}&wd={key}', headers=self.headers).json()
|
||||
return {'list':self.getlist(rsp['data']),'page':pg}
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
header = {
|
||||
'user_id': 'XPGBOX',
|
||||
'token2': 'SnAXiSW8vScXE0Z9aDOnK5xffbO75w1+uPom3WjnYfVEA1oWtUdi2Ihy1N8=',
|
||||
'version': 'XPGBOX com.phoenix.tv1.5.7',
|
||||
'hash': 'd78a',
|
||||
'screenx': '2345',
|
||||
'user-agent': 'Lavf/58.12.100',
|
||||
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36',
|
||||
'token': 'ElEDlwCVgXcFHFhddiq2JKteHofExRBUrfNlmHrWetU3VVkxnzJAodl52N9EUFS+Dig2A/fBa/V9RuoOZRBjYvI+GW8kx3+xMlRecaZuECdb/3AdGkYpkjW3wCnpMQxf8vVeCz5zQLDr8l8bUChJiLLJLGsI+yiNskiJTZz9HiGBZhZuWh1mV1QgYah5CLTbSz8=',
|
||||
'timestamp': '1743060300',
|
||||
'screeny': '1065',
|
||||
}
|
||||
if 'http' not in id:id=f"http://c.xpgtv.net/m3u8/{id}.m3u8"
|
||||
return {"parse": 0, "url": id, "header": header}
|
||||
|
||||
def localProxy(self, param):
|
||||
pass
|
||||
|
||||
def getlist(self,data):
|
||||
videos = []
|
||||
for vod in data:
|
||||
r=f"更新至{vod.get('updateInfo')}" if vod.get('updateInfo') else ''
|
||||
videos.append({
|
||||
"vod_id": vod['id'],
|
||||
"vod_name": vod['name'],
|
||||
"vod_pic": vod['pic'],
|
||||
"vod_remarks": r or vod['score']
|
||||
})
|
||||
return videos
|
||||
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
import re
|
||||
import sys
|
||||
from Crypto.Hash import MD5
|
||||
sys.path.append("..")
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Util.Padding import pad, unpad
|
||||
from urllib.parse import quote, urlparse
|
||||
from base64 import b64encode, b64decode
|
||||
import json
|
||||
import time
|
||||
from base.spider import Spider
|
||||
|
||||
|
||||
class Spider(Spider):
|
||||
|
||||
def init(self, extend=""):
|
||||
self.host = 'http://47.122.22.78'
|
||||
self.did=self.getdid()
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def action(self, action):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
def homeContent(self, filter):
|
||||
data = self.getdata("/api.php/getappapi.index/initV119")
|
||||
dy = {"class": "类型", "area": "地区", "lang": "语言", "year": "年份", "letter": "字母", "by": "排序",
|
||||
"sort": "排序"}
|
||||
filters = {}
|
||||
classes = []
|
||||
json_data = data["type_list"]
|
||||
homedata = data["banner_list"][8:]
|
||||
for item in json_data:
|
||||
if item["type_name"] == "全部":
|
||||
continue
|
||||
has_non_empty_field = False
|
||||
jsontype_extend = json.loads(item["type_extend"])
|
||||
homedata.extend(item["recommend_list"])
|
||||
jsontype_extend["sort"] = "最新,最热,最赞"
|
||||
classes.append({"type_name": item["type_name"], "type_id": item["type_id"]})
|
||||
for key in dy:
|
||||
if key in jsontype_extend and jsontype_extend[key].strip() != "":
|
||||
has_non_empty_field = True
|
||||
break
|
||||
if has_non_empty_field:
|
||||
filters[str(item["type_id"])] = []
|
||||
for dkey in jsontype_extend:
|
||||
if dkey in dy and jsontype_extend[dkey].strip() != "":
|
||||
values = jsontype_extend[dkey].split(",")
|
||||
value_array = [{"n": value.strip(), "v": value.strip()} for value in values if
|
||||
value.strip() != ""]
|
||||
filters[str(item["type_id"])].append({"key": dkey, "name": dy[dkey], "value": value_array})
|
||||
result = {}
|
||||
result["class"] = classes
|
||||
result["filters"] = filters
|
||||
result["list"] = homedata[1:]
|
||||
return result
|
||||
|
||||
def homeVideoContent(self):
|
||||
pass
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
body = {"area": extend.get('area', '全部'), "year": extend.get('year', '全部'), "type_id": tid, "page": pg,
|
||||
"sort": extend.get('sort', '最新'), "lang": extend.get('lang', '全部'),
|
||||
"class": extend.get('class', '全部')}
|
||||
result = {}
|
||||
data = self.getdata("/api.php/getappapi.index/typeFilterVodList", body)
|
||||
result["list"] = data["recommend_list"]
|
||||
result["page"] = pg
|
||||
result["pagecount"] = 9999
|
||||
result["limit"] = 90
|
||||
result["total"] = 999999
|
||||
return result
|
||||
|
||||
def detailContent(self, ids):
|
||||
body = f"vod_id={ids[0]}"
|
||||
data = self.getdata("/api.php/getappapi.index/vodDetail", body)
|
||||
vod = data["vod"]
|
||||
play = []
|
||||
names = []
|
||||
for itt in data["vod_play_list"]:
|
||||
a = []
|
||||
names.append(itt["player_info"]["show"])
|
||||
for it in itt['urls']:
|
||||
it['user_agent'] = itt["player_info"].get("user_agent")
|
||||
it["parse"] = itt["player_info"].get("parse")
|
||||
a.append(f"{it['name']}${self.e64(json.dumps(it))}")
|
||||
play.append("#".join(a))
|
||||
vod["vod_play_from"] = "$$$".join(names)
|
||||
vod["vod_play_url"] = "$$$".join(play)
|
||||
result = {"list": [vod]}
|
||||
return result
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
body = f"keywords={key}&type_id=0&page={pg}"
|
||||
data = self.getdata("/api.php/getappapi.index/searchList", body)
|
||||
result = {"list": data["search_list"], "page": pg}
|
||||
return result
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
ids = json.loads(self.d64(id))
|
||||
h = {"User-Agent": (ids['user_agent'] or "okhttp/3.14.9")}
|
||||
try:
|
||||
if re.search(r'url=', ids['parse_api_url']):
|
||||
data = self.fetch(ids['parse_api_url'], headers=h, timeout=10).json()
|
||||
url = data.get('url') or data['data'].get('url')
|
||||
else:
|
||||
body = f"parse_api={ids.get('parse') or ids['parse_api_url'].replace(ids['url'], '')}&url={quote(self.aes(ids['url'], True))}&token={ids.get('token')}"
|
||||
b = self.getdata("/api.php/getappapi.index/vodParse", body)['json']
|
||||
url = json.loads(b)['url']
|
||||
if 'error' in url: raise ValueError(f"解析失败: {url}")
|
||||
p = 0
|
||||
except Exception as e:
|
||||
print('错误信息:', e)
|
||||
url, p = ids['url'], 1
|
||||
|
||||
if re.search(r'\.jpg|\.png|\.jpeg', url):
|
||||
url = self.Mproxy(url)
|
||||
result = {}
|
||||
result["parse"] = p
|
||||
result["url"] = url
|
||||
result["header"] = h
|
||||
return result
|
||||
|
||||
def localProxy(self, param):
|
||||
return self.Mlocal(param)
|
||||
|
||||
def aes(self, text, b=None):
|
||||
key = b"1234567890123456"
|
||||
cipher = AES.new(key, AES.MODE_CBC, key)
|
||||
if b:
|
||||
ct_bytes = cipher.encrypt(pad(text.encode("utf-8"), AES.block_size))
|
||||
ct = b64encode(ct_bytes).decode("utf-8")
|
||||
return ct
|
||||
else:
|
||||
pt = unpad(cipher.decrypt(b64decode(text)), AES.block_size)
|
||||
return pt.decode("utf-8")
|
||||
|
||||
def header(self):
|
||||
t = str(int(time.time()))
|
||||
header = {"Referer": self.host,
|
||||
"User-Agent": "okhttp/3.14.9", "app-version-code": "101", "app-ui-mode": "light",
|
||||
"app-api-verify-time": t, "app-user-device-id": self.did,
|
||||
"app-api-verify-sign": self.aes(t, True),
|
||||
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"}
|
||||
return header
|
||||
|
||||
def getdid(self):
|
||||
did=self.getCache('did')
|
||||
if not did:
|
||||
t = str(int(time.time()))
|
||||
did = self.md5(t)
|
||||
self.setCache('did', did)
|
||||
return did
|
||||
|
||||
def getdata(self, path, data=None):
|
||||
vdata = self.post(f"{self.host}{path}", headers=self.header(), data=data, timeout=10).json()['data']
|
||||
data1 = self.aes(vdata)
|
||||
return json.loads(data1)
|
||||
|
||||
def Mproxy(self, url):
|
||||
return f"{self.getProxyUrl()}&url={self.e64(url)}&type=m3u8"
|
||||
|
||||
def Mlocal(self, param, header=None):
|
||||
url = self.d64(param["url"])
|
||||
ydata = self.fetch(url, headers=header, allow_redirects=False)
|
||||
data = ydata.content.decode('utf-8')
|
||||
if ydata.headers.get('Location'):
|
||||
url = ydata.headers['Location']
|
||||
data = self.fetch(url, headers=header).content.decode('utf-8')
|
||||
parsed_url = urlparse(url)
|
||||
durl = parsed_url.scheme + "://" + parsed_url.netloc
|
||||
lines = data.strip().split('\n')
|
||||
for index, string in enumerate(lines):
|
||||
if '#EXT' not in string and 'http' not in string:
|
||||
last_slash_index = string.rfind('/')
|
||||
lpath = string[:last_slash_index + 1]
|
||||
lines[index] = durl + ('' if lpath.startswith('/') else '/') + lpath
|
||||
data = '\n'.join(lines)
|
||||
return [200, "application/vnd.apple.mpegur", data]
|
||||
|
||||
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 md5(self, text):
|
||||
h = MD5.new()
|
||||
h.update(text.encode('utf-8'))
|
||||
return h.hexdigest()
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# by @嗷呜
|
||||
import re
|
||||
import sys
|
||||
from Crypto.Hash import MD5
|
||||
sys.path.append("..")
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Util.Padding import pad, unpad
|
||||
from urllib.parse import quote, urlparse
|
||||
from base64 import b64encode, b64decode
|
||||
import json
|
||||
import time
|
||||
from base.spider import Spider
|
||||
|
||||
class Spider(Spider):
|
||||
|
||||
def init(self, extend=""):
|
||||
self.host = self.gethost()
|
||||
pass
|
||||
|
||||
def getName(self):
|
||||
pass
|
||||
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def action(self, action):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
def homeContent(self, filter):
|
||||
data = self.getdata("/api.php/getappapi.index/initV119")
|
||||
dy = {"class": "类型", "area": "地区", "lang": "语言", "year": "年份", "letter": "字母", "by": "排序",
|
||||
"sort": "排序"}
|
||||
filters = {}
|
||||
classes = []
|
||||
json_data = data["type_list"]
|
||||
homedata = data["banner_list"][8:]
|
||||
for item in json_data:
|
||||
if item["type_name"] == "全部":
|
||||
continue
|
||||
has_non_empty_field = False
|
||||
jsontype_extend = json.loads(item["type_extend"])
|
||||
homedata.extend(item["recommend_list"])
|
||||
jsontype_extend["sort"] = "最新,最热,最赞"
|
||||
classes.append({"type_name": item["type_name"], "type_id": item["type_id"]})
|
||||
for key in dy:
|
||||
if key in jsontype_extend and jsontype_extend[key].strip() != "":
|
||||
has_non_empty_field = True
|
||||
break
|
||||
if has_non_empty_field:
|
||||
filters[str(item["type_id"])] = []
|
||||
for dkey in jsontype_extend:
|
||||
if dkey in dy and jsontype_extend[dkey].strip() != "":
|
||||
values = jsontype_extend[dkey].split(",")
|
||||
value_array = [{"n": value.strip(), "v": value.strip()} for value in values if
|
||||
value.strip() != ""]
|
||||
filters[str(item["type_id"])].append({"key": dkey, "name": dy[dkey], "value": value_array})
|
||||
result = {}
|
||||
result["class"] = classes
|
||||
result["filters"] = filters
|
||||
result["list"] = homedata[1:]
|
||||
return result
|
||||
|
||||
def homeVideoContent(self):
|
||||
pass
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
body = {"area": extend.get('area', '全部'), "year": extend.get('year', '全部'), "type_id": tid, "page": pg,
|
||||
"sort": extend.get('sort', '最新'), "lang": extend.get('lang', '全部'),
|
||||
"class": extend.get('class', '全部')}
|
||||
result = {}
|
||||
data = self.getdata("/api.php/getappapi.index/typeFilterVodList", body)
|
||||
result["list"] = data["recommend_list"]
|
||||
result["page"] = pg
|
||||
result["pagecount"] = 9999
|
||||
result["limit"] = 90
|
||||
result["total"] = 999999
|
||||
return result
|
||||
|
||||
def detailContent(self, ids):
|
||||
body = f"vod_id={ids[0]}"
|
||||
data = self.getdata("/api.php/getappapi.index/vodDetail", body)
|
||||
vod = data["vod"]
|
||||
play = []
|
||||
names = []
|
||||
for itt in data["vod_play_list"]:
|
||||
a = []
|
||||
names.append(itt["player_info"]["show"])
|
||||
for it in itt['urls']:
|
||||
it['user_agent']=itt["player_info"].get("user_agent")
|
||||
it["parse"]=itt["player_info"].get("parse")
|
||||
a.append(f"{it['name']}${self.e64(json.dumps(it))}")
|
||||
play.append("#".join(a))
|
||||
vod["vod_play_from"] = "$$$".join(names)
|
||||
vod["vod_play_url"] = "$$$".join(play)
|
||||
result = {"list": [vod]}
|
||||
return result
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
body = f"keywords={key}&type_id=0&page={pg}"
|
||||
data = self.getdata("/api.php/getappapi.index/searchList", body)
|
||||
result = {"list": data["search_list"], "page": pg}
|
||||
return result
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
ids = json.loads(self.d64(id))
|
||||
h = {"User-Agent": (ids['user_agent'] or "okhttp/3.14.9")}
|
||||
try:
|
||||
if re.search(r'url=', ids['parse_api_url']):
|
||||
data = self.fetch(ids['parse_api_url'], headers=h, timeout=10).json()
|
||||
url = data.get('url') or data['data'].get('url')
|
||||
else:
|
||||
body = f"parse_api={ids.get('parse') or ids['parse_api_url'].replace(ids['url'], '')}&url={quote(self.aes(ids['url'], True))}&token={ids.get('token')}"
|
||||
b = self.getdata("/api.php/getappapi.index/vodParse", body)['json']
|
||||
url = json.loads(b)['url']
|
||||
if 'error' in url: raise ValueError(f"解析失败: {url}")
|
||||
p = 0
|
||||
except Exception as e:
|
||||
print('错误信息:', e)
|
||||
url, p = ids['url'], 1
|
||||
|
||||
if re.search(r'\.jpg|\.png|\.jpeg', url):
|
||||
url = self.Mproxy(url)
|
||||
result = {}
|
||||
result["parse"] = p
|
||||
result["url"] = url
|
||||
result["header"] = h
|
||||
return result
|
||||
|
||||
def localProxy(self, param):
|
||||
return self.Mlocal(param)
|
||||
|
||||
def gethost(self):
|
||||
headers = {
|
||||
'User-Agent': 'okhttp/3.14.9'
|
||||
}
|
||||
host = self.fetch('http://host.yyys.news/250123.txt', headers=headers).text
|
||||
return host.strip()
|
||||
|
||||
phend = {
|
||||
'User-Agent': 'Dalvik/2.1.0 (Linux; U; Android 11; M2012K10C Build/RP1A.200720.011)',
|
||||
'allowCrossProtocolRedirects': 'true'
|
||||
}
|
||||
|
||||
def aes(self, text,b=None):
|
||||
key = b"RuN9LRvwTRgpQnpK"
|
||||
cipher = AES.new(key, AES.MODE_CBC, key)
|
||||
if b:
|
||||
ct_bytes = cipher.encrypt(pad(text.encode("utf-8"), AES.block_size))
|
||||
ct = b64encode(ct_bytes).decode("utf-8")
|
||||
return ct
|
||||
else :
|
||||
pt = unpad(cipher.decrypt(b64decode(text)), AES.block_size)
|
||||
return pt.decode("utf-8")
|
||||
|
||||
def header(self):
|
||||
t = str(int(time.time()))
|
||||
header = {"Referer":self.host,
|
||||
"User-Agent": "okhttp/3.14.9", "app-version-code": "547", "app-ui-mode": "light",
|
||||
"app-api-verify-time": t, "app-user-device-id": self.md5(t),
|
||||
"app-api-verify-sign": self.aes(t,True),
|
||||
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"}
|
||||
return header
|
||||
|
||||
def getdata(self, path, data=None):
|
||||
vdata = self.post(f"{self.host}{path}", headers=self.header(), data=data, timeout=10).json()['data']
|
||||
data1 = self.aes(vdata)
|
||||
return json.loads(data1)
|
||||
|
||||
def Mproxy(self, url):
|
||||
return f"{self.getProxyUrl()}&url={self.e64(url)}&type=m3u8"
|
||||
|
||||
def Mlocal(self, param,header=None):
|
||||
url = self.d64(param["url"])
|
||||
ydata = self.fetch(url, headers=header, allow_redirects=False)
|
||||
data = ydata.content.decode('utf-8')
|
||||
if ydata.headers.get('Location'):
|
||||
url = ydata.headers['Location']
|
||||
data = self.fetch(url, headers=header).content.decode('utf-8')
|
||||
parsed_url = urlparse(url)
|
||||
durl = parsed_url.scheme + "://" + parsed_url.netloc
|
||||
lines = data.strip().split('\n')
|
||||
for index, string in enumerate(lines):
|
||||
if '#EXT' not in string and 'http' not in string:
|
||||
last_slash_index = string.rfind('/')
|
||||
lpath = string[:last_slash_index + 1]
|
||||
lines[index] = durl + ('' if lpath.startswith('/') else '/') + lpath
|
||||
data = '\n'.join(lines)
|
||||
return [200, "application/vnd.apple.mpegur", data]
|
||||
|
||||
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 md5(self, text):
|
||||
h = MD5.new()
|
||||
h.update(text.encode('utf-8'))
|
||||
return h.hexdigest()
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# by @嗷呜
|
||||
import json
|
||||
import random
|
||||
import string
|
||||
import sys
|
||||
from base64 import b64decode, b64encode
|
||||
from urllib.parse import quote, unquote
|
||||
sys.path.append('..')
|
||||
import concurrent.futures
|
||||
from base.spider import Spider
|
||||
|
||||
|
||||
class Spider(Spider):
|
||||
|
||||
def init(self, extend=""):
|
||||
pass
|
||||
|
||||
def getName(self):
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
host='http://w.dcmovie.top'
|
||||
|
||||
headers = {
|
||||
'User-Agent': 'okhttp/4.9.1',
|
||||
'mark-time': 'null',
|
||||
'fn-api-version': '1.3.2',
|
||||
'versionCode': '5',
|
||||
'product': 'gysg',
|
||||
}
|
||||
|
||||
def homeContent(self, filter):
|
||||
data=self.fetch(f"{self.host}/api.php/vod/type", headers=self.headers).json()
|
||||
result,filters,videos = {},{},[]
|
||||
classes = [{'type_id': i['type_name'], 'type_name': i['type_name']} for i in data['list'][1:]]
|
||||
body={'token':'', 'type_id':data['list'][0]['type_id']}
|
||||
ldata=self.post(f"{self.host}/api.php/vod/category", data=body, headers=self.headers).json()
|
||||
for i in ldata['data']['banner']:
|
||||
videos.append({
|
||||
'vod_id':i.get('vod_id'),
|
||||
'vod_name':i.get('vod_name'),
|
||||
'vod_pic':i.get('vod_pic_thumb')
|
||||
})
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=len(classes)) as executor:
|
||||
future_to_aid = {executor.submit(self.fts, aid): aid for aid in classes}
|
||||
for future in concurrent.futures.as_completed(future_to_aid):
|
||||
aid = future_to_aid[future]
|
||||
try:
|
||||
aid_id, fts = future.result()
|
||||
filters[aid_id] = fts
|
||||
except Exception as e:
|
||||
print(f"Error processing aid {aid}: {e}")
|
||||
result['class'] = classes
|
||||
result['filters'] = filters
|
||||
result['list'] = videos
|
||||
return result
|
||||
|
||||
def homeVideoContent(self):
|
||||
pass
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
params={'state':extend.get('state',tid) or tid,'class':extend.get('classes','全部'),'area':extend.get('area','全部'),'year':extend.get('year','全部'),'lang':extend.get('lang','全部'),'version':extend.get('version','全部'),'pg':pg}
|
||||
data=self.fetch(f"{self.host}/api.php/vod/list", params=params, headers=self.headers).json()
|
||||
result = {}
|
||||
videos = []
|
||||
for i in data['data']['list']:
|
||||
if str(i.get('vod_id', 0)) != '0':
|
||||
videos.append({
|
||||
'vod_id': i.get('vod_id'),
|
||||
'vod_name': i.get('vod_name'),
|
||||
'vod_pic': i.get('vod_pic'),
|
||||
'vod_year': f"{i.get('vod_score')}分",
|
||||
'vod_remarks': i.get('vod_remarks')
|
||||
})
|
||||
result['list'] = videos
|
||||
result['page'] = pg
|
||||
result['pagecount'] = 9999
|
||||
result['limit'] = 90
|
||||
result['total'] = 999999
|
||||
return result
|
||||
|
||||
def detailContent(self, ids):
|
||||
body={'ids':ids[0],'uni_code':self.getunc(),'ac':'detail','token':''}
|
||||
data=self.post(f"{self.host}/api.php/vod/detail2", data=body, headers=self.headers).json()
|
||||
v=data['data']
|
||||
vod = {
|
||||
'type_name': v.get('type_name'),
|
||||
'vod_year': v.get('vod_year'),
|
||||
'vod_area': v.get('vod_area'),
|
||||
'vod_lang': v.get('vod_lang'),
|
||||
'vod_remarks': v.get('vod_remarks'),
|
||||
'vod_actor': v.get('vod_actor'),
|
||||
'vod_director': v.get('vod_director'),
|
||||
'vod_content': v.get('vod_content')
|
||||
}
|
||||
n,p=[],[]
|
||||
for i in v['vod_play_list']:
|
||||
pp=i['player_info']
|
||||
n.append(pp['show'])
|
||||
np=[]
|
||||
for j in i['urls']:
|
||||
cd={'parse':pp.get('parse'),'url':j['url'],'headers':pp.get('headers')}
|
||||
np.append(f"{j['name']}${self.e64(json.dumps(cd))}")
|
||||
p.append('#'.join(np))
|
||||
vod.update({'vod_play_from':'$$$'.join(n),'vod_play_url':'$$$'.join(p)})
|
||||
return {'list':[vod]}
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
data=self.fetch(f"{self.host}/api.php/vod/search", params={'keywords':key,'type':'1','pg':pg}, headers=self.headers).json()
|
||||
return {'list':data['list'],'page':pg}
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
ids=json.loads(self.d64(id))
|
||||
headers = {}
|
||||
urls=ids['url']
|
||||
if ids.get('headers'):
|
||||
hs=ids['headers'].split('=>',1)
|
||||
headers[hs[0].strip()]=hs[-1].strip()
|
||||
if isinstance(ids.get('parse'), list) and len(ids['parse']) > 0:
|
||||
urls=[]
|
||||
for i,x in enumerate(ids['parse']):
|
||||
su=f"{self.getProxyUrl()}&url={quote(x+ids['url'])}"
|
||||
urls.extend([f'解析{i+1}',su])
|
||||
return {'parse': 0, 'url': urls, 'header': headers}
|
||||
|
||||
def localProxy(self, param):
|
||||
try:
|
||||
body = {'url':unquote(param['url'])}
|
||||
data=self.post(f"{self.host}/api.php/vod/m_jie_xi", data=body, headers=self.headers).json()
|
||||
url=data.get('url') or data['data'].get('url')
|
||||
return [302,'video/MP2T',None,{'Location':url}]
|
||||
except:
|
||||
return []
|
||||
|
||||
def liveContent(self, url):
|
||||
pass
|
||||
|
||||
def fts(self, tdata):
|
||||
params={'state':tdata['type_id'],'pg':'1'}
|
||||
data = self.fetch(f"{self.host}/api.php/vod/list", params=params, headers=self.headers).json()
|
||||
ftks = ["classes", "area", "lang", "year", "version", "state"]
|
||||
filter = [
|
||||
{
|
||||
'name': k,
|
||||
'key': k,
|
||||
'value': [{'n': i, 'v': i} for i in v.split(',')]
|
||||
}
|
||||
for k, v in data['data']['classes']["type_extend"].items()
|
||||
if k in ftks and v
|
||||
]
|
||||
return tdata['type_id'],filter
|
||||
|
||||
def getunc(self):
|
||||
chars = string.ascii_lowercase + string.digits
|
||||
data = ''.join(random.choice(chars) for _ in range(16))
|
||||
return self.e64(data)
|
||||
|
||||
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:
|
||||
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:
|
||||
return ""
|
||||
@@ -0,0 +1,301 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# by @嗷呜
|
||||
import sys
|
||||
from urllib.parse import urlparse
|
||||
sys.path.append("..")
|
||||
import re
|
||||
import hashlib
|
||||
import hmac
|
||||
import random
|
||||
import string
|
||||
from Crypto.Util.Padding import unpad
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from Crypto.PublicKey import RSA
|
||||
from Crypto.Cipher import PKCS1_v1_5, AES
|
||||
from base64 import b64encode, b64decode
|
||||
import json
|
||||
import time
|
||||
from base.spider import Spider
|
||||
|
||||
|
||||
class Spider(Spider):
|
||||
|
||||
def init(self, extend=""):
|
||||
self.device = self.device_id()
|
||||
self.host = self.gethost()
|
||||
pass
|
||||
|
||||
def getName(self):
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def action(self, action):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
def homeContent(self, filter):
|
||||
result = {}
|
||||
filters = {}
|
||||
classes = []
|
||||
bba = self.url()
|
||||
data = self.fetch(f"{self.host}/api/v1/app/config?pack={bba[0]}&signature={bba[1]}", headers=self.header()).text
|
||||
data1 = self.aes(data)
|
||||
dy = {"class": "类型", "area": "地区", "lang": "语言", "year": "年份", "letter": "字母", "by": "排序",
|
||||
"sort": "排序"}
|
||||
data1['data']['movie_screen']['sort'].pop(0)
|
||||
for item in data1['data']['movie_screen']['sort']:
|
||||
item['n'] = item.pop('name')
|
||||
item['v'] = item.pop('value')
|
||||
for item in data1['data']['movie_screen']['filter']:
|
||||
has_non_empty_field = False
|
||||
classes.append({"type_name": item["name"], "type_id": str(item["id"])})
|
||||
for key in dy:
|
||||
if key in item and item[key]:
|
||||
has_non_empty_field = True
|
||||
break
|
||||
if has_non_empty_field:
|
||||
filters[str(item["id"])] = []
|
||||
filters[str(item["id"])].append(
|
||||
{"key": 'sort', "name": '排序', "value": data1['data']['movie_screen']['sort']})
|
||||
for dkey in item:
|
||||
if dkey in dy and item[dkey]:
|
||||
item[dkey].pop(0)
|
||||
value_array = [
|
||||
{"n": value.strip(), "v": value.strip()}
|
||||
for value in item[dkey]
|
||||
if value.strip() != ""
|
||||
]
|
||||
filters[str(item["id"])].append(
|
||||
{"key": dkey, "name": dy[dkey], "value": value_array}
|
||||
)
|
||||
result["class"] = classes
|
||||
result["filters"] = filters
|
||||
return result
|
||||
|
||||
def homeVideoContent(self):
|
||||
bba = self.url()
|
||||
url = f'{self.host}/api/v1/movie/index_recommend?pack={bba[0]}&signature={bba[1]}'
|
||||
data = self.fetch(url, headers=self.header()).json()
|
||||
videos = []
|
||||
for item in data['data']:
|
||||
if len(item['list']) > 0:
|
||||
for it in item['list']:
|
||||
try:
|
||||
videos.append(self.voides(it))
|
||||
except Exception as e:
|
||||
continue
|
||||
result = {"list": videos}
|
||||
return result
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
body = {"type_id": tid, "sort": extend.get("sort", "by_default"), "class": extend.get("class", "类型"),
|
||||
"area": extend.get("area", "地区"), "year": extend.get("year", "年份"), "page": str(pg),
|
||||
"pageSize": "21"}
|
||||
result = {}
|
||||
list = []
|
||||
bba = self.url(body)
|
||||
url = f"{self.host}/api/v1/movie/screen/list?pack={bba[0]}&signature={bba[1]}"
|
||||
data = self.fetch(url, headers=self.header()).json()['data']['list']
|
||||
for item in data:
|
||||
list.append(self.voides(item))
|
||||
result["list"] = list
|
||||
result["page"] = pg
|
||||
result["pagecount"] = 9999
|
||||
result["limit"] = 90
|
||||
result["total"] = 999999
|
||||
return result
|
||||
|
||||
def detailContent(self, ids):
|
||||
body = {"id": ids[0]}
|
||||
bba = self.url(body)
|
||||
url = f'{self.host}/api/v1/movie/detail?pack={bba[0]}&signature={bba[1]}'
|
||||
data = self.fetch(url, headers=self.header()).json()['data']
|
||||
video = {'vod_name': data.get('name'), 'type_name': data.get('type_name'), 'vod_year': data.get('year'),
|
||||
'vod_area': data.get('area'), 'vod_remarks': data.get('dynami'), 'vod_content': data.get('content')}
|
||||
play = []
|
||||
names = []
|
||||
tasks = []
|
||||
for itt in data["play_from"]:
|
||||
name = itt["name"]
|
||||
a = []
|
||||
if len(itt["list"]) > 0:
|
||||
names.append(name)
|
||||
play.append(self.playeach(itt['list']))
|
||||
else:
|
||||
tasks.append({"movie_id": ids[0], "from_code": itt["code"]})
|
||||
names.append(name)
|
||||
if tasks:
|
||||
with ThreadPoolExecutor(max_workers=len(tasks)) as executor:
|
||||
results = executor.map(self.playlist, tasks)
|
||||
for result in results:
|
||||
if result:
|
||||
play.append(result)
|
||||
else:
|
||||
play.append("")
|
||||
video["vod_play_from"] = "$$$".join(names)
|
||||
video["vod_play_url"] = "$$$".join(play)
|
||||
result = {"list": [video]}
|
||||
return result
|
||||
|
||||
def searchContent(self, key, quick, pg=1):
|
||||
body = {"keyword": key, "sort": "", "type_id": "0", "page": str(pg), "pageSize": "10",
|
||||
"res_type": "by_movie_name"}
|
||||
bba = self.url(body)
|
||||
url = f"{self.host}/api/v1/movie/search?pack={bba[0]}&signature={bba[1]}"
|
||||
data = self.fetch(url, headers=self.header()).json()['data'].get('list')
|
||||
videos = []
|
||||
for it in data:
|
||||
try:
|
||||
videos.append(self.voides(it))
|
||||
except Exception as e:
|
||||
continue
|
||||
result = {"list": videos, "page": pg}
|
||||
return result
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
url = id
|
||||
if not re.search(r"\.m3u8|\.mp4", url):
|
||||
try:
|
||||
data = json.loads(b64decode(id.encode('utf-8')).decode('utf-8'))
|
||||
bba = self.url(data)
|
||||
data2 = self.fetch(f"{self.host}/api/v1/movie_addr/parse_url?pack={bba[0]}&signature={bba[1]}",
|
||||
headers=self.header()).json()['data']
|
||||
url = data2.get('play_url') or data2.get('download_url')
|
||||
except Exception as e:
|
||||
pass
|
||||
if re.search(r'\.jpg|\.png|\.jpeg', url):
|
||||
url = self.Mproxy(url)
|
||||
result = {}
|
||||
result["parse"] = 0
|
||||
result["url"] = url
|
||||
result["header"] = {'user-agent': 'okhttp/4.9.2'}
|
||||
return result
|
||||
|
||||
def localProxy(self, param):
|
||||
return self.Mlocal(param)
|
||||
|
||||
def Mproxy(self, url):
|
||||
return self.getProxyUrl() + "&url=" + b64encode(url.encode('utf-8')).decode('utf-8') + "&type=m3u8"
|
||||
|
||||
def Mlocal(self, param,header=None):
|
||||
url = self.d64(param["url"])
|
||||
ydata = self.fetch(url, headers=header, allow_redirects=False)
|
||||
data = ydata.content.decode('utf-8')
|
||||
if ydata.headers.get('Location'):
|
||||
url = ydata.headers['Location']
|
||||
data = self.fetch(url, headers=header).content.decode('utf-8')
|
||||
parsed_url = urlparse(url)
|
||||
durl = parsed_url.scheme + "://" + parsed_url.netloc
|
||||
lines = data.strip().split('\n')
|
||||
for index, string in enumerate(lines):
|
||||
if '#EXT' not in string and 'http' not in string:
|
||||
last_slash_index = string.rfind('/')
|
||||
lpath = string[:last_slash_index + 1]
|
||||
lines[index] = durl + ('' if lpath.startswith('/') else '/') + lpath
|
||||
data = '\n'.join(lines)
|
||||
return [200, "application/vnd.apple.mpegur", data]
|
||||
|
||||
def device_id(self):
|
||||
characters = string.ascii_lowercase + string.digits
|
||||
random_string = ''.join(random.choices(characters, k=32))
|
||||
return random_string
|
||||
|
||||
def gethost(self):
|
||||
try:
|
||||
url = 'https://dns.alidns.com/dns-query'
|
||||
headers = {
|
||||
'User-Agent': 'okhttp/4.9.2',
|
||||
'Accept': 'application/dns-message'
|
||||
}
|
||||
params = {
|
||||
'dns': 'AAABAAABAAAAAAAACWJmbTExYXM5ZgdmdXFpeXVuAmNuAAAcAAE'
|
||||
}
|
||||
response = self.fetch(url, headers=headers, params=params)
|
||||
host=self.parse_dns_name(response.content, 12)
|
||||
return f"https://{host}"
|
||||
except:
|
||||
return "https://bfm11as9f.fuqiyun.cn"
|
||||
|
||||
def parse_dns_name(self, data, offset):
|
||||
parts = []
|
||||
while True:
|
||||
length = data[offset]
|
||||
if length == 0:
|
||||
break
|
||||
offset += 1
|
||||
parts.append(data[offset:offset + length].decode('utf-8'))
|
||||
offset += length
|
||||
return '.'.join(parts)
|
||||
|
||||
def header(self):
|
||||
headers = {
|
||||
'User-Agent': 'Android',
|
||||
'Accept': 'application/prs.55App.v2+json',
|
||||
'timestamp': str(int(time.time())),
|
||||
'x-client-setting': '{"pure-mode":0}',
|
||||
'x-client-uuid': '{"device_id":' + self.device + '}, "type":1,"brand":"Redmi", "model":"M2012K10C", "system_version":30, "sdk_version":"3.1.0.7"}',
|
||||
'x-client-version': '3096 '
|
||||
}
|
||||
return headers
|
||||
|
||||
def url(self, id=None):
|
||||
if not id:
|
||||
id = {}
|
||||
id["timestamp"] = str(int(time.time()))
|
||||
public_key = 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA02F/kPg5A2NX4qZ5JSns+bjhVMCC6JbTiTKpbgNgiXU+Kkorg6Dj76gS68gB8llhbUKCXjIdygnHPrxVHWfzmzisq9P9awmXBkCk74Skglx2LKHa/mNz9ivg6YzQ5pQFUEWS0DfomGBXVtqvBlOXMCRxp69oWaMsnfjnBV+0J7vHbXzUIkqBLdXSNfM9Ag5qdRDrJC3CqB65EJ3ARWVzZTTcXSdMW9i3qzEZPawPNPe5yPYbMZIoXLcrqvEZnRK1oak67/ihf7iwPJqdc+68ZYEmmdqwunOvRdjq89fQMVelmqcRD9RYe08v+xDxG9Co9z7hcXGTsUquMxkh29uNawIDAQAB'
|
||||
encrypted_text = json.dumps(id)
|
||||
public_key = RSA.import_key(b64decode(public_key))
|
||||
cipher = PKCS1_v1_5.new(public_key)
|
||||
encrypted_message = cipher.encrypt(encrypted_text.encode('utf-8'))
|
||||
encrypted_message_base64 = b64encode(encrypted_message).decode('utf-8')
|
||||
result = encrypted_message_base64.replace('+', '-').replace('/', '_').replace('=', '')
|
||||
key = '635a580fcb5dc6e60caa39c31a7bde48'
|
||||
sign = hmac.new(key.encode(), result.encode(), hashlib.md5).hexdigest()
|
||||
return result, sign
|
||||
|
||||
def playlist(self, body):
|
||||
try:
|
||||
bba = self.url(body)
|
||||
url = f'{self.host}/api/v1/movie_addr/list?pack={bba[0]}&signature={bba[1]}'
|
||||
data = self.fetch(url, headers=self.header()).json()['data']
|
||||
return self.playeach(data)
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
def playeach(self, data):
|
||||
play_urls = []
|
||||
for it in data:
|
||||
if re.search(r"mp4|m3u8", it["play_url"]):
|
||||
play_urls.append(f"{it['episode_name']}${it['play_url']}")
|
||||
else:
|
||||
vd={"from_code": it['from_code'], "play_url": it['play_url'], "episode_id": it['episode_id'], "type": "play"}
|
||||
play_urls.append(
|
||||
f"{it['episode_name']}${b64encode(json.dumps(vd).encode('utf-8')).decode('utf-8')}"
|
||||
)
|
||||
return '#'.join(play_urls)
|
||||
|
||||
def voides(self, item):
|
||||
if item['name'] or item['title']:
|
||||
voide = {
|
||||
"vod_id": item.get('id') or item.get('click'),
|
||||
'vod_name': item.get('name') or item.get('title'),
|
||||
'vod_pic': item.get('cover') or item.get('image'),
|
||||
'vod_year': item.get('year') or item.get('label'),
|
||||
'vod_remarks': item.get('dynamic') or item.get('sub_title')
|
||||
}
|
||||
return voide
|
||||
|
||||
def aes(self, text):
|
||||
text = text.replace('-', '+').replace('_', '/') + '=='
|
||||
key = b"e6d5de5fcc51f53d"
|
||||
iv = b"2f13eef7dfc6c613"
|
||||
cipher = AES.new(key, AES.MODE_CBC, iv)
|
||||
pt = unpad(cipher.decrypt(b64decode(text)), AES.block_size).decode("utf-8")
|
||||
return json.loads(pt)
|
||||
@@ -0,0 +1,184 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# by @嗷呜
|
||||
import json
|
||||
import sys
|
||||
import time
|
||||
import requests
|
||||
from base64 import b64decode, b64encode
|
||||
from Crypto.Hash import MD5
|
||||
from pyquery import PyQuery as pq
|
||||
sys.path.append('..')
|
||||
from base.spider import Spider
|
||||
|
||||
class Spider(Spider):
|
||||
|
||||
def init(self, extend=""):
|
||||
pass
|
||||
|
||||
def getName(self):
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
host='http://v.rbotv.cn'
|
||||
|
||||
headers = {
|
||||
'User-Agent': 'okhttp-okgo/jeasonlzy',
|
||||
'Accept-Language': 'zh-CN,zh;q=0.8'
|
||||
}
|
||||
|
||||
def homeContent(self, filter):
|
||||
data=requests.post(f'{self.host}/v3/type/top_type',headers=self.headers,files=self.getfiles({'': (None, '')})).json()
|
||||
result = {}
|
||||
classes = []
|
||||
filters = {}
|
||||
for k in data['data']['list']:
|
||||
classes.append({
|
||||
'type_name': k['type_name'],
|
||||
'type_id': k['type_id']
|
||||
})
|
||||
fts = []
|
||||
for i,x in k.items():
|
||||
if isinstance(x, list) and len(x)>2:
|
||||
fts.append({
|
||||
'name': i,
|
||||
'key': i,
|
||||
'value': [{'n': j, 'v': j} for j in x if j and j!= '全部']
|
||||
})
|
||||
if len(fts):filters[k['type_id']] = fts
|
||||
result['class'] = classes
|
||||
result['filters'] = filters
|
||||
return result
|
||||
|
||||
def homeVideoContent(self):
|
||||
data=requests.post(f'{self.host}/v3/type/tj_vod',headers=self.headers,files=self.getfiles({'': (None, '')})).json()
|
||||
return {'list':self.getv(data['data']['cai']+data['data']['loop'])}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
files = {
|
||||
'type_id': (None, tid),
|
||||
'limit': (None, '12'),
|
||||
'page': (None, pg)
|
||||
}
|
||||
for k,v in extend.items():
|
||||
if k=='extend':k='class'
|
||||
files[k] = (None, v)
|
||||
data=requests.post(f'{self.host}/v3/home/type_search',headers=self.headers,files=self.getfiles(files)).json()
|
||||
result = {}
|
||||
result['list'] = self.getv(data['data']['list'])
|
||||
result['page'] = pg
|
||||
result['pagecount'] = 9999
|
||||
result['limit'] = 90
|
||||
result['total'] = 999999
|
||||
return result
|
||||
|
||||
def detailContent(self, ids):
|
||||
data=requests.post(f'{self.host}/v3/home/vod_details',headers=self.headers,files=self.getfiles({'vod_id': (None, ids[0])})).json()
|
||||
v=data['data']
|
||||
vod = {
|
||||
'vod_name': v.get('vod_name'),
|
||||
'type_name': v.get('type_name'),
|
||||
'vod_year': v.get('vod_year'),
|
||||
'vod_area': v.get('vod_area'),
|
||||
'vod_remarks': v.get('vod_remarks'),
|
||||
'vod_actor': v.get('vod_actor'),
|
||||
'vod_director': v.get('vod_director'),
|
||||
'vod_content': pq(pq(v.get('vod_content','无') or '无').text()).text()
|
||||
}
|
||||
n,p=[],[]
|
||||
for o,i in enumerate(v['vod_play_list']):
|
||||
n.append(f"线路{o+1}({i.get('flag')})")
|
||||
c=[]
|
||||
for j in i.get('urls'):
|
||||
d={'url':j.get('url'),'p':i.get('parse_urls'),'r':i.get('referer'),'u':i.get('ua')}
|
||||
c.append(f"{j.get('name')}${self.e64(json.dumps(d))}")
|
||||
p.append('#'.join(c))
|
||||
vod.update({'vod_play_from':'$$$'.join(n),'vod_play_url':'$$$'.join(p)})
|
||||
return {'list':[vod]}
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
files = {
|
||||
'limit': (None, '12'),
|
||||
'page': (None, pg),
|
||||
'keyword': (None, key),
|
||||
}
|
||||
data=requests.post(f'{self.host}/v3/home/search',headers=self.headers,files=self.getfiles(files)).json()
|
||||
return {'list':self.getv(data['data']['list']),'page':pg}
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
ids=json.loads(self.d64(id))
|
||||
url=ids['url']
|
||||
if isinstance(ids['p'],list) and len(ids['p']):
|
||||
url=[]
|
||||
for i,x in enumerate(ids['p']):
|
||||
up={'url':ids['url'],'p':x,'r':ids['r'],'u':ids['u']}
|
||||
url.extend([f"解析{i+1}",f"{self.getProxyUrl()}&data={self.e64(json.dumps(up))}"])
|
||||
h={}
|
||||
if ids.get('r'):
|
||||
h['Referer'] = ids['r']
|
||||
if ids.get('u'):
|
||||
h['User-Agent'] = ids['u']
|
||||
return {'parse': 0, 'url': url, 'header': h}
|
||||
|
||||
def localProxy(self, param):
|
||||
data=json.loads(self.d64(param['data']))
|
||||
h = {}
|
||||
if data.get('r'):
|
||||
h['Referer'] = data['r']
|
||||
if data.get('u'):
|
||||
h['User-Agent'] = data['u']
|
||||
res=self.fetch(f"{data['p']}{data['url']}",headers=h).json()
|
||||
url=res.get('url') or res['data'].get('url')
|
||||
return [302,'video/MP2T',None,{'Location':url}]
|
||||
|
||||
def liveContent(self, url):
|
||||
pass
|
||||
|
||||
def getfiles(self, p=None):
|
||||
if p is None:p = {}
|
||||
t=str(int(time.time()))
|
||||
h = MD5.new()
|
||||
h.update(f"7gp0bnd2sr85ydii2j32pcypscoc4w6c7g5spl{t}".encode('utf-8'))
|
||||
s = h.hexdigest()
|
||||
files = {
|
||||
'sign': (None, s),
|
||||
'timestamp': (None, t)
|
||||
}
|
||||
p.update(files)
|
||||
return p
|
||||
|
||||
def getv(self,data):
|
||||
videos = []
|
||||
for i in data:
|
||||
if i.get('vod_id') and str(i['vod_id']) != '0':
|
||||
videos.append({
|
||||
'vod_id': i['vod_id'],
|
||||
'vod_name': i.get('vod_name'),
|
||||
'vod_pic': i.get('vod_pic') or i.get('vod_pic_thumb'),
|
||||
'vod_year': i.get('tag'),
|
||||
'vod_remarks': i.get('vod_remarks')
|
||||
})
|
||||
return videos
|
||||
|
||||
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:
|
||||
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:
|
||||
return ""
|
||||
@@ -0,0 +1,166 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# by @嗷呜
|
||||
# 温馨提示:搜索只能搜拼音联想
|
||||
# 播放需要挂代理
|
||||
import sys
|
||||
import time
|
||||
import uuid
|
||||
from Crypto.Hash import MD5
|
||||
sys.path.append('..')
|
||||
from base.spider import Spider
|
||||
|
||||
|
||||
class Spider(Spider):
|
||||
|
||||
def init(self, extend=""):
|
||||
self.uid = self.getuid()
|
||||
self.token, self.code = self.getuserinfo()
|
||||
pass
|
||||
|
||||
def getName(self):
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
host = 'https://tvapi211.magicetech.com'
|
||||
|
||||
headers = {'User-Agent': 'okhttp/3.11.0'}
|
||||
|
||||
def homeContent(self, filter):
|
||||
body = {'token': self.token, 'authcode': self.code}
|
||||
data = self.post(f'{self.host}/hr_1_1_0/apptvapi/web/index.php/video/filter-header', json=self.getbody(body),
|
||||
headers=self.headers).json()
|
||||
result = {}
|
||||
classes = []
|
||||
filters = {}
|
||||
for k in data['data']:
|
||||
classes.append({
|
||||
'type_name': k['channel_name'],
|
||||
'type_id': str(k['channel_id']),
|
||||
})
|
||||
filters[str(k['channel_id'])] = []
|
||||
for i in k['search_box']:
|
||||
if len(i['list']):
|
||||
filters[str(k['channel_id'])].append({
|
||||
'key': i['field'],
|
||||
'name': i['label'],
|
||||
'value': [{'n': j['display'], 'v': str(j['value'])} for j in i['list'] if j['value']]
|
||||
})
|
||||
result['class'] = classes
|
||||
result['filters'] = filters
|
||||
return result
|
||||
|
||||
def homeVideoContent(self):
|
||||
body = {'token': self.token, 'authcode': self.code}
|
||||
data = self.post(f'{self.host}/hr_1_1_0/apptvapi/web/index.php/video/index-tv', json=self.getbody(body),
|
||||
headers=self.headers).json()
|
||||
return {'list': self.getlist(data['data'][0]['banner'])}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
body = {'token': self.token, 'authcode': self.code, 'channel_id': tid, 'area': extend.get('area', '0'),
|
||||
'year': extend.get('year', '0'), 'sort': extend.get('sort', '0'), 'tag': extend.get('tag', 'hot'),
|
||||
'status': extend.get('status', '0'), 'page_num': pg, 'page_size': '24'}
|
||||
data = self.post(f'{self.host}/hr_1_1_0/apptvapi/web/index.php/video/filter-video', json=self.getbody(body),
|
||||
headers=self.headers).json()
|
||||
result = {}
|
||||
result['list'] = self.getlist(data['data']['list'])
|
||||
result['page'] = pg
|
||||
result['pagecount'] = 9999
|
||||
result['limit'] = 90
|
||||
result['total'] = 999999
|
||||
return result
|
||||
|
||||
def detailContent(self, ids):
|
||||
ids = ids[0].split('@')
|
||||
body = {'token': self.token, 'authcode': self.code, 'channel_id': ids[0], 'video_id': ids[1]}
|
||||
data = self.post(f'{self.host}/hr_1_1_0/apptvapi/web/index.php/video/detail', json=self.getbody(body),
|
||||
headers=self.headers).json()
|
||||
vdata = {}
|
||||
for k in data['data']['chapters']:
|
||||
i = k['sourcelist']
|
||||
for j in i:
|
||||
if j['source_name'] not in vdata: vdata[j['source_name']] = []
|
||||
vdata[j['source_name']].append(f"{k['title']}${j['source_url']}")
|
||||
plist, names = [], []
|
||||
for key, value in vdata.items():
|
||||
names.append(key)
|
||||
plist.append('#'.join(value))
|
||||
vod = {
|
||||
'vod_play_from': '$$$'.join(names),
|
||||
'vod_play_url': '$$$'.join(plist),
|
||||
}
|
||||
return {'list': [vod]}
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
body = {'token': self.token, 'authcode': self.code, 'keyword': key, 'page_num': pg}
|
||||
data = self.post(f'{self.host}/hr_1_1_0/apptvapi/web/index.php/search/letter-result', json=self.getbody(body),
|
||||
headers=self.headers).json()
|
||||
return {'list': self.getlist(data['data']['list'])}
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
# https://rysp.tv
|
||||
# https://aigua.tv
|
||||
result = {
|
||||
"parse": 0,
|
||||
"url": id,
|
||||
"header": {
|
||||
"User-Agent": "Dalvik/2.1.0 (Linux; U; Android 11; M2012K10C Build/RP1A.200720.011)",
|
||||
"Origin": "https://aigua.tv",
|
||||
"Referer": "https://aigua.tv/"
|
||||
}
|
||||
}
|
||||
return result
|
||||
|
||||
def localProxy(self, param):
|
||||
pass
|
||||
|
||||
def getuserinfo(self):
|
||||
data = self.post(f'{self.host}/hr_1_1_0/apptvapi/web/index.php/user/auth-login', json=self.getbody(),
|
||||
headers=self.headers).json()
|
||||
v = data['data']
|
||||
return v['user_token'], v['authcode']
|
||||
|
||||
def getuid(self):
|
||||
uid = self.getCache('uid')
|
||||
if not uid:
|
||||
uid = str(uuid.uuid4())
|
||||
self.setCache('uid', uid)
|
||||
return uid
|
||||
|
||||
def getbody(self, json_data=None):
|
||||
if json_data is None: json_data = {}
|
||||
params = {"product": "4", "ver": "1.1.0", "debug": "1", "appId": "1", "osType": "3", "marketChannel": "tv",
|
||||
"sysVer": "11", "time": str(int(time.time())), "packageName": "com.gzsptv.gztvvideo",
|
||||
"udid": self.uid, }
|
||||
json_data.update(params)
|
||||
sorted_json = dict(sorted(json_data.items(), key=lambda item: item[0]))
|
||||
text = '&'.join(f"{k}={v}" for k, v in sorted_json.items() if v != '')
|
||||
md5_hash = self.md5(f"jI7POOBbmiUZ0lmi{text}D9ShYdN51ksWptpkTu11yenAJu7Zu3cR").upper()
|
||||
json_data.update({'sign': md5_hash})
|
||||
return json_data
|
||||
|
||||
def md5(self, text):
|
||||
h = MD5.new()
|
||||
h.update(text.encode('utf-8'))
|
||||
return h.hexdigest()
|
||||
|
||||
def getlist(self, data):
|
||||
videos = []
|
||||
for i in data:
|
||||
if type(i.get('video')) == dict: i = i['video']
|
||||
videos.append({
|
||||
'vod_id': f"{i.get('channel_id')}@{i.get('video_id')}",
|
||||
'vod_name': i.get('video_name'),
|
||||
'vod_pic': i.get('cover'),
|
||||
'vod_year': i.get('score'),
|
||||
'vod_remarks': i.get('flag'),
|
||||
})
|
||||
return videos
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# by @嗷呜
|
||||
import re
|
||||
import sys
|
||||
from Crypto.Hash import MD5
|
||||
sys.path.append("..")
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Util.Padding import pad, unpad
|
||||
from urllib.parse import quote, urlparse
|
||||
from base64 import b64encode, b64decode
|
||||
import json
|
||||
import time
|
||||
from base.spider import Spider
|
||||
|
||||
class Spider(Spider):
|
||||
|
||||
def init(self, extend=""):
|
||||
self.host = "http://ppx.bjx365.top"
|
||||
pass
|
||||
|
||||
def getName(self):
|
||||
pass
|
||||
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def action(self, action):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
def homeContent(self, filter):
|
||||
data = self.getdata("/api.php/getappapi.index/initV119")
|
||||
dy = {"class": "类型", "area": "地区", "lang": "语言", "year": "年份", "letter": "字母", "by": "排序",
|
||||
"sort": "排序"}
|
||||
filters = {}
|
||||
classes = []
|
||||
json_data = data["type_list"]
|
||||
homedata = data["banner_list"][8:]
|
||||
for item in json_data:
|
||||
if item["type_name"] == "全部":
|
||||
continue
|
||||
has_non_empty_field = False
|
||||
jsontype_extend = json.loads(item["type_extend"])
|
||||
homedata.extend(item["recommend_list"])
|
||||
jsontype_extend["sort"] = "最新,最热,最赞"
|
||||
classes.append({"type_name": item["type_name"], "type_id": item["type_id"]})
|
||||
for key in dy:
|
||||
if key in jsontype_extend and jsontype_extend[key].strip() != "":
|
||||
has_non_empty_field = True
|
||||
break
|
||||
if has_non_empty_field:
|
||||
filters[str(item["type_id"])] = []
|
||||
for dkey in jsontype_extend:
|
||||
if dkey in dy and jsontype_extend[dkey].strip() != "":
|
||||
values = jsontype_extend[dkey].split(",")
|
||||
value_array = [{"n": value.strip(), "v": value.strip()} for value in values if
|
||||
value.strip() != ""]
|
||||
filters[str(item["type_id"])].append({"key": dkey, "name": dy[dkey], "value": value_array})
|
||||
result = {}
|
||||
result["class"] = classes
|
||||
result["filters"] = filters
|
||||
result["list"] = homedata[1:]
|
||||
return result
|
||||
|
||||
def homeVideoContent(self):
|
||||
pass
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
body = {"area": extend.get('area', '全部'), "year": extend.get('year', '全部'), "type_id": tid, "page": pg,
|
||||
"sort": extend.get('sort', '最新'), "lang": extend.get('lang', '全部'),
|
||||
"class": extend.get('class', '全部')}
|
||||
result = {}
|
||||
data = self.getdata("/api.php/getappapi.index/typeFilterVodList", body)
|
||||
result["list"] = data["recommend_list"]
|
||||
result["page"] = pg
|
||||
result["pagecount"] = 9999
|
||||
result["limit"] = 90
|
||||
result["total"] = 999999
|
||||
return result
|
||||
|
||||
def detailContent(self, ids):
|
||||
body = f"vod_id={ids[0]}"
|
||||
data = self.getdata("/api.php/getappapi.index/vodDetail", body)
|
||||
vod = data["vod"]
|
||||
play = []
|
||||
names = []
|
||||
for itt in data["vod_play_list"]:
|
||||
a = []
|
||||
names.append(itt["player_info"]["show"])
|
||||
for it in itt['urls']:
|
||||
it['user_agent']=itt["player_info"].get("user_agent")
|
||||
it["parse"]=itt["player_info"].get("parse")
|
||||
a.append(f"{it['name']}${self.e64(json.dumps(it))}")
|
||||
play.append("#".join(a))
|
||||
vod["vod_play_from"] = "$$$".join(names)
|
||||
vod["vod_play_url"] = "$$$".join(play)
|
||||
result = {"list": [vod]}
|
||||
return result
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
body = f"keywords={key}&type_id=0&page={pg}"
|
||||
data = self.getdata("/api.php/getappapi.index/searchList", body)
|
||||
result = {"list": data["search_list"], "page": pg}
|
||||
return result
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
ids = json.loads(self.d64(id))
|
||||
h={"User-Agent": (ids['user_agent'] or "okhttp/3.14.9")}
|
||||
url = ids['url']
|
||||
p=1
|
||||
try:
|
||||
if re.search(r'\?url=', ids['parse_api_url']):
|
||||
data=self.fetch(ids['parse_api_url'], headers=h, timeout=10).json()
|
||||
url=data.get('url') or data['data'].get('url')
|
||||
elif not re.search(r'\.m3u8|\.mp4', ids.get('url')):
|
||||
body = f"parse_api={ids.get('parse') or ids['parse_api_url'].replace(ids['url'], '')}&url={quote(self.aes('encrypt', ids['url']))}&token={ids.get('token')}"
|
||||
b = self.getdata("/api.php/getappapi.index/vodParse", body)['json']
|
||||
url = json.loads(b)['url']
|
||||
p=0
|
||||
except Exception as e:
|
||||
print('错误信息:',e)
|
||||
pass
|
||||
if re.search(r'\.jpg|\.png|\.jpeg', url):
|
||||
url = self.Mproxy(url)
|
||||
result = {}
|
||||
result["parse"] = p
|
||||
result["url"] = url
|
||||
result["header"] = h
|
||||
return result
|
||||
|
||||
def localProxy(self, param):
|
||||
return self.Mlocal(param)
|
||||
|
||||
def aes(self, operation, text):
|
||||
key = "pipixia217522324".encode("utf-8")
|
||||
iv = key
|
||||
if operation == "encrypt":
|
||||
cipher = AES.new(key, AES.MODE_CBC, iv)
|
||||
ct_bytes = cipher.encrypt(pad(text.encode("utf-8"), AES.block_size))
|
||||
ct = b64encode(ct_bytes).decode("utf-8")
|
||||
return ct
|
||||
elif operation == "decrypt":
|
||||
cipher = AES.new(key, AES.MODE_CBC, iv)
|
||||
pt = unpad(cipher.decrypt(b64decode(text)), AES.block_size)
|
||||
return pt.decode("utf-8")
|
||||
|
||||
def header(self):
|
||||
t = str(int(time.time()))
|
||||
header = {"Referer":self.host,
|
||||
"User-Agent": "okhttp/3.14.9", "app-version-code": "300", "app-ui-mode": "light",
|
||||
"app-api-verify-time": t, "app-user-device-id": self.md5(t),
|
||||
"app-api-verify-sign": self.aes("encrypt", t),
|
||||
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"}
|
||||
return header
|
||||
|
||||
def getdata(self, path, data=None):
|
||||
vdata = self.post(f"{self.host}{path}", headers=self.header(), data=data, timeout=10).json()['data']
|
||||
data1 = self.aes("decrypt", vdata)
|
||||
return json.loads(data1)
|
||||
|
||||
def Mproxy(self, url):
|
||||
return self.getProxyUrl() + "&url=" + b64encode(url.encode('utf-8')).decode('utf-8') + "&type=m3u8"
|
||||
|
||||
def Mlocal(self, param,header=None):
|
||||
url = self.d64(param["url"])
|
||||
ydata = self.fetch(url, headers=header, allow_redirects=False)
|
||||
data = ydata.content.decode('utf-8')
|
||||
if ydata.headers.get('Location'):
|
||||
url = ydata.headers['Location']
|
||||
data = self.fetch(url, headers=header).content.decode('utf-8')
|
||||
parsed_url = urlparse(url)
|
||||
durl = parsed_url.scheme + "://" + parsed_url.netloc
|
||||
lines = data.strip().split('\n')
|
||||
for index, string in enumerate(lines):
|
||||
if '#EXT' not in string and 'http' not in string:
|
||||
last_slash_index = string.rfind('/')
|
||||
lpath = string[:last_slash_index + 1]
|
||||
lines[index] = durl + ('' if lpath.startswith('/') else '/') + lpath
|
||||
data = '\n'.join(lines)
|
||||
return [200, "application/vnd.apple.mpegur", data]
|
||||
|
||||
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 md5(self, text):
|
||||
h = MD5.new()
|
||||
h.update(text.encode('utf-8'))
|
||||
return h.hexdigest()
|
||||
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# by @嗷呜
|
||||
import sys
|
||||
sys.path.append('..')
|
||||
from base.spider import Spider
|
||||
|
||||
|
||||
class Spider(Spider):
|
||||
def getName(self):
|
||||
return "mp"
|
||||
|
||||
def init(self, extend=""):
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
host = 'https://g.c494.com'
|
||||
|
||||
header = {
|
||||
'User-Agent': 'Dart/2.10 (dart:io)',
|
||||
'platform_version': 'RP1A.200720.011',
|
||||
'version': '2.2.3',
|
||||
'copyright': 'xiaogui',
|
||||
'platform': 'android',
|
||||
'client_name': '576O5p+P5b2x6KeG',
|
||||
}
|
||||
|
||||
def homeContent(self, filter):
|
||||
data = self.fetch(f'{self.host}/api.php/app/nav?token=', headers=self.header).json()
|
||||
dy = {"class": "类型", "area": "地区", "lang": "语言", "year": "年份", "letter": "字母", "by": "排序",
|
||||
"sort": "排序"}
|
||||
filters = {}
|
||||
classes = []
|
||||
json_data = data["list"]
|
||||
for item in json_data:
|
||||
has_non_empty_field = False
|
||||
jsontype_extend = item["type_extend"]
|
||||
classes.append({"type_name": item["type_name"], "type_id": str(item["type_id"])})
|
||||
for key in dy:
|
||||
if key in jsontype_extend and jsontype_extend[key].strip() != "":
|
||||
has_non_empty_field = True
|
||||
break
|
||||
if has_non_empty_field:
|
||||
filters[str(item["type_id"])] = []
|
||||
for dkey in jsontype_extend:
|
||||
if dkey in dy and jsontype_extend[dkey].strip() != "":
|
||||
values = jsontype_extend[dkey].split(",")
|
||||
value_array = [{"n": value.strip(), "v": value.strip()} for value in values if
|
||||
value.strip() != ""]
|
||||
filters[str(item["type_id"])].append({"key": dkey, "name": dy[dkey], "value": value_array})
|
||||
result = {}
|
||||
result["class"] = classes
|
||||
result["filters"] = filters
|
||||
return result
|
||||
|
||||
def homeVideoContent(self):
|
||||
rsp = self.fetch(f"{self.host}/api.php/app/index_video?token=", headers=self.header)
|
||||
root = rsp.json()['list']
|
||||
videos = [item for vodd in root for item in vodd['vlist']]
|
||||
return {'list': videos}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
parms = {"pg": pg, "tid": tid, "class": extend.get("class", ""), "area": extend.get("area", ""),
|
||||
"lang": extend.get("lang", ""), "year": extend.get("year", ""), "token": ""}
|
||||
data = self.fetch(f'{self.host}/api.php/app/video', params=parms, headers=self.header).json()
|
||||
return data
|
||||
|
||||
def detailContent(self, ids):
|
||||
parms = {"id": ids[0], "token": ""}
|
||||
data = self.fetch(f'{self.host}/api.php/app/video_detail', params=parms, headers=self.header).json()
|
||||
vod = data['data']
|
||||
vod.pop('pause_advert_list', None)
|
||||
vod.pop('init_advert_list', None)
|
||||
vod.pop('vod_url_with_player', None)
|
||||
return {"list": [vod]}
|
||||
|
||||
def searchContent(self, key, quick, pg='1'):
|
||||
parms = {'pg': pg, 'text': key, 'token': ''}
|
||||
data = self.fetch(f'{self.host}/api.php/app/search', params=parms, headers=self.header).json()
|
||||
return data
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
return {"parse": 0, "url": id, "header": {'User-Agent': 'User-Agent: Lavf/58.12.100'}}
|
||||
|
||||
def localProxy(self, param):
|
||||
pass
|
||||
@@ -0,0 +1,215 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# by @嗷呜
|
||||
import re
|
||||
import sys
|
||||
sys.path.append('..')
|
||||
from base.spider import Spider
|
||||
from Cryptodome.Cipher import AES
|
||||
from Cryptodome.Util.Padding import pad, unpad
|
||||
from base64 import b64encode, b64decode
|
||||
import json
|
||||
import time
|
||||
|
||||
|
||||
class Spider(Spider):
|
||||
def getName(self):
|
||||
return "py_胖虎"
|
||||
|
||||
def init(self, extend=""):
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
def aes(self, operation, text):
|
||||
key = "ihIwTbt2YAe9TGea".encode('utf-8')
|
||||
iv = key
|
||||
|
||||
if operation == 'encrypt':
|
||||
cipher = AES.new(key, AES.MODE_CBC, iv)
|
||||
ct_bytes = cipher.encrypt(pad(text.encode('utf-8'), AES.block_size))
|
||||
ct = b64encode(ct_bytes).decode('utf-8')
|
||||
return ct
|
||||
elif operation == 'decrypt':
|
||||
cipher = AES.new(key, AES.MODE_CBC, iv)
|
||||
pt = unpad(cipher.decrypt(b64decode(text)), AES.block_size)
|
||||
return pt.decode('utf-8')
|
||||
|
||||
host = "http://sm.physkan.top:3389"
|
||||
t = str(int(time.time()))
|
||||
|
||||
def homeContent(self, filter):
|
||||
self.header = {
|
||||
'User-Agent': 'okhttp/3.14.9',
|
||||
'app-version-code': '402',
|
||||
'app-ui-mode': 'light',
|
||||
'app-user-device-id': '25f869d32598d3d3089a929453dff0bb7',
|
||||
'app-api-verify-time': self.t,
|
||||
'app-api-verify-sign': self.aes('encrypt', self.t),
|
||||
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
|
||||
}
|
||||
data = self.fetch("{0}/api.php/getappapi.index/initV119".format(self.host), headers=self.header).content.decode(
|
||||
'utf-8')
|
||||
data1 = json.loads(data)['data']
|
||||
print(data1)
|
||||
data2 = self.aes('decrypt', data1)
|
||||
dy = {
|
||||
"class": "类型",
|
||||
"area": "地区",
|
||||
"lang": "语言",
|
||||
"year": "年份",
|
||||
"letter": "字母",
|
||||
"by": "排序",
|
||||
"sort": "排序"
|
||||
}
|
||||
|
||||
filter = {}
|
||||
classes = []
|
||||
json_data = json.loads(data2)['type_list']
|
||||
self.homedata = json.loads(data2)['banner_list']
|
||||
|
||||
for item in json_data:
|
||||
if item['type_name'] == '全部':
|
||||
continue
|
||||
|
||||
has_non_empty_field = False
|
||||
jsontype_extend = json.loads(item['type_extend'])
|
||||
jsontype_extend["sort"] = "最新,最热,最赞"
|
||||
|
||||
classes.append({
|
||||
"type_name": item['type_name'],
|
||||
"type_id": item['type_id']
|
||||
})
|
||||
|
||||
for key in dy:
|
||||
if key in jsontype_extend and jsontype_extend[key].strip() != "":
|
||||
has_non_empty_field = True
|
||||
break
|
||||
|
||||
if has_non_empty_field:
|
||||
filter[str(item['type_id'])] = []
|
||||
|
||||
for dkey in jsontype_extend:
|
||||
if dkey in dy and jsontype_extend[dkey].strip() != "":
|
||||
values = jsontype_extend[dkey].split(',')
|
||||
value_array = [
|
||||
{"n": value.strip(), "v": value.strip()}
|
||||
for value in values if value.strip() != ''
|
||||
]
|
||||
|
||||
filter[str(item['type_id'])].append({
|
||||
"key": dkey,
|
||||
"name": dy[dkey],
|
||||
"value": value_array
|
||||
})
|
||||
result = {}
|
||||
result['class'] = classes
|
||||
result['filter'] = filter
|
||||
return result
|
||||
|
||||
def homeVideoContent(self):
|
||||
result = {
|
||||
'list': self.homedata
|
||||
}
|
||||
return result
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
body = f"area={extend.get('area', '全部')}&year={extend.get('year', '全部')}&type_id={tid}&page={pg}&sort={extend.get('sort', '最新')}&lang={extend.get('lang', '全部')}&class={extend.get('class', '全部')}"
|
||||
result = {}
|
||||
url = '{0}/api.php/getappapi.index/typeFilterVodList'.format(self.host)
|
||||
data = self.post(url, headers=self.header, data=body).content.decode('utf-8')
|
||||
data1 = json.loads(data)['data']
|
||||
data2 = self.aes('decrypt', data1)
|
||||
result['list'] = json.loads(data2)['recommend_list']
|
||||
result['page'] = pg
|
||||
result['pagecount'] = 9999
|
||||
result['limit'] = 90
|
||||
result['total'] = 999999
|
||||
return result
|
||||
|
||||
def detailContent(self, ids):
|
||||
body = f"vod_id={ids[0]}"
|
||||
print(body)
|
||||
url = '{0}/api.php/getappapi.index/vodDetail'.format(self.host)
|
||||
data = self.post(url, headers=self.header, data=body).content.decode('utf-8')
|
||||
data1 = json.loads(data)['data']
|
||||
data2 = json.loads(self.aes('decrypt', data1))
|
||||
print(data2)
|
||||
vod = data2['vod']
|
||||
print(vod)
|
||||
play = []
|
||||
names = []
|
||||
for itt in data2['vod_play_list']:
|
||||
a = []
|
||||
names.append(itt['player_info']['show'])
|
||||
parse = itt['player_info']['parse']
|
||||
for it in itt['urls']:
|
||||
if re.search(r'mp4|m3u8', it['url']):
|
||||
a.append(f"{it['name']}${it['url']}")
|
||||
elif re.search(r'www.yemu.xyz', it['parse_api_url']):
|
||||
a.append(f"{it['name']}${it['parse_api_url']}")
|
||||
else:
|
||||
a.append(
|
||||
f"{it['name']}${'parse_api=' + parse + '&url=' + self.aes('encrypt', it['url']) + '&token=' + it['token']}")
|
||||
play.append('#'.join(a))
|
||||
vod['vod_play_from'] = '$$$'.join(names)
|
||||
vod['vod_play_url'] = '$$$'.join(play)
|
||||
result = {
|
||||
'list': [
|
||||
vod
|
||||
]
|
||||
}
|
||||
return result
|
||||
|
||||
def searchContent(self, key, quick, pg='1'):
|
||||
body = f"keywords={key}&type_id=0&page={pg}"
|
||||
url = '{0}/api.php/getappapi.index/searchList'.format(self.host)
|
||||
data = self.post(url, headers=self.header, data=body).content.decode('utf-8')
|
||||
data1 = json.loads(data)['data']
|
||||
data2 = self.aes('decrypt', data1)
|
||||
result = {
|
||||
'list': json.loads(data2)['search_list']
|
||||
}
|
||||
return result
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
def edu(str):
|
||||
def replacer(match):
|
||||
from urllib.parse import quote_plus
|
||||
return match.group(1) + quote_plus(match.group(2)) + match.group(3)
|
||||
|
||||
return re.sub(r'(url=)(.*?)(&token)', replacer, str)
|
||||
|
||||
url = id
|
||||
parse = 0
|
||||
if 'm3u8' not in url and 'mp4' not in url:
|
||||
try:
|
||||
body = edu(url)
|
||||
print(body)
|
||||
data = self.post('{0}/api.php/getappapi.index/vodParse'.format(self.host), headers=self.header,
|
||||
data=body).content.decode('utf-8')
|
||||
data1 = json.loads(data)['data']
|
||||
data2 = json.loads(self.aes('decrypt', data1))['json']
|
||||
url = json.loads(data2)['url']
|
||||
except:
|
||||
url = id
|
||||
parse = 1
|
||||
if not id.startswith('https://www.yemu.xyz'):
|
||||
url = 'https://www.yemu.xyz/?url={0}'.format(id)
|
||||
result = {}
|
||||
print(url)
|
||||
headers = self.header.copy()
|
||||
del headers['Content-type']
|
||||
result["parse"] = parse
|
||||
result["url"] = url
|
||||
result["header"] = headers
|
||||
return result
|
||||
|
||||
def localProxy(self, param):
|
||||
pass
|
||||
@@ -0,0 +1,255 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# by @嗷呜
|
||||
import re
|
||||
import sys
|
||||
from Crypto.Hash import MD5
|
||||
sys.path.append("..")
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Util.Padding import pad, unpad
|
||||
from urllib.parse import quote, urlparse
|
||||
from base64 import b64encode, b64decode
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
import json
|
||||
import time
|
||||
from base.spider import Spider
|
||||
|
||||
class Spider(Spider):
|
||||
|
||||
'''
|
||||
sites照常配置,
|
||||
lives配置:
|
||||
{
|
||||
"name": "xxxx",
|
||||
"type": 3,
|
||||
"api": "路径/若惜追剧APP.py",
|
||||
"ext": ""
|
||||
}
|
||||
'''
|
||||
|
||||
def init(self, extend=""):
|
||||
self.host = self.gethost()
|
||||
pass
|
||||
|
||||
def getName(self):
|
||||
pass
|
||||
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def action(self, action):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
def homeContent(self, filter):
|
||||
data = self.getdata("/api.php/getappapi.index/initV119")
|
||||
dy = {"class": "类型", "area": "地区", "lang": "语言", "year": "年份", "letter": "字母", "by": "排序",
|
||||
"sort": "排序"}
|
||||
filters = {}
|
||||
classes = []
|
||||
json_data = data["type_list"]
|
||||
homedata = data["banner_list"][8:]
|
||||
for item in json_data:
|
||||
if item["type_name"] == "全部":
|
||||
continue
|
||||
has_non_empty_field = False
|
||||
jsontype_extend = json.loads(item["type_extend"])
|
||||
homedata.extend(item["recommend_list"])
|
||||
jsontype_extend["sort"] = "最新,最热,最赞"
|
||||
classes.append({"type_name": item["type_name"], "type_id": item["type_id"]})
|
||||
for key in dy:
|
||||
if key in jsontype_extend and jsontype_extend[key].strip() != "":
|
||||
has_non_empty_field = True
|
||||
break
|
||||
if has_non_empty_field:
|
||||
filters[str(item["type_id"])] = []
|
||||
for dkey in jsontype_extend:
|
||||
if dkey in dy and jsontype_extend[dkey].strip() != "":
|
||||
values = jsontype_extend[dkey].split(",")
|
||||
value_array = [{"n": value.strip(), "v": value.strip()} for value in values if
|
||||
value.strip() != ""]
|
||||
filters[str(item["type_id"])].append({"key": dkey, "name": dy[dkey], "value": value_array})
|
||||
result = {}
|
||||
result["class"] = classes
|
||||
result["filters"] = filters
|
||||
result["list"] = homedata[1:]
|
||||
return result
|
||||
|
||||
def homeVideoContent(self):
|
||||
pass
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
body = {"area": extend.get('area', '全部'), "year": extend.get('year', '全部'), "type_id": tid, "page": pg,
|
||||
"sort": extend.get('sort', '最新'), "lang": extend.get('lang', '全部'),
|
||||
"class": extend.get('class', '全部')}
|
||||
result = {}
|
||||
data = self.getdata("/api.php/getappapi.index/typeFilterVodList", body)
|
||||
result["list"] = data["recommend_list"]
|
||||
result["page"] = pg
|
||||
result["pagecount"] = 9999
|
||||
result["limit"] = 90
|
||||
result["total"] = 999999
|
||||
return result
|
||||
|
||||
def detailContent(self, ids):
|
||||
body = f"vod_id={ids[0]}"
|
||||
data = self.getdata("/api.php/getappapi.index/vodDetail", body)
|
||||
vod = data["vod"]
|
||||
play = []
|
||||
names = []
|
||||
for itt in data["vod_play_list"]:
|
||||
a = []
|
||||
names.append(itt["player_info"]["show"])
|
||||
for it in itt['urls']:
|
||||
it['user_agent']=itt["player_info"].get("user_agent")
|
||||
it["parse"]=itt["player_info"].get("parse")
|
||||
a.append(f"{it['name']}${self.e64(json.dumps(it))}")
|
||||
play.append("#".join(a))
|
||||
vod["vod_play_from"] = "$$$".join(names)
|
||||
vod["vod_play_url"] = "$$$".join(play)
|
||||
result = {"list": [vod]}
|
||||
return result
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
body = f"keywords={key}&type_id=0&page={pg}"
|
||||
data = self.getdata("/api.php/getappapi.index/searchList", body)
|
||||
result = {"list": data["search_list"], "page": pg}
|
||||
return result
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
ids = json.loads(self.d64(id))
|
||||
h={"User-Agent": (ids['user_agent'] or "okhttp/3.14.9")}
|
||||
try:
|
||||
if re.search(r'url=', ids['parse_api_url']):
|
||||
data=self.fetch(ids['parse_api_url'], headers=h, timeout=10).json()
|
||||
url=data.get('url') or data['data'].get('url')
|
||||
else:
|
||||
body = f"parse_api={ids.get('parse') or ids['parse_api_url'].replace(ids['url'], '')}&url={quote(self.aes(ids['url'],True))}&token={ids.get('token')}"
|
||||
b = self.getdata("/api.php/getappapi.index/vodParse", body)['json']
|
||||
url = json.loads(b)['url']
|
||||
if 'error' in url:raise ValueError(f"解析失败: {url}")
|
||||
p=0
|
||||
except Exception as e:
|
||||
print('错误信息:',e)
|
||||
url, p = ids['url'], 1
|
||||
|
||||
if re.search(r'\.jpg|\.png|\.jpeg', url):
|
||||
url = self.Mproxy(url)
|
||||
result = {}
|
||||
result["parse"] = p
|
||||
result["url"] = url
|
||||
result["header"] = h
|
||||
return result
|
||||
|
||||
def liveContent(self, url):
|
||||
id=self.homeContent(True)['class'][-1]['type_id']
|
||||
vlist=self.categoryContent(id,1,False,{})['list']
|
||||
results = []
|
||||
with ThreadPoolExecutor(max_workers=len(vlist)) as executor:
|
||||
futures = [executor.submit(self.livedetailContent, item['vod_name'], item['vod_id']) for item in vlist]
|
||||
for future in futures:
|
||||
try:
|
||||
detail = future.result()
|
||||
if detail:
|
||||
results.append(detail)
|
||||
except Exception as e:
|
||||
print(f"处理详情数据失败: {str(e)}")
|
||||
return '\n'.join(results)
|
||||
|
||||
def livedetailContent(self, name,id):
|
||||
try:
|
||||
print(f"获取直播源:{name}")
|
||||
body = f"vod_id={id}"
|
||||
data = self.getdata("/api.php/getappapi.index/vodDetail", body)
|
||||
play = [f"{name},#genre#"]
|
||||
for itt in data["vod_play_list"]:
|
||||
for it in itt['urls']:
|
||||
play.append(f"{it['name']}, {it['url']}")
|
||||
except Exception as e:
|
||||
print(f"获取直播源失败:{str(e)}")
|
||||
play=[]
|
||||
return '\n'.join(play)
|
||||
|
||||
def localProxy(self, param):
|
||||
return self.Mlocal(param)
|
||||
|
||||
def gethost(self):
|
||||
headers = {
|
||||
'User-Agent': 'okhttp/3.14.9'
|
||||
}
|
||||
host = self.fetch('https://rxysyyds.oss-cn-chengdu.aliyuncs.com/getapp.txt', headers=headers).text
|
||||
return host.strip()
|
||||
|
||||
def aes(self, text,b=None):
|
||||
key = b"ebad3f1a58b13933"
|
||||
cipher = AES.new(key, AES.MODE_CBC, key)
|
||||
if b:
|
||||
ct_bytes = cipher.encrypt(pad(text.encode("utf-8"), AES.block_size))
|
||||
ct = b64encode(ct_bytes).decode("utf-8")
|
||||
return ct
|
||||
else :
|
||||
pt = unpad(cipher.decrypt(b64decode(text)), AES.block_size)
|
||||
return pt.decode("utf-8")
|
||||
|
||||
def header(self):
|
||||
t = str(int(time.time()))
|
||||
header = {"Referer":self.host,
|
||||
"User-Agent": "okhttp/3.14.9", "app-version-code": "140", "app-ui-mode": "light",
|
||||
"app-api-verify-time": t, "app-user-device-id": self.md5(t),
|
||||
"app-api-verify-sign": self.aes(t,True),
|
||||
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"}
|
||||
return header
|
||||
|
||||
def getdata(self, path, data=None):
|
||||
vdata = self.post(f"{self.host}{path}", headers=self.header(), data=data, timeout=10).json()['data']
|
||||
data1 = self.aes(vdata)
|
||||
return json.loads(data1)
|
||||
|
||||
def Mproxy(self, url):
|
||||
return f"{self.getProxyUrl()}&url={self.e64(url)}&type=m3u8"
|
||||
|
||||
def Mlocal(self, param,header=None):
|
||||
url = self.d64(param["url"])
|
||||
ydata = self.fetch(url, headers=header, allow_redirects=False)
|
||||
data = ydata.content.decode('utf-8')
|
||||
if ydata.headers.get('Location'):
|
||||
url = ydata.headers['Location']
|
||||
data = self.fetch(url, headers=header).content.decode('utf-8')
|
||||
parsed_url = urlparse(url)
|
||||
durl = parsed_url.scheme + "://" + parsed_url.netloc
|
||||
lines = data.strip().split('\n')
|
||||
for index, string in enumerate(lines):
|
||||
if '#EXT' not in string and 'http' not in string:
|
||||
last_slash_index = string.rfind('/')
|
||||
lpath = string[:last_slash_index + 1]
|
||||
lines[index] = durl + ('' if lpath.startswith('/') else '/') + lpath
|
||||
data = '\n'.join(lines)
|
||||
return [200, "application/vnd.apple.mpegur", data]
|
||||
|
||||
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 md5(self, text):
|
||||
h = MD5.new()
|
||||
h.update(text.encode('utf-8'))
|
||||
return h.hexdigest()
|
||||
@@ -0,0 +1,239 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# by @嗷呜
|
||||
import sys
|
||||
sys.path.append("..")
|
||||
import re
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Util.Padding import pad, unpad
|
||||
from base64 import b64encode, b64decode
|
||||
import json
|
||||
from base.spider import Spider
|
||||
from urllib.parse import quote
|
||||
|
||||
|
||||
class Spider(Spider):
|
||||
|
||||
def getName(self):
|
||||
return "视觉"
|
||||
|
||||
def init(self, extend=""):
|
||||
self.host = self.host()
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def action(self, action):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
def homeContent(self, filter):
|
||||
data = self.fetch(
|
||||
f"{self.host}/api/v3/drama/getCategory?orderBy=type_id",
|
||||
headers=self.headers,
|
||||
).json()
|
||||
dy = {
|
||||
"class": "类型",
|
||||
"area": "地区",
|
||||
"lang": "语言",
|
||||
"year": "年份",
|
||||
"letter": "字母",
|
||||
"by": "排序",
|
||||
"sort": "排序",
|
||||
}
|
||||
filters = {}
|
||||
classes = []
|
||||
for item in data["data"]:
|
||||
has_non_empty_field = False
|
||||
jsontype_extend = json.loads(item["converUrl"])
|
||||
classes.append({"type_name": item["name"], "type_id": str(item["id"])})
|
||||
for key in dy:
|
||||
if key in jsontype_extend and jsontype_extend[key].strip() != "":
|
||||
has_non_empty_field = True
|
||||
break
|
||||
if has_non_empty_field:
|
||||
filters[str(item["id"])] = []
|
||||
for dkey in jsontype_extend:
|
||||
if dkey in dy and jsontype_extend[dkey].strip() != "":
|
||||
values = jsontype_extend[dkey].split(",")
|
||||
value_array = [
|
||||
{"n": value.strip(), "v": value.strip()}
|
||||
for value in values
|
||||
if value.strip() != ""
|
||||
]
|
||||
filters[str(item["id"])].append(
|
||||
{"key": dkey, "name": dy[dkey], "value": value_array}
|
||||
)
|
||||
result = {}
|
||||
result["class"] = classes
|
||||
result["filters"] = filters
|
||||
return result
|
||||
|
||||
def homeVideoContent(self):
|
||||
data = self.fetch(f"{self.host}/api/ex/v3/security/tag/list", headers=self.headers).json()["data"]
|
||||
data1 = self.aes(self.aes(data, self.key[0]), self.key[1], 'decrypt', True)
|
||||
list = []
|
||||
for item in data1[0]['carousels']:
|
||||
id = item['link'].split("id=")[1]
|
||||
list.append({
|
||||
"vod_id": id,
|
||||
'vod_name': item.get("title"),
|
||||
'vod_pic': item.get("cover"),
|
||||
'vod_remarks': item.get("sort"),
|
||||
})
|
||||
result = {"list": list}
|
||||
return result
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
params = []
|
||||
if extend.get('area'):
|
||||
params.append(f"vodArea={extend['area']}")
|
||||
if extend.get('classs'):
|
||||
params.append(f"vodClass={extend['class']}")
|
||||
params.append("pagesize=20")
|
||||
params.append(f"typeId1={tid}")
|
||||
params.append(f"page={pg}")
|
||||
if extend.get('year'):
|
||||
params.append(f"vodYear={extend['year']}")
|
||||
body = '&'.join(params)
|
||||
path = self.aes(self.aes(body, self.key[1], 'encrypt'), self.key[0], 'encrypt', True)
|
||||
data = self.fetch(f"{self.host}/api/ex/v3/security/drama/list?query={path}", headers=self.headers).json()[
|
||||
"data"]
|
||||
data = self.aes(self.aes(data, self.key[0]), self.key[1], 'decrypt', True)['list']
|
||||
list = []
|
||||
for item in data:
|
||||
list.append({
|
||||
'vod_id': item.get("id"),
|
||||
'vod_pic': item["coverImage"].get("path"),
|
||||
'vod_name': item.get("name"),
|
||||
'vod_year': item.get("year"),
|
||||
'vod_remarks': item.get("remark")
|
||||
})
|
||||
result = {}
|
||||
result["list"] = list
|
||||
result["page"] = pg
|
||||
result["pagecount"] = 9999
|
||||
result["limit"] = 90
|
||||
result["total"] = 999999
|
||||
return result
|
||||
|
||||
def detailContent(self, ids):
|
||||
url = f"{self.host}/api/v3/drama/getDetail?id={ids[0]}"
|
||||
data = self.post(url, headers=self.headers).json()["data"]
|
||||
vod = {
|
||||
'vod_name': data.get("name"),
|
||||
'vod_area': data.get("area"),
|
||||
'type_name': data.get("clazz"),
|
||||
'vod_actor': data.get("actor"),
|
||||
'vod_director': data.get("director"),
|
||||
'vod_content': data.get("brief").strip(),
|
||||
}
|
||||
play = []
|
||||
names = []
|
||||
plays = {}
|
||||
for itt in data["videos"]:
|
||||
if itt["sourceCn"] not in names:
|
||||
plays[itt["source"]] = []
|
||||
names.append(itt["sourceCn"])
|
||||
url = f"vodPlayFrom={itt['source']}&playUrl={itt['path']}"
|
||||
if re.search(r"\.(mp4|m3u8|flv)$", itt["path"]):
|
||||
url = itt["path"]
|
||||
plays[itt["source"]].append(f"{itt['titleOld']}${url}")
|
||||
for it in plays:
|
||||
play.append("#".join(plays[it]))
|
||||
vod["vod_play_from"] = "$$$".join(names)
|
||||
vod["vod_play_url"] = "$$$".join(play)
|
||||
result = {"list": [vod]}
|
||||
return result
|
||||
|
||||
def searchContent(self, key, quick, pg=1):
|
||||
body = f"pagesize=20&page={pg}&searchKeys={key}"
|
||||
path = self.aes(self.aes(body, self.key[1], 'encrypt'), self.key[0], 'encrypt', True)
|
||||
data = self.fetch(f"{self.host}/api/ex/v3/security/drama/list?query={path}", headers=self.headers).json()[
|
||||
"data"]
|
||||
data = self.aes(self.aes(data, self.key[0]), self.key[1], 'decrypt', True)['list']
|
||||
list = []
|
||||
for item in data:
|
||||
list.append({
|
||||
'vod_id': item.get("id"),
|
||||
'vod_pic': item["coverImage"].get("path"),
|
||||
'vod_name': item.get("name"),
|
||||
'vod_year': item.get("year"),
|
||||
'vod_remarks': item.get("remark")
|
||||
})
|
||||
result = {"list": list, "page": pg}
|
||||
return result
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
url = id
|
||||
if "vodPlayFrom" in url:
|
||||
try:
|
||||
path = self.aes(self.aes(id, self.key[1], 'encrypt'), self.key[0], 'encrypt', True)
|
||||
data = \
|
||||
self.fetch(f"{self.host}/api/ex/v3/security/videoUsableUrl?query={path}", headers=self.headers).json()[
|
||||
"data"]
|
||||
url = self.aes(self.aes(data, self.key[0]), self.key[1], 'decrypt', True)['playUrl']
|
||||
# try:
|
||||
# url1 = self.fetch(url, headers=self.headers, timeout=5, allow_redirects=False).headers['Location']
|
||||
# if "http" in url1 and url1:
|
||||
# url = url1
|
||||
# except:
|
||||
# pass
|
||||
except Exception as e:
|
||||
pass
|
||||
if '.jpg' in url or '.jpeg' in url or '.png' in url:
|
||||
url = self.getProxyUrl() + "&url=" + b64encode(url.encode('utf-8')).decode('utf-8') + "&type=m3u8"
|
||||
result = {}
|
||||
result["parse"] = 0
|
||||
result["url"] = url
|
||||
result["header"] = {'User-Agent': 'okhttp/3.12.1'}
|
||||
return result
|
||||
|
||||
def localProxy(self, param):
|
||||
url = b64decode(param["url"]).decode('utf-8')
|
||||
durl = url[:url.rfind('/')]
|
||||
data = self.fetch(url, headers=self.headers).content.decode("utf-8")
|
||||
lines = data.strip().split('\n')
|
||||
for index, string in enumerate(lines):
|
||||
if '#EXT' not in string and 'http' not in string:
|
||||
lines[index] = durl + ('' if string.startswith('/') else '/') + string
|
||||
data = '\n'.join(lines)
|
||||
return [200, "application/vnd.apple.mpegur", data]
|
||||
|
||||
def host(self):
|
||||
try:
|
||||
url = self.fetch('https://www.shijue.pro/token.txt', headers=self.headers).json()['domain']
|
||||
return url
|
||||
except:
|
||||
return "http://118.25.18.217:6632"
|
||||
|
||||
headers = {
|
||||
'User-Agent': 'okhttp/3.12.1',
|
||||
'Content-Type': 'application/json;'
|
||||
}
|
||||
key = ['TFLYWVJ5EG5YB1PLZLVVMGVLBGRIDCSW', 'nj6E5K4yYYT5W4ScJ3J3rJ2zrzcJkpTk']
|
||||
|
||||
def aes(self, word, key, mode='decrypt', bool=False):
|
||||
key = key.encode('utf-8')
|
||||
if mode == 'decrypt':
|
||||
word = b64decode(word)
|
||||
cipher = AES.new(key, AES.MODE_ECB)
|
||||
decrypted = cipher.decrypt(word)
|
||||
word = unpad(decrypted, AES.block_size).decode('utf-8')
|
||||
if bool:
|
||||
word = json.loads(word)
|
||||
elif mode == 'encrypt':
|
||||
cipher = AES.new(key, AES.MODE_ECB)
|
||||
padded = pad(word.encode('utf-8'), AES.block_size)
|
||||
encrypted = cipher.encrypt(padded)
|
||||
word = b64encode(encrypted).decode('utf-8')
|
||||
if bool:
|
||||
word = quote(word)
|
||||
return word
|
||||
|
||||
|
||||
@@ -0,0 +1,340 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# by @嗷呜
|
||||
import binascii
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
import uuid
|
||||
from urllib.parse import urlparse
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
sys.path.append('..')
|
||||
from base.spider import Spider
|
||||
from base64 import b64encode, b64decode
|
||||
from Crypto.PublicKey import RSA
|
||||
from Crypto.Cipher import AES, PKCS1_v1_5
|
||||
from Crypto.Util.Padding import unpad, pad
|
||||
from Crypto.Hash import MD5
|
||||
|
||||
|
||||
class Spider(Spider):
|
||||
|
||||
def init(self, extend=""):
|
||||
self.host = self.gethost()
|
||||
pass
|
||||
|
||||
def getName(self):
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
headers = {
|
||||
'AppID': '534',
|
||||
'app_id': '534',
|
||||
'version': '1.0.3',
|
||||
'package': 'com.hjmore.wallpaper',
|
||||
'user_id': '3507f394e83d2424',
|
||||
'user-id': '3507f394e83d2424',
|
||||
'app_name': 'lanlan',
|
||||
'app-name': 'lanlan',
|
||||
'Content-Type': 'application/json; charset=utf-8;',
|
||||
'User-Agent': 'okhttp/4.9.0'
|
||||
}
|
||||
|
||||
def homeContent(self, filter):
|
||||
hdata=self.getdata('/api.php/provide/index',self.getbody({'tid':'0'}))
|
||||
vlist=hdata['data'].get('tj',[])
|
||||
result = {}
|
||||
classes = []
|
||||
filters = {}
|
||||
for i in hdata['data']['sub_data']:
|
||||
id=str(i['type_id'])
|
||||
classes.append({'type_id': id, 'type_name': i['type_name']})
|
||||
if len(i['data']):
|
||||
vlist.extend(i['data'])
|
||||
with ThreadPoolExecutor(max_workers=len(classes)) as executor:
|
||||
results = executor.map(self.getf, classes)
|
||||
for id, ft in results:
|
||||
if len(ft):filters[id] = ft
|
||||
result['class'] = classes
|
||||
result['filters'] = filters
|
||||
result['list'] = vlist
|
||||
return result
|
||||
|
||||
def homeVideoContent(self):
|
||||
pass
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
body={
|
||||
"tid": tid,
|
||||
"type": extend.get('type'),
|
||||
"lang": extend.get('lang'),
|
||||
"area": extend.get('area'),
|
||||
"year": extend.get('year'),
|
||||
"pg": pg
|
||||
}
|
||||
body = {k: v for k, v in body.items() if v is not None and v != ""}
|
||||
data=self.getdata('/api.php/provide/nav',self.getbody(body))
|
||||
result = {}
|
||||
result['list'] = data['data']['data']
|
||||
result['page'] = pg
|
||||
result['pagecount'] = 9999
|
||||
result['limit'] = 90
|
||||
result['total'] = 999999
|
||||
return result
|
||||
pass
|
||||
|
||||
def detailContent(self, ids):
|
||||
data=self.getdata('/api.php/provide/vod',self.getbody({'ids':ids[0]}))
|
||||
vod=data['data']
|
||||
plist=[]
|
||||
names=[]
|
||||
for i in vod['vod_play_url']:
|
||||
ulist=[]
|
||||
names.append(i['name'].split(' ')[0])
|
||||
jdata={'parse':''}
|
||||
if i.get('parse') and isinstance(i['parse'], list) and len(i['parse']):
|
||||
jdata['parse']=self.e64(json.dumps(i['parse']))
|
||||
for j in i['data']:
|
||||
jdata['url']=j['url']
|
||||
ulist.append(f'{j["name"]}${self.e64(json.dumps(jdata))}')
|
||||
plist.append('#'.join(ulist))
|
||||
vod['vod_play_from']='$$$'.join(names)
|
||||
vod['vod_play_url']='$$$'.join(plist)
|
||||
vod.pop('cover_list', None)
|
||||
return {'list':[vod]}
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
body={"wd":key,"tid":"0","pg":pg}
|
||||
data=self.getdata('/api.php/provide/search',self.getbody(body))
|
||||
vlist=[]
|
||||
for i in data['data']:
|
||||
i.pop('vod_play_from', None)
|
||||
vlist.append(i)
|
||||
return {'list':vlist,'page':pg}
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
data=json.loads(self.d64(id))
|
||||
parse=data.get('parse')
|
||||
url,p,head = data.get('url'),1,''
|
||||
if parse:
|
||||
parse=json.loads(self.d64(parse))
|
||||
if not re.search(r'\.m3u8|.mp4|\.flv', url) and parse:
|
||||
for p in parse:
|
||||
try:
|
||||
data=self.fetch(f'{p}{url}',self.headers).json()
|
||||
url=data.get('data',{}).get('url') or data.get('url')
|
||||
head=data.get('data',{}).get('header') or data.get('header')
|
||||
p=0
|
||||
break
|
||||
except:
|
||||
p,url=1,data.get('url')
|
||||
head = {'User-Agent': 'okhttp/4.9.0'}
|
||||
return {'parse': p, 'url': url, 'header': head}
|
||||
|
||||
def localProxy(self, param):
|
||||
pass
|
||||
|
||||
def getf(self, map):
|
||||
ft,id =[], map['type_id']
|
||||
try:
|
||||
fdata = self.getdata('/api.php/provide/nav', self.getbody({'tid': id, 'pg': '1'}))
|
||||
dy = ['area', 'year', 'lang', 'type']
|
||||
fd = fdata['data']['type_extend']
|
||||
has_non_empty_field = False
|
||||
for key in dy:
|
||||
if key in fd and fd[key].strip() != "":
|
||||
has_non_empty_field = True
|
||||
break
|
||||
if has_non_empty_field:
|
||||
for dkey in fd:
|
||||
if dkey in dy and fd[dkey].strip() != "":
|
||||
values = fd[dkey].split(",")
|
||||
value_array = [{"n": value.strip(), "v": value.strip()} for value in values if
|
||||
value.strip() != ""]
|
||||
ft.append({"key": dkey, "name": dkey, "value": value_array})
|
||||
return (id, ft)
|
||||
except:
|
||||
return (id, ft)
|
||||
|
||||
def getskey(self):
|
||||
random_bytes = os.urandom(16)
|
||||
return binascii.hexlify(random_bytes).decode()
|
||||
|
||||
def getohost(self):
|
||||
url='https://bianyuan001.oss-cn-beijing.aliyuncs.com/huidu1.0.0.json'
|
||||
response = self.fetch(url, headers=self.headers).json()
|
||||
return response['servers'][0]
|
||||
|
||||
def gethost(self):
|
||||
body={
|
||||
"gr_rp_size": "1080*2272",
|
||||
"gr_app_list": "%E5%B1%8F%E5%B9%95%E5%BD%95%E5%88%B6%EF%BC%88com.miui.screenrecorder%29%0A%E5%A4%B8%E5%85%8B%EF%BC%88com.quark.browser%29%0A%E8%BE%B9%E7%BC%98%E8%A7%86%E9%A2%91%EF%BC%88com.hjmore.wallpaper%29%0A%E5%93%94%E5%93%A9%E5%93%94%E5%93%A9%EF%BC%88tv.danmaku.bili%29%0A%E7%81%AB%E6%98%9F%E6%90%9C%E9%A2%98%EF%BC%88com.fenbi.android.souti%29%0A%E6%94%AF%E4%BB%98%E5%AE%9D%EF%BC%88com.eg.android.AlipayGphone%29%0AWPS%20Office%EF%BC%88cn.wps.moffice_eng%29",
|
||||
"gr_lal": "0.0%2C0.0",
|
||||
"gr_system_type": "android",
|
||||
"gr_device_imei": "3507f394e83d2424",
|
||||
"gr_app_version": "1.0.3",
|
||||
"gr_device_model": "Xiaomi%20M2012K10C%20%28Android%20%E7%89%88%E6%9C%AC%3A%2011%2C%20SDK%E7%89%88%E6%9C%AC%3A%2030%29",
|
||||
"gr_city": "%E8%B4%B5%E5%B7%9E%2C%E6%9C%AA%E7%9F%A5%2C%E6%9C%AA%E7%9F%A5",
|
||||
"requestId": self.uuid(),
|
||||
"timeStamp": str(int(time.time() * 1000)),
|
||||
"version": "1.0.3",
|
||||
"package": "com.hjmore.wallpaper",
|
||||
"userLoginToken": "",
|
||||
"app_id": "534",
|
||||
"appName": 2131951658,
|
||||
"device_id": "3507f394e83d2424",
|
||||
"device-id": "3507f394e83d2424",
|
||||
"oaid": "",
|
||||
"imei": "",
|
||||
"referer_shop": "边缘影视",
|
||||
"referer-shop": "边缘影视",
|
||||
"access_fine_location": 0,
|
||||
"access-fine-location": 0
|
||||
}
|
||||
ohost = self.getohost()
|
||||
data=self.getdata(f'/api.php/settings/grayscale_list',body,ohost)
|
||||
parsed_url = urlparse(data['data']['grayscale']['server_url'][0])
|
||||
domain = parsed_url.scheme + "://" + parsed_url.netloc
|
||||
return domain
|
||||
|
||||
def drsa(self, encrypted_data):
|
||||
private_key_pem = """-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDA5NWiAwRjH50/
|
||||
IJY1N0zLopa4jpuWE7kWMn1Qunu6SjBgTvNRmRUoPDHn54haLfbfXIa2X+/sIaMB
|
||||
/O3HhrpVsz55E5W2vpZ5fBYWh+M65bQERKTW+l72H7GR9x0yj3QPByzzfsj/QkyP
|
||||
81prpwR9i8yMe7yG9TFKqUQCPE+/GrhNU1Qf6nFmV+vMnlP9DantkwAt4fPOMZn3
|
||||
j4da65/1YQV+F5bYzaLenNVKbHf8U8fVYLZWIy4yk2Vpe4R2Z+JX/eHWsChE9hOu
|
||||
iFm02eTW5NJLZlWUxYrSE23VXi8oXSEdON3UEOrwSdAUh4SXxLZ9U7KpNVdTwWyR
|
||||
AS4GyzJ/AgMBAAECggEBAKzmcXefLLeNBu4mz30z7Go7es5DRcLoOudiqmFKRs1c
|
||||
4q/xFLj3drdx/WnZZ6ctvDPKRBYFOJF4NRz7Ekfew/c9i6oLnA8KFuceCs53T37j
|
||||
ltCclwT7t1L2ZbxovIsteuJdlDVOV+w2CVqez1Xfh27heKAT6ZEvBtfdkVBPr0uj
|
||||
oVwa2+XlJmYZw5dHeB7ySVeAQ+69zDuADB8OWxPWsv6Del+Fhf0kTHAw4WgqcYsd
|
||||
JUunCjgLdJUlDgXzH/M/Nj8NYVEuq6QpmhaktJ4fwn/F7u3lQllVCFKj5lr0Xb92
|
||||
y7lvQlGqMKX1oxf+P5c5/vie1kDx1Rj4S++flIcVlUECgYEA4BuxCZ1c8oOF98bs
|
||||
KTAONnnZniQ1BRt7rA+O9+++lDjxJhxkuthwjB9YzrnZtxHJtvIIie9Jv8MVfzHa
|
||||
p2woDtiEh3YYwmIlgNUFvTcGe++tTiEiLDcGc/xNhpvfbLaw9QB7/HQ+LT1QCMxJ
|
||||
ufdBrR98l0khIGjYqxDW3W5pV70CgYEA3Ff/9+GM2XI/EUSTYrpnwp5R5OsXz1DL
|
||||
3CFFgp1EPCNk/c3YNWnrUtTkfmKAlRqWIHfphvH/jS6jpGrfRxDggPwGMtBc134b
|
||||
brIM5i4KNj/EcE+w5g03HaKBf1ZihHDQ53c6wTn6IFOHJNSPRLqMNqRymfbclNyO
|
||||
lBMHQmB8yOsCgYBCdZPTwRnuRTi2WQRx1nFwkEQL1Lrwb80GInsIZc2DkTtaTPNG
|
||||
QadmtmkUrSK2Wo0SNsZ3eUHKn2TBmpw4KCfc9zKeJVSEWKy8fu+7xBSlLlebotHK
|
||||
gOrl/H1VHOZuC+OAVItwO1yw98zDPynh/0Q3ve2pw6MSRGV0nYLKmdKdlQKBgQCJ
|
||||
Ty1rw1qKhu9WS22tMIxIc3CFPxtvTeI8I1+1rVtAPq5Im2YIoyDKVXCucaO/RvoW
|
||||
8aLNPTELQe0oIJFTL+k3d9ZFBCNXBncB3GK9biNe+w3nD0IlmkamaQZZ2/M4pTUJ
|
||||
iPtMPlzomCS3ht5g7f9CbegcmgGLooYXMGRtsMMSUQKBgQCoj+3UciH2i+HyUla5
|
||||
1FxivjH3MqSTE4Q7OdzrELb6DoLYzjgWAbpG8HIuodD4uG5xz1oR5H7vkblf1itB
|
||||
hwOwDEiabyX76e/I3Q0ovwBV+9PMjM4UVU0kHoiu3Z2s90ckwNh58w3QH5fn9E0b
|
||||
fqMnB6uWze+xrXWijaOzVZhIZg==
|
||||
-----END RSA PRIVATE KEY-----"""
|
||||
private_key = RSA.import_key(private_key_pem)
|
||||
cipher = PKCS1_v1_5.new(private_key)
|
||||
decrypted_data = cipher.decrypt(b64decode(encrypted_data), None)
|
||||
return decrypted_data.decode('utf-8')
|
||||
|
||||
def ersa(self, data):
|
||||
public_key = """-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA+0QMb3WDXjNBRovRhTLH
|
||||
g3d+CliZAva2tepWNNN0Pj6DgE3ZTnPR34iL/cjo9Jbd3dqAJs/YkKnFurGkDxz5
|
||||
TthIqvmz244wiFcHt+FGWoJsj5ZVvrH3pPwH85ggmI1DjxSJEUhB12Z9X6FGli8D
|
||||
drR9xeLe5y8vFekux8xCQ7pwH1mNQu4Wy32WVM8aLjmRjNzEWOvEMAWCRuwymEdS
|
||||
zlWoH53qk1dqd6DAmOJhWU2hH6Yt2ZY9LTaDGiHrS+g0DuwajAQzhbM8eonGYMph
|
||||
nP4q0UTHWEfaGR3HoILmeM32M+qF/UCGfgfR6tCMiXPoHwnD2zoxbZ2p+QlYuTZL
|
||||
vQIDAQAB
|
||||
-----END PUBLIC KEY-----"""
|
||||
key = RSA.importKey(public_key)
|
||||
cipher = PKCS1_v1_5.new(key)
|
||||
encrypted = cipher.encrypt(data.encode())
|
||||
return b64encode(encrypted).decode()
|
||||
|
||||
def eaes(self, data, key):
|
||||
key = key.encode('utf-8')
|
||||
cipher = AES.new(key, AES.MODE_ECB)
|
||||
padded = pad(data.encode('utf-8'), AES.block_size)
|
||||
encrypted = cipher.encrypt(padded)
|
||||
word = b64encode(encrypted).decode('utf-8')
|
||||
return word
|
||||
|
||||
def daes(self, encrypted_data, key):
|
||||
key = key.encode('utf-8')
|
||||
cipher = AES.new(key, AES.MODE_ECB)
|
||||
encrypted = b64decode(encrypted_data)
|
||||
decrypted = cipher.decrypt(encrypted)
|
||||
unpadded = unpad(decrypted, AES.block_size)
|
||||
return unpadded.decode('utf-8')
|
||||
|
||||
def getbody(self,params=None):
|
||||
body = {
|
||||
"requestId": self.uuid(),
|
||||
"timeStamp": str(int(time.time()*1000)),
|
||||
"version": "1.0.3",
|
||||
"package": "com.hjmore.wallpaper",
|
||||
"userLoginToken": "",
|
||||
"app_id": "534",
|
||||
"appName": 2131951658,
|
||||
"device_id": "3507f394e83d2424",
|
||||
"device-id": "3507f394e83d2424",
|
||||
"oaid": "",
|
||||
"imei": "",
|
||||
"referer_shop": "边缘影视",
|
||||
"referer-shop": "边缘影视",
|
||||
"access_fine_location": 0,
|
||||
"access-fine-location": 0
|
||||
}
|
||||
if params:
|
||||
body.update(params)
|
||||
return body
|
||||
|
||||
def getdata(self, path, body,host=None):
|
||||
jdata=json.dumps(body)
|
||||
msign = self.md5(jdata)
|
||||
skey = self.getskey()
|
||||
jsign={'key': skey,'sign': msign}
|
||||
Sign=self.ersa(json.dumps(jsign))
|
||||
header=self.headers.copy()
|
||||
header['Sign']=Sign
|
||||
dbody=self.eaes(jdata, skey)
|
||||
response = self.post(f'{host or self.host}{path}', headers=header, data=dbody)
|
||||
rdata=response.text
|
||||
if response.headers.get('Sign'):
|
||||
dkey=self.drsa(response.headers['Sign'])
|
||||
rdata=self.daes(rdata, dkey)
|
||||
return json.loads(rdata)
|
||||
|
||||
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 md5(self,text):
|
||||
h = MD5.new()
|
||||
h.update(text.encode('utf-8'))
|
||||
return h.hexdigest()
|
||||
|
||||
def uuid(self):
|
||||
return str(uuid.uuid4())
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# by @嗷呜
|
||||
import json
|
||||
import random
|
||||
import sys
|
||||
from base64 import b64encode, b64decode
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
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})
|
||||
pass
|
||||
|
||||
def getName(self):
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
host='http://zero.mitotv.com'
|
||||
|
||||
headers = {
|
||||
'User-Agent': 'okhttp/4.12.0',
|
||||
'client': 'app',
|
||||
'deviceType': 'Android'
|
||||
}
|
||||
|
||||
def homeContent(self, filter):
|
||||
data=self.post(f"{self.host}/api/v1/app/screen/screenType", headers=self.headers).json()
|
||||
result = {}
|
||||
cate = {
|
||||
"类型": "classify",
|
||||
"地区": "region",
|
||||
"年份": "year"
|
||||
}
|
||||
sort={
|
||||
'key':'sreecnTypeEnum',
|
||||
'name': '排序',
|
||||
'value':[{'n':'人气','v':'POPULARITY'},{'n':'评分','v':'COLLECT'},{'n':'热搜','v':'HOT'}]
|
||||
}
|
||||
classes = []
|
||||
filters = {}
|
||||
for k in data['data']:
|
||||
classes.append({
|
||||
'type_name': k['name'],
|
||||
'type_id': k['id']
|
||||
})
|
||||
filters[k['id']] = [
|
||||
{
|
||||
'name': v['name'],
|
||||
'key': cate[v['name']],
|
||||
'value': [
|
||||
{'n': i['name'], 'v': i['name']}
|
||||
for i in v['children']
|
||||
]
|
||||
}
|
||||
for v in k['children']
|
||||
]
|
||||
filters[k['id']].append(sort)
|
||||
result['class'] = classes
|
||||
result['filters'] = filters
|
||||
return result
|
||||
|
||||
def homeVideoContent(self):
|
||||
jdata={"condition":64,"pageNum":1,"pageSize":40}
|
||||
data=self.post(f"{self.host}/api/v1/app/recommend/recommendSubList", headers=self.headers, json=jdata).json()
|
||||
return {'list':self.getlist(data['data']['records'])}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
jdata = {
|
||||
'condition': {
|
||||
'sreecnTypeEnum': 'NEWEST',
|
||||
'typeId': tid,
|
||||
},
|
||||
'pageNum': int(pg),
|
||||
'pageSize': 40,
|
||||
}
|
||||
jdata['condition'].update(extend)
|
||||
data = self.post(f"{self.host}/api/v1/app/screen/screenMovie", headers=self.headers, json=jdata).json()
|
||||
result = {}
|
||||
result['list'] = self.getlist(data['data']['records'])
|
||||
result['page'] = pg
|
||||
result['pagecount'] = 9999
|
||||
result['limit'] = 90
|
||||
result['total'] = 999999
|
||||
return result
|
||||
|
||||
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['data']
|
||||
vod = {
|
||||
'type_name': v.get('classify'),
|
||||
'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': ''
|
||||
}
|
||||
c = self.post(f"{self.host}/api/v1/app/play/movieDetails", headers=self.headers, json=jdata).json()
|
||||
l = c['data']['moviePlayerList']
|
||||
n = {str(i['id']): i['moviePlayerName'] for i in l}
|
||||
m = jdata.copy()
|
||||
m.update({'playerId': str(l[0]['id'])})
|
||||
pd = self.getv(m, c['data']['episodeList'])
|
||||
if len(l)-1:
|
||||
with ThreadPoolExecutor(max_workers=len(l)-1) as executor:
|
||||
future_to_player = {executor.submit(self.getd, jdata, player): player for player in l[1:]}
|
||||
for future in future_to_player:
|
||||
try:
|
||||
o,p = future.result()
|
||||
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[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": key
|
||||
},
|
||||
"pageNum": int(pg),
|
||||
"pageSize": 40
|
||||
}
|
||||
data=self.post(f"{self.host}/api/v1/app/search/searchMovie", headers=self.headers, json=jdata).json()
|
||||
return {'list':self.getlist(data['data']['records']),'page':pg}
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
jdata=json.loads(self.d64(id))
|
||||
data = self.post(f"{self.host}/api/v1/app/play/movieDetails", headers=self.headers, json=jdata).json()
|
||||
try:
|
||||
params={'playerUrl':data['data']['url'],'playerId':jdata['playerId']}
|
||||
pd=self.fetch(f"{self.host}/api/v1/app/play/analysisMovieUrl", headers=self.headers, params=params).json()
|
||||
url,p=pd['data'],0
|
||||
except Exception as e:
|
||||
print(f"请求失败: {e}")
|
||||
url,p=data['data']['url'],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):
|
||||
data=self.fetch(f"{self.host}/api/v1/app/user/visitorInfo", headers=self.headers).json()
|
||||
return data['data']['token']
|
||||
|
||||
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': str(player['id'])})
|
||||
response = self.post(f"{self.host}/api/v1/app/play/movieDetails", headers=self.headers, json=x).json()
|
||||
return x, response['data']['episodeList']
|
||||
|
||||
def getv(self,d,c):
|
||||
f={d['playerId']:''}
|
||||
g=[]
|
||||
for i in c:
|
||||
j=d.copy()
|
||||
j.update({'episodeId':str(i['id'])})
|
||||
g.append(f"{i['episode']}${self.e64(json.dumps(j))}")
|
||||
f[d['playerId']]='#'.join(g)
|
||||
return f
|
||||
|
||||
def getlist(self,data):
|
||||
videos = []
|
||||
for i in data:
|
||||
videos.append({
|
||||
'vod_id': f"{i['id']}@@{i['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:
|
||||
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 ""
|
||||
@@ -0,0 +1,209 @@
|
||||
import re
|
||||
import sys
|
||||
from Crypto.Hash import MD5
|
||||
sys.path.append("..")
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Util.Padding import pad, unpad
|
||||
from urllib.parse import quote, urlparse
|
||||
from base64 import b64encode, b64decode
|
||||
import json
|
||||
import time
|
||||
from base.spider import Spider
|
||||
|
||||
|
||||
class Spider(Spider):
|
||||
|
||||
def init(self, extend=""):
|
||||
self.host = self.gethost()
|
||||
pass
|
||||
|
||||
def isVideoFormat(self, url):
|
||||
pass
|
||||
|
||||
def manualVideoCheck(self):
|
||||
pass
|
||||
|
||||
def action(self, action):
|
||||
pass
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
def homeContent(self, filter):
|
||||
data = self.getdata("/api.php/getappapi.index/initV119")
|
||||
dy = {"class": "类型", "area": "地区", "lang": "语言", "year": "年份", "letter": "字母", "by": "排序",
|
||||
"sort": "排序"}
|
||||
filters = {}
|
||||
classes = []
|
||||
json_data = data["type_list"]
|
||||
homedata = data["banner_list"][8:]
|
||||
for item in json_data:
|
||||
if item["type_name"] == "全部":
|
||||
continue
|
||||
has_non_empty_field = False
|
||||
jsontype_extend = json.loads(item["type_extend"])
|
||||
homedata.extend(item["recommend_list"])
|
||||
jsontype_extend["sort"] = "最新,最热,最赞"
|
||||
classes.append({"type_name": item["type_name"], "type_id": item["type_id"]})
|
||||
for key in dy:
|
||||
if key in jsontype_extend and jsontype_extend[key].strip() != "":
|
||||
has_non_empty_field = True
|
||||
break
|
||||
if has_non_empty_field:
|
||||
filters[str(item["type_id"])] = []
|
||||
for dkey in jsontype_extend:
|
||||
if dkey in dy and jsontype_extend[dkey].strip() != "":
|
||||
values = jsontype_extend[dkey].split(",")
|
||||
value_array = [{"n": value.strip(), "v": value.strip()} for value in values if
|
||||
value.strip() != ""]
|
||||
filters[str(item["type_id"])].append({"key": dkey, "name": dy[dkey], "value": value_array})
|
||||
result = {}
|
||||
result["class"] = classes
|
||||
result["filters"] = filters
|
||||
result["list"] = homedata[1:]
|
||||
return result
|
||||
|
||||
def homeVideoContent(self):
|
||||
pass
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
body = {"area": extend.get('area', '全部'), "year": extend.get('year', '全部'), "type_id": tid, "page": pg,
|
||||
"sort": extend.get('sort', '最新'), "lang": extend.get('lang', '全部'),
|
||||
"class": extend.get('class', '全部')}
|
||||
result = {}
|
||||
data = self.getdata("/api.php/getappapi.index/typeFilterVodList", body)
|
||||
result["list"] = data["recommend_list"]
|
||||
result["page"] = pg
|
||||
result["pagecount"] = 9999
|
||||
result["limit"] = 90
|
||||
result["total"] = 999999
|
||||
return result
|
||||
|
||||
def detailContent(self, ids):
|
||||
body = f"vod_id={ids[0]}"
|
||||
data = self.getdata("/api.php/getappapi.index/vodDetail", body)
|
||||
vod = data["vod"]
|
||||
play = []
|
||||
names = []
|
||||
for itt in data["vod_play_list"]:
|
||||
a = []
|
||||
names.append(itt["player_info"]["show"])
|
||||
for it in itt['urls']:
|
||||
it['user_agent'] = itt["player_info"].get("user_agent")
|
||||
it["parse"] = itt["player_info"].get("parse")
|
||||
a.append(f"{it['name']}${self.e64(json.dumps(it))}")
|
||||
play.append("#".join(a))
|
||||
vod["vod_play_from"] = "$$$".join(names)
|
||||
vod["vod_play_url"] = "$$$".join(play)
|
||||
result = {"list": [vod]}
|
||||
return result
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
body = f"keywords={key}&type_id=0&page={pg}"
|
||||
data = self.getdata("/api.php/getappapi.index/searchList", body)
|
||||
result = {"list": data["search_list"], "page": pg}
|
||||
return result
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
ids = json.loads(self.d64(id))
|
||||
h = {"User-Agent": (ids['user_agent'] or "okhttp/3.14.9")}
|
||||
try:
|
||||
if re.search(r'url=', ids['parse_api_url']):
|
||||
data = self.fetch(ids['parse_api_url'], headers=h, timeout=10).json()
|
||||
url = data.get('url') or data['data'].get('url')
|
||||
else:
|
||||
body = f"parse_api={ids.get('parse') or ids['parse_api_url'].replace(ids['url'], '')}&url={quote(self.aes(ids['url'], True))}&token={ids.get('token')}"
|
||||
b = self.getdata("/api.php/getappapi.index/vodParse", body)['json']
|
||||
url = json.loads(b)['url']
|
||||
if 'error' in url: raise ValueError(f"解析失败: {url}")
|
||||
p = 0
|
||||
except Exception as e:
|
||||
print('错误信息:', e)
|
||||
url, p = ids['url'], 1
|
||||
|
||||
if re.search(r'\.jpg|\.png|\.jpeg', url):
|
||||
url = self.Mproxy(url)
|
||||
result = {}
|
||||
result["parse"] = p
|
||||
result["url"] = url
|
||||
result["header"] = h
|
||||
return result
|
||||
|
||||
def localProxy(self, param):
|
||||
return self.Mlocal(param)
|
||||
|
||||
def gethost(self):
|
||||
headers = {
|
||||
'User-Agent': 'okhttp/3.14.9'
|
||||
}
|
||||
response = self.fetch('https://snysw.xyz/mfys.txt',headers=headers).text
|
||||
return response.strip()
|
||||
|
||||
def aes(self, text, b=None):
|
||||
key = b"1234567887654321"
|
||||
cipher = AES.new(key, AES.MODE_CBC, key)
|
||||
if b:
|
||||
ct_bytes = cipher.encrypt(pad(text.encode("utf-8"), AES.block_size))
|
||||
ct = b64encode(ct_bytes).decode("utf-8")
|
||||
return ct
|
||||
else:
|
||||
pt = unpad(cipher.decrypt(b64decode(text)), AES.block_size)
|
||||
return pt.decode("utf-8")
|
||||
|
||||
def header(self):
|
||||
t = str(int(time.time()))
|
||||
header = {"Referer": self.host,
|
||||
"User-Agent": "okhttp/3.14.9", "app-version-code": "140", "app-ui-mode": "light",
|
||||
"app-api-verify-time": t, "app-user-device-id": self.md5(t),
|
||||
"app-api-verify-sign": self.aes(t, True),
|
||||
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"}
|
||||
return header
|
||||
|
||||
def getdata(self, path, data=None):
|
||||
vdata = self.post(f"{self.host}{path}", headers=self.header(), data=data, timeout=10).json()['data']
|
||||
data1 = self.aes(vdata)
|
||||
return json.loads(data1)
|
||||
|
||||
def Mproxy(self, url):
|
||||
return f"{self.getProxyUrl()}&url={self.e64(url)}&type=m3u8"
|
||||
|
||||
def Mlocal(self, param, header=None):
|
||||
url = self.d64(param["url"])
|
||||
ydata = self.fetch(url, headers=header, allow_redirects=False)
|
||||
data = ydata.content.decode('utf-8')
|
||||
if ydata.headers.get('Location'):
|
||||
url = ydata.headers['Location']
|
||||
data = self.fetch(url, headers=header).content.decode('utf-8')
|
||||
parsed_url = urlparse(url)
|
||||
durl = parsed_url.scheme + "://" + parsed_url.netloc
|
||||
lines = data.strip().split('\n')
|
||||
for index, string in enumerate(lines):
|
||||
if '#EXT' not in string and 'http' not in string:
|
||||
last_slash_index = string.rfind('/')
|
||||
lpath = string[:last_slash_index + 1]
|
||||
lines[index] = durl + ('' if lpath.startswith('/') else '/') + lpath
|
||||
data = '\n'.join(lines)
|
||||
return [200, "application/vnd.apple.mpegur", data]
|
||||
|
||||
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 md5(self, text):
|
||||
h = MD5.new()
|
||||
h.update(text.encode('utf-8'))
|
||||
return h.hexdigest()
|
||||
Reference in New Issue
Block a user