201 lines
7.7 KiB
Python
201 lines
7.7 KiB
Python
import base64, json, random, struct, time, sys, traceback
|
|
if sys.version_info[0] >= 3:
|
|
import urllib.request as urllib
|
|
xrange = range
|
|
else:
|
|
import urllib
|
|
|
|
from core import httptools
|
|
from threading import Thread
|
|
|
|
from file import File
|
|
from handler import Handler
|
|
from platformcode import logger
|
|
from server import Server
|
|
|
|
|
|
class Client(object):
|
|
VIDEO_EXTS = {'.avi': 'video/x-msvideo', '.mp4': 'video/mp4', '.mkv': 'video/x-matroska',
|
|
'.m4v': 'video/mp4', '.mov': 'video/quicktime', '.mpg': 'video/mpeg','.ogv': 'video/ogg',
|
|
'.ogg': 'video/ogg', '.webm': 'video/webm', '.ts': 'video/mp2t', '.3gp': 'video/3gpp'}
|
|
|
|
def __init__(self, url, port=None, ip=None, auto_shutdown=True, wait_time=20, timeout=5, is_playing_fnc=None):
|
|
|
|
self.port = port if port else random.randint(8000,8099)
|
|
self.ip = ip if ip else "127.0.0.1"
|
|
self.connected = False
|
|
self.start_time = None
|
|
self.last_connect = None
|
|
self.is_playing_fnc = is_playing_fnc
|
|
self.auto_shutdown = auto_shutdown
|
|
self.wait_time = wait_time
|
|
self.timeout = timeout
|
|
self.running = False
|
|
self.file = None
|
|
self.files = []
|
|
|
|
self._server = Server((self.ip, self.port), Handler, client=self)
|
|
self.add_url(url)
|
|
self.start()
|
|
|
|
def start(self):
|
|
self.start_time = time.time()
|
|
self.running = True
|
|
self._server.run()
|
|
t= Thread(target=self._auto_shutdown)
|
|
t.setDaemon(True)
|
|
t.start()
|
|
logger.info("MEGA Server Started")
|
|
|
|
def _auto_shutdown(self):
|
|
while self.running:
|
|
time.sleep(1)
|
|
if self.file and self.file.cursor:
|
|
self.last_connect = time.time()
|
|
|
|
if self.is_playing_fnc and self.is_playing_fnc():
|
|
self.last_connect = time.time()
|
|
|
|
if self.auto_shutdown:
|
|
#shudown por haber cerrado el reproductor
|
|
if self.connected and self.last_connect and self.is_playing_fnc and not self.is_playing_fnc():
|
|
if time.time() - self.last_connect - 1 > self.timeout:
|
|
self.stop()
|
|
|
|
#shutdown por no realizar ninguna conexion
|
|
if (not self.file or not self.file.cursor) and self.start_time and self.wait_time and not self.connected:
|
|
if time.time() - self.start_time - 1 > self.wait_time:
|
|
self.stop()
|
|
|
|
#shutdown tras la ultima conexion
|
|
if (not self.file or not self.file.cursor) and self.timeout and self.connected and self.last_connect and not self.is_playing_fnc:
|
|
if time.time() - self.last_connect - 1 > self.timeout:
|
|
self.stop()
|
|
|
|
def stop(self):
|
|
self.running = False
|
|
self._server.stop()
|
|
logger.info("MEGA Server Stopped")
|
|
|
|
def get_play_list(self):
|
|
if len(self.files) > 1:
|
|
return "http://" + self.ip + ":" + str(self.port) + "/playlist.pls"
|
|
else:
|
|
return "http://" + self.ip + ":" + str(self.port) + "/" + urllib.quote(self.files[0].name.encode("utf8"))
|
|
|
|
def get_files(self):
|
|
files = []
|
|
enc_url = None
|
|
if self.files:
|
|
for file in self.files:
|
|
n = file.name.encode("utf8")
|
|
u = "http://" + self.ip + ":" + str(self.port) + "/" + urllib.quote(n)
|
|
s = file.size
|
|
file_id = file.file_id
|
|
enc_url = file.url
|
|
files.append({"name":n,"url":u,"size":s, "id": file_id})
|
|
if len(self.files) == 1:
|
|
try:
|
|
code = httptools.downloadpage(enc_url, only_headers=True).code
|
|
if code > 300:
|
|
return code
|
|
else:
|
|
return files
|
|
|
|
except:
|
|
logger.info(traceback.format_exc())
|
|
pass
|
|
|
|
return files
|
|
|
|
def add_url(self, url):
|
|
url = url.split("#")[1]
|
|
id_video = None
|
|
if "|" in url:
|
|
url, id_video = url.split("|")
|
|
if url.startswith("F!"):
|
|
if len(url.split("!")) ==3:
|
|
folder_id = url.split("!")[1]
|
|
folder_key = url.split("!")[2]
|
|
master_key = self.base64_to_a32(folder_key)
|
|
files = self.api_req({"a":"f","c":1,"r":1},"&n="+folder_id)
|
|
for file in files["f"]:
|
|
if file["t"] == 0:
|
|
if id_video and id_video != file["h"]:
|
|
continue
|
|
key = file['k'][file['k'].index(':') + 1:]
|
|
key = self.decrypt_key(self.base64_to_a32(key), master_key)
|
|
k = (key[0] ^ key[4], key[1] ^ key[5], key[2] ^ key[6], key[3] ^ key[7])
|
|
attributes = self.base64urldecode(file['a'])
|
|
attributes = self.dec_attr(attributes, k)
|
|
self.files.append(File(info=attributes, file_id=file["h"], key=key, folder_id=folder_id, file= file, client = self ))
|
|
else:
|
|
raise Exception("Enlace no valido")
|
|
|
|
elif url.startswith("!") or url.startswith("N!"):
|
|
if len(url.split("!")) ==3:
|
|
file_id = url.split("!")[1]
|
|
file_key = url.split("!")[2]
|
|
file = self.api_req({'a': 'g', 'g': 1, 'p': file_id})
|
|
key = self.base64_to_a32(file_key)
|
|
k = (key[0] ^ key[4], key[1] ^ key[5], key[2] ^ key[6], key[3] ^ key[7])
|
|
attributes = self.base64urldecode(file['at'])
|
|
attributes = self.dec_attr(attributes, k)
|
|
self.files.append(File(info=attributes, file_id=file_id, key=key, file= file, client = self))
|
|
else:
|
|
raise Exception("Enlace no valido")
|
|
else:
|
|
raise Exception("Enlace no valido")
|
|
|
|
def api_req(self, req, get=""):
|
|
seqno = random.randint(0, 0xFFFFFFFF)
|
|
url = 'https://g.api.mega.co.nz/cs?id=%d%s' % (seqno, get)
|
|
page = httptools.downloadpage(url, post=json.dumps([req])).data
|
|
return json.loads(page)[0]
|
|
|
|
def base64urldecode(self,data):
|
|
data += '=='[(2 - len(data) * 3) % 4:]
|
|
for search, replace in (('-', '+'), ('_', '/'), (',', '')):
|
|
data = data.replace(search, replace)
|
|
return base64.b64decode(data)
|
|
|
|
def base64urlencode(self,data):
|
|
data = base64.b64encode(data)
|
|
for search, replace in (('+', '-'), ('/', '_'), ('=', '')):
|
|
data = data.replace(search, replace)
|
|
return data
|
|
|
|
def a32_to_str(self,a):
|
|
return struct.pack('>%dI' % len(a), *a)
|
|
|
|
def str_to_a32(self,b):
|
|
if len(b) % 4: # Add padding, we need a string with a length multiple of 4
|
|
b += '\0' * (4 - len(b) % 4)
|
|
return struct.unpack('>%dI' % (len(b) / 4), b)
|
|
|
|
def base64_to_a32(self,s):
|
|
return self.str_to_a32(self.base64urldecode(s))
|
|
|
|
def a32_to_base64(self,a):
|
|
return self.base64urlencode(self.a32_to_str(a))
|
|
|
|
def aes_cbc_decrypt(self, data, key):
|
|
try:
|
|
from Cryptodome.Cipher import AES
|
|
except:
|
|
from Crypto.Cipher import AES
|
|
decryptor = AES.new(key, AES.MODE_CBC, '\0' * 16)
|
|
return decryptor.decrypt(data)
|
|
|
|
def aes_cbc_decrypt_a32(self,data, key):
|
|
return self.str_to_a32(self.aes_cbc_decrypt(self.a32_to_str(data), self.a32_to_str(key)))
|
|
|
|
def decrypt_key(self,a, key):
|
|
return sum((self.aes_cbc_decrypt_a32(a[i:i+4], key) for i in xrange(0, len(a), 4)), ())
|
|
|
|
def dec_attr(self, attr, key):
|
|
attr = self.aes_cbc_decrypt(attr, self.a32_to_str(key)).rstrip('\0')
|
|
if not attr.endswith("}"):
|
|
attr = attr.rsplit("}", 1)[0] + "}"
|
|
return json.loads(attr[4:]) if attr[:6] == 'MEGA{"' else False
|