├── .gitignore ├── .travis.yml ├── CONTRIBUTORS.md ├── MANIFEST.in ├── README.md ├── mega ├── __init__.py ├── crypto.py ├── exceptions.py ├── mega.py └── utils.py ├── requirements.txt ├── setup.py └── tests.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | dist/ 3 | build/ 4 | *.egg-info/ 5 | .* 6 | !.gitignore 7 | !.travis.yml -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | env: 3 | global: 4 | - secure: "SFpNG6AhSyHTaFFQvwjBjeyq+ozFFGRugWccbqv23NQkR3kXpmyqbx0v+Wz/\nBgsB3FCtHEnF0+KM6q6zPBDfqbJgLsu4ib2/Vienzjq4Nr5ZRunuQtYj67b+\nSDPP6w7Z2FmDq7Bgiy61QW4q2gBB1fRmWaeeLPu5R18TmVfT/fc=" 5 | - secure: "VJ2j+HdyxvZjLEXmYmV3TyGyUFEN2jZTyJJ+oNUedPe3G3tezGrcsmCXUEWU\nlNkN2Ciwh09iaN0GKxsETGWX2N9I1UDxOUgDjbjs5+q02rXDnWh3MSyJykoO\ngFW5XjaR605Tw2xK/uVZaNkr8pjeiaAzmjr0ojLjfTcELATTFR8=" 6 | python: 7 | - "2.7" 8 | - "2.6" 9 | install: 10 | - python setup.py install 11 | script: 12 | - python tests.py 13 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | ## Contributors 2 | 3 | - [Alejandro Garcia](https://github.com/Agrs) 4 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include requirements.txt 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # python-mega 2 | 3 | [![Build Status](https://travis-ci.org/juanriaza/python-mega.png?branch=master)](https://travis-ci.org/juanriaza/python-mega) 4 | 5 | ## Overview 6 | 7 | Wrapper around the [Mega API](https://mega.co.nz/#developers). Based on the work of [Julien Marchand](http://julien-marchand.fr/blog/using-mega-api-with-python-examples/). 8 | 9 | ## Installation 10 | 11 | Install using `pip`, including any optional packages you want... 12 | 13 | $ pip install python-mega 14 | 15 | ...or clone the project from github. 16 | 17 | $ git clone git@juanriaza/python-mega.git 18 | $ cd python-mega 19 | $ pip install -r requirements.txt 20 | 21 | ## How to use it? 22 | 23 | With your credentials: 24 | 25 | ```python 26 | from mega import Mega 27 | 28 | email = 'kim@dot.com' 29 | password = 'olakase' 30 | 31 | m = Mega.from_credentials(email, password) 32 | ``` 33 | 34 | …or you can use an ephemeral account: 35 | 36 | ```python 37 | m = Mega.from_ephemeral() 38 | ``` 39 | 40 | And fire some requests: 41 | 42 | ```python 43 | # list of files 44 | files = m.get_files() 45 | # download a file 46 | m.download_file(file_id, file_key) 47 | # upload a file 48 | m.uploadfile('/home/kim/mega/secret_plans') 49 | # download from an url 50 | m.download_from_url('https://mega.co.nz/#!wYo3AYZC!Zwi1f3ANtYwKNOc07fwuN1enOoRj4CreFouuGqi4D6Y') 51 | ``` 52 | 53 | ## Running the tests 54 | 55 | $ ./tests.py 56 | 57 | ## Changelog 58 | 59 | ### 0.1.0 60 | 61 | **31th Jan 2012** 62 | 63 | * First release. 64 | 65 | ## Acknowledgements 66 | 67 | - Many thanks to Julien Marchand for the initial work. 68 | - All of the [contributors](https://github.com/juanriaza/python-mega/blob/master/CONTRIBUTORS.md) to this project. 69 | -------------------------------------------------------------------------------- /mega/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '0.1.0' 2 | 3 | from .mega import Mega # NOQA 4 | -------------------------------------------------------------------------------- /mega/crypto.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import json 3 | 4 | from Crypto.Cipher import AES 5 | 6 | from .utils import a32_to_str, str_to_a32, a32_to_base64 7 | 8 | 9 | def aes_cbc_encrypt(data, key): 10 | encryptor = AES.new(key, AES.MODE_CBC, '\0' * 16) 11 | return encryptor.encrypt(data) 12 | 13 | 14 | def aes_cbc_decrypt(data, key): 15 | decryptor = AES.new(key, AES.MODE_CBC, '\0' * 16) 16 | return decryptor.decrypt(data) 17 | 18 | 19 | def aes_cbc_encrypt_a32(data, key): 20 | return str_to_a32(aes_cbc_encrypt(a32_to_str(data), a32_to_str(key))) 21 | 22 | 23 | def aes_cbc_decrypt_a32(data, key): 24 | return str_to_a32(aes_cbc_decrypt(a32_to_str(data), a32_to_str(key))) 25 | 26 | 27 | def stringhash(s, aeskey): 28 | s32 = str_to_a32(s) 29 | h32 = [0, 0, 0, 0] 30 | for i in xrange(len(s32)): 31 | h32[i % 4] ^= s32[i] 32 | for _ in xrange(0x4000): 33 | h32 = aes_cbc_encrypt_a32(h32, aeskey) 34 | return a32_to_base64((h32[0], h32[2])) 35 | 36 | 37 | def prepare_key(a): 38 | pkey = [0x93C467E3, 0x7DB0C7A4, 0xD1BE3F81, 0x0152CB56] 39 | for _ in xrange(0x10000): 40 | for j in xrange(0, len(a), 4): 41 | key = [0, 0, 0, 0] 42 | for i in xrange(4): 43 | if i + j < len(a): 44 | key[i] = a[i + j] 45 | pkey = aes_cbc_encrypt_a32(pkey, key) 46 | return pkey 47 | 48 | 49 | def encrypt_key(a, key): 50 | return sum( 51 | (aes_cbc_encrypt_a32(a[i:i+4], key) 52 | for i in xrange(0, len(a), 4)), ()) 53 | 54 | 55 | def decrypt_key(a, key): 56 | return sum( 57 | (aes_cbc_decrypt_a32(a[i:i+4], key) 58 | for i in xrange(0, len(a), 4)), ()) 59 | 60 | 61 | def enc_attr(attr, key): 62 | attr = 'MEGA' + json.dumps(attr) 63 | if len(attr) % 16: 64 | attr += '\0' * (16 - len(attr) % 16) 65 | return aes_cbc_encrypt(attr, a32_to_str(key)) 66 | 67 | 68 | def dec_attr(attr, key): 69 | attr = aes_cbc_decrypt(attr, a32_to_str(key)).rstrip('\0') 70 | return json.loads(attr[4:]) 71 | -------------------------------------------------------------------------------- /mega/exceptions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | class MegaException(Exception): 5 | pass 6 | 7 | class MegaIncorrectPasswordExcetion(MegaException): 8 | """ 9 | A incorrect password or email was given. 10 | """ 11 | 12 | class MegaRequestException(MegaException): 13 | """ 14 | There was an error in the request. 15 | """ 16 | pass 17 | -------------------------------------------------------------------------------- /mega/mega.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import os 3 | import json 4 | import random 5 | import binascii 6 | 7 | import requests 8 | from urlobject import URLObject 9 | from Crypto.Cipher import AES 10 | from Crypto.PublicKey import RSA 11 | from Crypto.Util import Counter 12 | 13 | from .crypto import prepare_key, stringhash, encrypt_key, decrypt_key,\ 14 | enc_attr, dec_attr, aes_cbc_encrypt_a32 15 | from .utils import a32_to_str, str_to_a32, a32_to_base64, base64_to_a32,\ 16 | mpi2int, base64urlencode, base64urldecode, get_chunks 17 | from .exceptions import MegaRequestException, MegaIncorrectPasswordExcetion 18 | 19 | 20 | class Mega(object): 21 | def __init__(self): 22 | self.seqno = random.randint(0, 0xFFFFFFFF) 23 | self.sid = None 24 | 25 | @classmethod 26 | def from_credentials(cls, email, password): 27 | inst = cls() 28 | inst.login_user(email, password) 29 | return inst 30 | 31 | @classmethod 32 | def from_ephemeral(cls): 33 | inst = cls() 34 | inst.login_ephemeral() 35 | return inst 36 | 37 | def api_req(self, data): 38 | params = {'id': self.seqno} 39 | self.seqno += 1 40 | if self.sid: 41 | params.update({'sid': self.sid}) 42 | data = json.dumps([data]) 43 | req = requests.post( 44 | 'https://g.api.mega.co.nz/cs', params=params, data=data) 45 | json_data = req.json() 46 | if isinstance(json_data, int): 47 | raise MegaRequestException(json_data) 48 | return json_data[0] 49 | 50 | def login_user(self, email, password): 51 | password_aes = prepare_key(str_to_a32(password)) 52 | uh = stringhash(email, password_aes) 53 | res = self.api_req({'a': 'us', 'user': email, 'uh': uh}) 54 | self._login_common(res, password_aes) 55 | 56 | def login_ephemeral(self): 57 | random_master_key = [random.randint(0, 0xFFFFFFFF)] * 4 58 | random_password_key = [random.randint(0, 0xFFFFFFFF)] * 4 59 | random_session_self_challenge = [random.randint(0, 0xFFFFFFFF)] * 4 60 | user_handle = self.api_req({ 61 | 'a': 'up', 62 | 'k': a32_to_base64(encrypt_key(random_master_key, 63 | random_password_key)), 64 | 'ts': base64urlencode(a32_to_str(random_session_self_challenge) + 65 | a32_to_str(encrypt_key( 66 | random_session_self_challenge, 67 | random_master_key))) 68 | }) 69 | res = self.api_req({'a': 'us', 'user': user_handle}) 70 | self._login_common(res, random_password_key) 71 | 72 | def _login_common(self, res, password): 73 | if res in (-2, -9): 74 | raise MegaIncorrectPasswordExcetion("Incorrect e-mail and/or password.") 75 | 76 | enc_master_key = base64_to_a32(res['k']) 77 | self.master_key = decrypt_key(enc_master_key, password) 78 | if 'tsid' in res: 79 | tsid = base64urldecode(res['tsid']) 80 | key_encrypted = a32_to_str( 81 | encrypt_key(str_to_a32(tsid[:16]), self.master_key)) 82 | if key_encrypted == tsid[-16:]: 83 | self.sid = res['tsid'] 84 | elif 'csid' in res: 85 | enc_rsa_priv_key = base64_to_a32(res['privk']) 86 | rsa_priv_key = decrypt_key(enc_rsa_priv_key, self.master_key) 87 | 88 | privk = a32_to_str(rsa_priv_key) 89 | self.rsa_priv_key = [0, 0, 0, 0] 90 | 91 | for i in xrange(4): 92 | l = ((ord(privk[0]) * 256 + ord(privk[1]) + 7) / 8) + 2 93 | self.rsa_priv_key[i] = mpi2int(privk[:l]) 94 | privk = privk[l:] 95 | 96 | enc_sid = mpi2int(base64urldecode(res['csid'])) 97 | decrypter = RSA.construct( 98 | (self.rsa_priv_key[0] * self.rsa_priv_key[1], 99 | 0L, 100 | self.rsa_priv_key[2], 101 | self.rsa_priv_key[0], 102 | self.rsa_priv_key[1])) 103 | sid = '%x' % decrypter.key._decrypt(enc_sid) 104 | sid = binascii.unhexlify('0' + sid if len(sid) % 2 else sid) 105 | self.sid = base64urlencode(sid[:43]) 106 | 107 | def get_files(self): 108 | files_data = self.api_req({'a': 'f', 'c': 1}) 109 | for file in files_data['f']: 110 | if file['t'] in (0, 1): 111 | key = file['k'].split(':')[1] 112 | key = decrypt_key(base64_to_a32(key), self.master_key) 113 | # file 114 | if file['t'] == 0: 115 | k = (key[0] ^ key[4], 116 | key[1] ^ key[5], 117 | key[2] ^ key[6], 118 | key[3] ^ key[7]) 119 | # directory 120 | else: 121 | k = key 122 | attributes = base64urldecode(file['a']) 123 | attributes = dec_attr(attributes, k) 124 | file['a'] = attributes 125 | file['k'] = key 126 | # Root ("Cloud Drive") 127 | elif file['t'] == 2: 128 | self.root_id = file['h'] 129 | # Inbox 130 | elif file['t'] == 3: 131 | self.inbox_id = file['h'] 132 | # Trash Bin 133 | elif file['t'] == 4: 134 | self.trashbin_id = file['h'] 135 | return files_data 136 | 137 | def download_from_url(self, url): 138 | url_object = URLObject(url) 139 | file_id, file_key = url_object.fragment[1:].split('!') 140 | self.download_file(file_id, file_key, public=True) 141 | 142 | def download_file(self, file_id, file_key, public=False): 143 | if public: 144 | file_key = base64_to_a32(file_key) 145 | file_data = self.api_req({'a': 'g', 'g': 1, 'p': file_id}) 146 | else: 147 | file_data = self.api_req({'a': 'g', 'g': 1, 'n': file_id}) 148 | 149 | k = (file_key[0] ^ file_key[4], 150 | file_key[1] ^ file_key[5], 151 | file_key[2] ^ file_key[6], 152 | file_key[3] ^ file_key[7]) 153 | iv = file_key[4:6] + (0, 0) 154 | meta_mac = file_key[6:8] 155 | 156 | file_url = file_data['g'] 157 | file_size = file_data['s'] 158 | attributes = base64urldecode(file_data['at']) 159 | attributes = dec_attr(attributes, k) 160 | file_name = attributes['n'] 161 | 162 | infile = requests.get(file_url, stream=True).raw 163 | outfile = open(file_name, 'wb') 164 | 165 | counter = Counter.new( 166 | 128, initial_value=((iv[0] << 32) + iv[1]) << 64) 167 | decryptor = AES.new(a32_to_str(k), AES.MODE_CTR, counter=counter) 168 | 169 | file_mac = (0, 0, 0, 0) 170 | for chunk_start, chunk_size in sorted(get_chunks(file_size).items()): 171 | chunk = infile.read(chunk_size) 172 | chunk = decryptor.decrypt(chunk) 173 | outfile.write(chunk) 174 | 175 | chunk_mac = [iv[0], iv[1], iv[0], iv[1]] 176 | for i in xrange(0, len(chunk), 16): 177 | block = chunk[i:i+16] 178 | if len(block) % 16: 179 | block += '\0' * (16 - (len(block) % 16)) 180 | block = str_to_a32(block) 181 | chunk_mac = [ 182 | chunk_mac[0] ^ block[0], 183 | chunk_mac[1] ^ block[1], 184 | chunk_mac[2] ^ block[2], 185 | chunk_mac[3] ^ block[3]] 186 | chunk_mac = aes_cbc_encrypt_a32(chunk_mac, k) 187 | 188 | file_mac = [ 189 | file_mac[0] ^ chunk_mac[0], 190 | file_mac[1] ^ chunk_mac[1], 191 | file_mac[2] ^ chunk_mac[2], 192 | file_mac[3] ^ chunk_mac[3]] 193 | file_mac = aes_cbc_encrypt_a32(file_mac, k) 194 | 195 | outfile.close() 196 | 197 | # Integrity check 198 | if (file_mac[0] ^ file_mac[1], file_mac[2] ^ file_mac[3]) != meta_mac: 199 | raise ValueError('MAC mismatch') 200 | 201 | def get_public_url(self, file_id, file_key): 202 | public_handle = self.api_req({'a': 'l', 'n': file_id}) 203 | decrypted_key = a32_to_base64(file_key) 204 | return 'http://mega.co.nz/#!%s!%s' % (public_handle, decrypted_key) 205 | 206 | def uploadfile(self, filename, dst=None): 207 | if not dst: 208 | root_id = getattr(self, 'root_id') 209 | if not root_id: 210 | self.get_files() 211 | dst = self.root_id 212 | infile = open(filename, 'rb') 213 | size = os.path.getsize(filename) 214 | ul_url = self.api_req({'a': 'u', 's': size})['p'] 215 | 216 | ul_key = [random.randint(0, 0xFFFFFFFF) for _ in xrange(6)] 217 | counter = Counter.new( 218 | 128, initial_value=((ul_key[4] << 32) + ul_key[5]) << 64) 219 | encryptor = AES.new( 220 | a32_to_str(ul_key[:4]), 221 | AES.MODE_CTR, 222 | counter=counter) 223 | 224 | file_mac = [0, 0, 0, 0] 225 | for chunk_start, chunk_size in sorted(get_chunks(size).items()): 226 | chunk = infile.read(chunk_size) 227 | 228 | chunk_mac = [ul_key[4], ul_key[5], ul_key[4], ul_key[5]] 229 | for i in xrange(0, len(chunk), 16): 230 | block = chunk[i:i+16] 231 | if len(block) % 16: 232 | block += '\0' * (16 - len(block) % 16) 233 | block = str_to_a32(block) 234 | chunk_mac = [chunk_mac[0] ^ block[0], 235 | chunk_mac[1] ^ block[1], 236 | chunk_mac[2] ^ block[2], 237 | chunk_mac[3] ^ block[3]] 238 | chunk_mac = aes_cbc_encrypt_a32(chunk_mac, ul_key[:4]) 239 | 240 | file_mac = [file_mac[0] ^ chunk_mac[0], 241 | file_mac[1] ^ chunk_mac[1], 242 | file_mac[2] ^ chunk_mac[2], 243 | file_mac[3] ^ chunk_mac[3]] 244 | file_mac = aes_cbc_encrypt_a32(file_mac, ul_key[:4]) 245 | 246 | chunk = encryptor.encrypt(chunk) 247 | url = '%s/%s' % (ul_url, str(chunk_start)) 248 | outfile = requests.post(url, data=chunk, stream=True).raw 249 | completion_handle = outfile.read() 250 | infile.close() 251 | 252 | meta_mac = (file_mac[0] ^ file_mac[1], file_mac[2] ^ file_mac[3]) 253 | 254 | attributes = {'n': os.path.basename(filename)} 255 | enc_attributes = base64urlencode(enc_attr(attributes, ul_key[:4])) 256 | key = [ul_key[0] ^ ul_key[4], 257 | ul_key[1] ^ ul_key[5], 258 | ul_key[2] ^ meta_mac[0], 259 | ul_key[3] ^ meta_mac[1], 260 | ul_key[4], ul_key[5], 261 | meta_mac[0], meta_mac[1]] 262 | encrypted_key = a32_to_base64(encrypt_key(key, self.master_key)) 263 | data = self.api_req({'a': 'p', 't': dst, 'n': [ 264 | {'h': completion_handle, 265 | 't': 0, 266 | 'a': enc_attributes, 267 | 'k': encrypted_key}]}) 268 | return data 269 | -------------------------------------------------------------------------------- /mega/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import base64 3 | import struct 4 | import binascii 5 | 6 | from Crypto.Cipher import AES 7 | 8 | 9 | def a32_to_str(a): 10 | return struct.pack('>%dI' % len(a), *a) 11 | 12 | 13 | def aes_cbc_encrypt(data, key): 14 | encryptor = AES.new(key, AES.MODE_CBC, '\0' * 16) 15 | return encryptor.encrypt(data) 16 | 17 | 18 | def aes_cbc_encrypt_a32(data, key): 19 | return str_to_a32(aes_cbc_encrypt(a32_to_str(data), a32_to_str(key))) 20 | 21 | 22 | def str_to_a32(b): 23 | if len(b) % 4: # Add padding, we need a string with a length multiple of 4 24 | b += '\0' * (4 - len(b) % 4) 25 | return struct.unpack('>%dI' % (len(b) / 4), b) 26 | 27 | 28 | def mpi2int(s): 29 | return int(binascii.hexlify(s[2:]), 16) 30 | 31 | 32 | def aes_cbc_decrypt(data, key): 33 | decryptor = AES.new(key, AES.MODE_CBC, '\0' * 16) 34 | return decryptor.decrypt(data) 35 | 36 | 37 | def aes_cbc_decrypt_a32(data, key): 38 | return str_to_a32(aes_cbc_decrypt(a32_to_str(data), a32_to_str(key))) 39 | 40 | 41 | def base64urldecode(data): 42 | data += '=='[(2 - len(data) * 3) % 4:] 43 | for search, replace in (('-', '+'), ('_', '/'), (',', '')): 44 | data = data.replace(search, replace) 45 | return base64.b64decode(data) 46 | 47 | 48 | def base64_to_a32(s): 49 | return str_to_a32(base64urldecode(s)) 50 | 51 | 52 | def base64urlencode(data): 53 | data = base64.b64encode(data) 54 | for search, replace in (('+', '-'), ('/', '_'), ('=', '')): 55 | data = data.replace(search, replace) 56 | return data 57 | 58 | 59 | def a32_to_base64(a): 60 | return base64urlencode(a32_to_str(a)) 61 | 62 | 63 | def get_chunks(size): 64 | chunks = {} 65 | p = pp = 0 66 | i = 1 67 | 68 | while i <= 8 and p < size - i * 0x20000: 69 | chunks[p] = i * 0x20000 70 | pp = p 71 | p += chunks[p] 72 | i += 1 73 | 74 | while p < size: 75 | chunks[p] = 0x100000 76 | pp = p 77 | p += chunks[p] 78 | 79 | chunks[pp] = size - pp 80 | if not chunks[pp]: 81 | del chunks[pp] 82 | 83 | return chunks 84 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests>=1.1.0 2 | URLObject>=2.1.1 3 | pycrypto>=2.6 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from setuptools import setup 5 | import re 6 | import os 7 | import sys 8 | 9 | 10 | def get_version(package): 11 | """ 12 | Return package version as listed in `__version__` in `init.py`. 13 | """ 14 | init_py = open(os.path.join(package, '__init__.py')).read() 15 | return re.search( 16 | "^__version__ = ['\"]([^'\"]+)['\"]", 17 | init_py, 18 | re.MULTILINE).group(1) 19 | 20 | 21 | def get_packages(package): 22 | """ 23 | Return root package and all sub-packages. 24 | """ 25 | return [dirpath 26 | for dirpath, dirnames, filenames in os.walk(package) 27 | if os.path.exists(os.path.join(dirpath, '__init__.py'))] 28 | 29 | 30 | def get_package_data(package): 31 | """ 32 | Return all files under the root package, that are not in a 33 | package themselves. 34 | """ 35 | walk = [(dirpath.replace(package + os.sep, '', 1), filenames) 36 | for dirpath, dirnames, filenames in os.walk(package) 37 | if not os.path.exists(os.path.join(dirpath, '__init__.py'))] 38 | 39 | filepaths = [] 40 | for base, filenames in walk: 41 | filepaths.extend([os.path.join(base, filename) 42 | for filename in filenames]) 43 | return {package: filepaths} 44 | 45 | 46 | version = get_version('mega') 47 | 48 | if sys.argv[-1] == 'publish': 49 | os.system("python setup.py sdist upload") 50 | args = {'version': version} 51 | print "You probably want to also tag the version now:" 52 | print " git tag -a %(version)s -m 'version %(version)s'" % args 53 | print " git push --tags" 54 | sys.exit() 55 | 56 | 57 | setup( 58 | name='python-mega', 59 | version=version, 60 | url='http://github.com/juanriaza/python-mega', 61 | license='BSD', 62 | description='Wrapper around the Mega API', 63 | author='Juan Riaza', 64 | author_email='juanriaza@gmail.com', 65 | packages=get_packages('mega'), 66 | package_data=get_package_data('mega'), 67 | install_requires=open('requirements.txt').read().split('\n'), 68 | classifiers=[ 69 | 'Development Status :: 4 - Beta', 70 | 'Intended Audience :: Developers', 71 | 'License :: OSI Approved :: BSD License', 72 | 'Operating System :: OS Independent', 73 | 'Programming Language :: Python', 74 | 'Topic :: Internet :: WWW/HTTP' 75 | ] 76 | ) 77 | -------------------------------------------------------------------------------- /tests.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import os 3 | import sys 4 | import unittest 5 | import tempfile 6 | 7 | from mega import Mega 8 | from mega.exceptions import MegaIncorrectPasswordExcetion 9 | 10 | class TestMega(unittest.TestCase): 11 | def setUp(self): 12 | unittest.TestCase.setUp(self) 13 | self._email = os.environ.get('MEGAEMAIL') or sys.argv[0] 14 | self._password = os.environ.get('MEGAPASSWORD') or sys.argv[1] 15 | 16 | def _check_file_exists(self, file_name, files): 17 | uploaded = False 18 | for f in files['f']: 19 | if isinstance(f['a'], dict): 20 | if f['a'].get('n') == file_name: 21 | uploaded = True 22 | return uploaded 23 | 24 | def _test_upload_file(self, api): 25 | api.get_files() 26 | 27 | # Create temp file 28 | uFile, uFilePath = tempfile.mkstemp() 29 | os.write(uFile, "Does it work?") 30 | os.close(uFile) 31 | api.uploadfile(uFilePath) # inception 32 | files = api.get_files() 33 | 34 | uFileName = os.path.basename(uFilePath) 35 | uploaded = self._check_file_exists(uFileName, files) 36 | self.assertEqual(uploaded, True) 37 | 38 | def test_login_fail(self): 39 | with self.assertRaises(MegaIncorrectPasswordExcetion): 40 | Mega.from_credentials("valid@email.com", "test"); 41 | 42 | def test_login_valid(self): 43 | Mega.from_credentials(self._email, self._password) 44 | 45 | def test_upload_file_logged(self): 46 | self._test_upload_file(Mega.from_credentials(self._email, self._password)) 47 | 48 | def test_upload_file_ephemeral(self): 49 | self._test_upload_file(Mega.from_ephemeral()) 50 | 51 | if __name__ == '__main__': 52 | unittest.main() 53 | --------------------------------------------------------------------------------