diff --git a/README.md b/README.md index 11a364a..81c264c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Mega.py -Python library for the Mega.co.nz API, currently supporting login, uploading & downloading of files. +Python library for the Mega.co.nz API, currently supporting login, uploading, downloading & deleting of files. This is a work in progress, further functionality coming shortly. diff --git a/crypto.py b/crypto.py index d1d30c1..977c4a8 100644 --- a/crypto.py +++ b/crypto.py @@ -3,6 +3,7 @@ import json import base64 import struct import binascii +import random def aes_cbc_encrypt(data, key): aes_cipher = AES.new(key, AES.MODE_CBC, '\0' * 16) @@ -128,3 +129,11 @@ def get_chunks(size): del chunks[pp] return chunks + +# more general functions +def make_id(length): + text = '' + possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" + for i in range(length): + text += random.choice(possible) + return text \ No newline at end of file diff --git a/mega.py b/mega.py index 741b4a6..24e1d90 100644 --- a/mega.py +++ b/mega.py @@ -18,6 +18,7 @@ class Mega(object): self.timeout = 160 #max time (secs) to wait for response from api requests self.sid = None self.sequence_num = random.randint(0, 0xFFFFFFFF) + self.request_id = make_id(10) @classmethod def login(class_, email, password): @@ -25,7 +26,6 @@ class Mega(object): instance.login_user(email, password) return instance - def login_user(self, email, password): password_aes = prepare_key(str_to_a32(password)) uh = stringhash(email, password_aes) @@ -35,7 +35,6 @@ class Mega(object): raise errors.RequestError(resp) self._login_process(resp, password_aes) - def _login_process(self, resp, password): encrypted_master_key = base64_to_a32(resp['k']) self.master_key = decrypt_key(encrypted_master_key, password) @@ -76,19 +75,22 @@ class Mega(object): req = requests.post( '{0}://g.api.{1}/cs'.format(self.schema,self.domain), params=params, data=json.dumps([data]), timeout=self.timeout) json_resp = req.json() + #if numeric error code response if isinstance(json_resp, int): raise errors.RequestError(json_resp) return json_resp[0] def get_files(self): + ''' + Get all files in account + ''' files = self.api_request({'a': 'f', 'c': 1}) files_dict = {} for file in files['f']: files_dict[file['h']] = self.process_file(file) return files_dict - def download_url(self, url): path = self.parse_url(url).split('!') file_id = path[0] @@ -108,6 +110,57 @@ class Mega(object): user_data = self.api_request({'a': 'ug'}) return user_data + def delete_url(self, url): + #delete a file via it's url + path = self.parse_url(url).split('!') + file_id = path[0] + return self.move(file_id, 4) + + def delete(self, file_id): + #straight delete by id + return self.move(file_id, 4) + + def move(self, file_id, target): + #TODO node_id improvements + ''' + Move a file to another parent node + params: + a : command + n : node we're moving + t : id of target parent node, moving to + i : request id + + targets + 2 : root + 3 : inbox + 4 : trash + ''' + #get node data + node_data = self.api_request({'a': 'f', 'f': 1, 'p': file_id}) + target_node_id = str(self.get_node_by_type(target)[0]) + node_id = None + + #determine node id + for i in node_data['f']: + if i['h'] is not u'': + node_id = i['h'] + + return self.api_request({'a': 'm', 'n': node_id, 't': target_node_id, 'i': self.request_id}) + + def get_node_by_type(self, type): + ''' + Get a node by it's numeric type id, e.g: + 0: file + 1: dir + 2: special: root cloud drive + 3: special: inbox + 4: special trash bin + ''' + nodes = self.get_files() + for node in nodes.items(): + if(node[1]['t'] == type): + return node + def download_file(self, file_id, file_key, is_public=False): if is_public: @@ -127,7 +180,7 @@ class Mega(object): attribs = decrypt_attr(attribs, k) file_name = attribs['n'] - print "Downloading %s (size: %d), url = %s" % (attribs['n'], file_size, file_url) + print "downloading {0} (size: {1}), url = {2}".format(attribs['n'], file_size, file_url) input_file = requests.get(file_url, stream=True).raw output_file = open(file_name, 'wb') @@ -168,6 +221,9 @@ class Mega(object): raise ValueError('Mismatched mac') def get_public_url(self, file_id, file_key): + ''' + Get a files public link including decrypted key + ''' if file_id and file_key: public_handle = self.api_request({'a': 'l', 'n': file_id}) decrypted_key = a32_to_base64(file_key) @@ -237,12 +293,6 @@ class Mega(object): def process_file(self, file): """ Process a file... - Possible node types are: - 0: File - 1: Dir - 2: Special - Root/Cloud drive - 3: Special - Inbox - 4: Special - Trash """ if file['t'] == 0 or file['t'] == 1: key = file['k'][file['k'].index(':') + 1:] diff --git a/tests/test.py b/tests/test.py index 8a54b5e..fe22c28 100644 --- a/tests/test.py +++ b/tests/test.py @@ -21,8 +21,12 @@ def test(): #upload file print(m.upload('test.py')) + #trash a file, by id or url + #print(m.delete('C5pxAbr')) + #print(m.delete_url('https://mega.co.nz/#!C5pxAbrL!SPxZH0Ovn2DLK_n5hLlkGQ2oTD8HcU6TYiz_TPg78kY')) + #download file from url - m.download_url('https://mega.co.nz/#!utYjgSTQ!OM4U3V5v_W4N5edSo0wolg1D5H0fwSrLD3oLnLuS9pc') + m.download_url('https://mega.co.nz/#!6hBW0R4a!By7-Vjj5xal8K5w_IXH3PlGNyZ1VvIrjZkOmHGq1X00') if __name__ == '__main__': test() \ No newline at end of file