async-mega-py 2.0.4.dev0__tar.gz → 2.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {async_mega_py-2.0.4.dev0 → async_mega_py-2.1.0}/PKG-INFO +42 -42
- {async_mega_py-2.0.4.dev0 → async_mega_py-2.1.0}/README.md +37 -34
- {async_mega_py-2.0.4.dev0 → async_mega_py-2.1.0}/pyproject.toml +9 -18
- async_mega_py-2.0.4.dev0/src/mega/cli/__init__.py → async_mega_py-2.1.0/src/mega/__main__.py +36 -23
- {async_mega_py-2.0.4.dev0 → async_mega_py-2.1.0}/src/mega/api.py +13 -10
- {async_mega_py-2.0.4.dev0 → async_mega_py-2.1.0}/src/mega/auth.py +46 -49
- {async_mega_py-2.0.4.dev0 → async_mega_py-2.1.0}/src/mega/chunker.py +39 -15
- async_mega_py-2.1.0/src/mega/client.py +290 -0
- async_mega_py-2.1.0/src/mega/core.py +450 -0
- {async_mega_py-2.0.4.dev0 → async_mega_py-2.1.0}/src/mega/crypto.py +16 -34
- {async_mega_py-2.0.4.dev0 → async_mega_py-2.1.0}/src/mega/data_structures.py +149 -49
- {async_mega_py-2.0.4.dev0 → async_mega_py-2.1.0}/src/mega/download.py +24 -12
- async_mega_py-2.1.0/src/mega/env.py +26 -0
- {async_mega_py-2.0.4.dev0 → async_mega_py-2.1.0}/src/mega/errors.py +13 -0
- {async_mega_py-2.0.4.dev0 → async_mega_py-2.1.0}/src/mega/filesystem.py +90 -7
- async_mega_py-2.0.4.dev0/src/mega/progress.py → async_mega_py-2.1.0/src/mega/progress/__init__.py +17 -75
- async_mega_py-2.1.0/src/mega/progress/rich_progress.py +58 -0
- {async_mega_py-2.0.4.dev0 → async_mega_py-2.1.0}/src/mega/transfer_it.py +19 -17
- {async_mega_py-2.0.4.dev0 → async_mega_py-2.1.0}/src/mega/upload.py +23 -24
- {async_mega_py-2.0.4.dev0 → async_mega_py-2.1.0}/src/mega/utils.py +63 -4
- async_mega_py-2.1.0/src/mega/vault.py +67 -0
- async_mega_py-2.0.4.dev0/src/mega/__main__.py +0 -4
- async_mega_py-2.0.4.dev0/src/mega/cli/app.py +0 -59
- async_mega_py-2.0.4.dev0/src/mega/client.py +0 -320
- async_mega_py-2.0.4.dev0/src/mega/core.py +0 -329
- async_mega_py-2.0.4.dev0/src/mega/env.py +0 -27
- async_mega_py-2.0.4.dev0/src/mega/vault.py +0 -110
- {async_mega_py-2.0.4.dev0 → async_mega_py-2.1.0}/LICENSE +0 -0
- {async_mega_py-2.0.4.dev0 → async_mega_py-2.1.0}/src/mega/__init__.py +0 -0
- {async_mega_py-2.0.4.dev0 → async_mega_py-2.1.0}/src/mega/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
|
-
Name: async-mega
|
|
3
|
-
Version: 2.0
|
|
2
|
+
Name: async-mega.py
|
|
3
|
+
Version: 2.1.0
|
|
4
4
|
Summary: Python library for the Mega.nz and Transfer.it API
|
|
5
5
|
Keywords: api,downloader,mega,mega.nz,transfer.it
|
|
6
6
|
Author: NTFSvolume
|
|
@@ -23,21 +23,18 @@ Classifier: Programming Language :: Python :: 3.15
|
|
|
23
23
|
Classifier: Programming Language :: Python
|
|
24
24
|
Requires-Dist: aiohttp>=3.11.18
|
|
25
25
|
Requires-Dist: aiolimiter>=1.2.1
|
|
26
|
-
Requires-Dist: pycryptodome
|
|
27
|
-
Requires-Dist:
|
|
28
|
-
Requires-Dist:
|
|
29
|
-
Requires-Dist: python-dotenv>=1.2.1 ; extra == 'default'
|
|
30
|
-
Requires-Dist: rich>=14.0.0 ; extra == 'default'
|
|
26
|
+
Requires-Dist: pycryptodome>=3.20.0
|
|
27
|
+
Requires-Dist: cyclopts>=4.10.1 ; extra == 'cli'
|
|
28
|
+
Requires-Dist: python-dotenv>=1.2.1 ; extra == 'cli'
|
|
31
29
|
Requires-Python: >=3.11
|
|
32
30
|
Project-URL: Homepage, https://github.com/NTFSvolume/mega.py
|
|
33
31
|
Provides-Extra: cli
|
|
34
|
-
Provides-Extra: default
|
|
35
32
|
Description-Content-Type: text/markdown
|
|
36
33
|
|
|
37
34
|
# Async Mega.py
|
|
38
35
|
|
|
39
36
|
[](https://pypi.org/project/async-mega-py/)
|
|
40
|
-
[](https://pypi.org/project/async-mega-py/)
|
|
37
|
+
[](https://pypi.org/project/async-mega-py/)
|
|
41
38
|
[](https://github.com/NTFSvolume/mega.py/actions/workflows/ruff.yml)
|
|
42
39
|
[](https://github.com/NTFSvolume/mega.py/blob/master/LICENSE)
|
|
43
40
|
[](https://github.com/NTFSvolume/mega.py/actions/workflows/ci.yml)
|
|
@@ -99,14 +96,10 @@ Login should always be the first thing you do. Almost all operations require a v
|
|
|
99
96
|
```python
|
|
100
97
|
from mega.client import MegaNzClient
|
|
101
98
|
|
|
102
|
-
|
|
103
|
-
await mega.login() # login using a temporary anonymous account
|
|
104
|
-
|
|
105
|
-
# Also works without using it as a context manager,
|
|
99
|
+
# Also works without using it as a context manager, but you have the responsability to close the session at the end
|
|
106
100
|
mega = MegaNzClient()
|
|
107
|
-
await mega.login()
|
|
108
|
-
#
|
|
109
|
-
# mega.close()
|
|
101
|
+
await mega.login() # login using a temporary anonymous account
|
|
102
|
+
# await mega.close()
|
|
110
103
|
|
|
111
104
|
```
|
|
112
105
|
|
|
@@ -133,19 +126,27 @@ contact = "test@mega.nz"
|
|
|
133
126
|
await mega.add_contact(contact)
|
|
134
127
|
await mega.remove_contact(contact)
|
|
135
128
|
|
|
129
|
+
async def view_paths():
|
|
130
|
+
fs = await mega.get_filesystem()
|
|
131
|
+
print (fs.paths.values())
|
|
132
|
+
|
|
136
133
|
# Create a folder
|
|
137
134
|
await mega.create_folder('new_folder')
|
|
138
135
|
await mega.create_folder('new_folder/sub_folder/subsub_folder')
|
|
136
|
+
await view_paths()
|
|
139
137
|
|
|
140
138
|
# Rename a file or a folder
|
|
141
139
|
folder = await mega.find('new_folder/sub_folder/subsub_folder')
|
|
142
140
|
await mega.rename(folder, new_name='my_new_name')
|
|
141
|
+
await view_paths()
|
|
143
142
|
|
|
144
|
-
#
|
|
145
|
-
|
|
146
|
-
await
|
|
147
|
-
await mega.destroy(folder.id) # This removes it completely from your account
|
|
143
|
+
# Send this node to the trash bin (still counts towards your quota)
|
|
144
|
+
await mega.delete(folder.id)
|
|
145
|
+
await view_paths()
|
|
148
146
|
|
|
147
|
+
# This removes it completely from your account
|
|
148
|
+
await mega.destroy(folder.id)
|
|
149
|
+
await view_paths()
|
|
149
150
|
```
|
|
150
151
|
|
|
151
152
|
### Upload / Downloads
|
|
@@ -154,29 +155,29 @@ await mega.destroy(folder.id) # This removes it completely from your account
|
|
|
154
155
|
# Upload a file, and get its public link
|
|
155
156
|
my_real_file = '/home/user/myfile.doc' # Change this to a real file path!
|
|
156
157
|
uploaded_file = await mega.upload(my_real_file) # Upload returns the Node that represents the file you just uploaded
|
|
157
|
-
await mega.export(uploaded_file)
|
|
158
|
+
await mega.export(uploaded_file) # This only works with real accounts. Temp accounts can't create public links
|
|
158
159
|
|
|
159
160
|
# Download a file from your account
|
|
160
161
|
output_dir = "my downloads"
|
|
161
162
|
await mega.download(uploaded_file, output_dir)
|
|
162
163
|
|
|
163
164
|
# Download a public file
|
|
164
|
-
|
|
165
|
-
public_handle, public_key = mega.parse_file_url(
|
|
165
|
+
file_url = "https://mega.nz/file/vJ9EBJBC#vA1XpbngXcnN7lqXEWGQFPDc5ZlG6yltCHwGN-0O2LQ"
|
|
166
|
+
public_handle, public_key = mega.parse_file_url(file_url)
|
|
166
167
|
await mega.download_public_file(public_handle, public_key, output_dir)
|
|
167
168
|
|
|
168
169
|
# Download a public folder
|
|
169
|
-
|
|
170
|
-
public_handle, public_key, selected_node = mega.parse_folder_url(
|
|
171
|
-
|
|
172
|
-
|
|
170
|
+
folder_url = "https://mega.nz/folder/CJ8y1SDJ#KX8YfTd526P4o_hDH02jLQ"
|
|
171
|
+
public_handle, public_key, selected_node = mega.parse_folder_url(folder_url)
|
|
172
|
+
results = await mega.download_public_folder(public_handle, public_key, output_dir, selected_node)
|
|
173
|
+
for node_id, result in results.items():
|
|
174
|
+
print ((node_id, result))
|
|
173
175
|
|
|
174
176
|
# Import a file from URL
|
|
175
|
-
|
|
176
|
-
public_handle, public_key = mega.parse_file_url(url)
|
|
177
|
+
public_handle, public_key = mega.parse_file_url(file_url)
|
|
177
178
|
await mega.import_public_file(public_handle, public_key, dest_node_id=folder.id)
|
|
178
179
|
|
|
179
|
-
# How do you know if an URL is a file or folder?
|
|
180
|
+
# How do you know if an URL is a file or folder?
|
|
180
181
|
result = mega.parse_url(url)
|
|
181
182
|
print (result.is_folder)
|
|
182
183
|
```
|
|
@@ -185,10 +186,8 @@ print (result.is_folder)
|
|
|
185
186
|
> You can show a progress bar on the terminal for each download/upload by calling them within the `progress_bar` context manager (needs optional dependency `rich` to be installed):
|
|
186
187
|
|
|
187
188
|
```python
|
|
188
|
-
url = "https://mega.co.nz/#F!utYjgSTQ!OM4U3V5v_W4N5edSo0wolg1D5H0fwSrLD3oLnLuS9pc"
|
|
189
|
-
public_handle, public_key = mega.parse_folder_url(url)
|
|
190
189
|
with mega.progress_bar:
|
|
191
|
-
|
|
190
|
+
results = await mega.download_public_folder(public_handle, public_key, output_dir / "with_bar")
|
|
192
191
|
```
|
|
193
192
|
|
|
194
193
|
## The filesystem object
|
|
@@ -277,12 +276,11 @@ The output will be:
|
|
|
277
276
|
|
|
278
277
|
```python
|
|
279
278
|
# Get the path to a node
|
|
280
|
-
|
|
281
|
-
# Should be: /tests/scripts/notes.txt
|
|
279
|
+
fs.absolute_path("0fPFklV3") # /tests/scripts/notes.txt
|
|
282
280
|
|
|
283
281
|
# Find a node by its *exact* path
|
|
284
282
|
result = fs.find("/tests/scripts/notes.txt")
|
|
285
|
-
|
|
283
|
+
print (result.id) # "0fPFklV3"
|
|
286
284
|
|
|
287
285
|
```
|
|
288
286
|
|
|
@@ -293,7 +291,7 @@ list(fs.deleted)
|
|
|
293
291
|
# List all the children of a folder (resursive)
|
|
294
292
|
folder = fs.find("/tests")
|
|
295
293
|
for node in fs.iterdir(folder.id, recursive=True):
|
|
296
|
-
print(fs.absolute_path(node))
|
|
294
|
+
print(fs.absolute_path(node.id))
|
|
297
295
|
|
|
298
296
|
```
|
|
299
297
|
Output will be:
|
|
@@ -306,7 +304,9 @@ Output will be:
|
|
|
306
304
|
"/tests/scripts",
|
|
307
305
|
"/tests/scripts/notes.txt",
|
|
308
306
|
"/tests/scripts/script.js",
|
|
309
|
-
"/tests/scripts/styles.css"
|
|
307
|
+
"/tests/scripts/styles.css",
|
|
308
|
+
"/tests/setup.sh",
|
|
309
|
+
"/tests/utils.py",
|
|
310
310
|
]
|
|
311
311
|
```
|
|
312
312
|
|
|
@@ -324,7 +324,7 @@ dict(fs.search("/tests/logo.png"))
|
|
|
324
324
|
## Transfer.it
|
|
325
325
|
|
|
326
326
|
> [!NOTE]
|
|
327
|
-
> The `transfer.it` client does not support uploads (
|
|
327
|
+
> The `transfer.it` client does not support uploads yet (PRs welcome)
|
|
328
328
|
|
|
329
329
|
```python
|
|
330
330
|
from mega.transfer_it import TransferItClient
|
|
@@ -335,8 +335,8 @@ async with TransferItClient() as client:
|
|
|
335
335
|
# but it does not have root, inbox or trash_bin nodes
|
|
336
336
|
fs = await client.get_filesystem(transfer_id)
|
|
337
337
|
output_dir = "My downloads"
|
|
338
|
-
|
|
339
|
-
|
|
338
|
+
results = await client.download_transfer(transfer_id, output_dir)
|
|
339
|
+
print (results)
|
|
340
340
|
```
|
|
341
341
|
|
|
342
342
|
## CLI
|
|
@@ -371,6 +371,6 @@ Usage: mega-py [OPTIONS] COMMAND [ARGS]...
|
|
|
371
371
|
```
|
|
372
372
|
|
|
373
373
|
> [!TIP]
|
|
374
|
-
> The CLI app does *not* accept login credentials, but you can still use your account by setting up the `
|
|
374
|
+
> The CLI app does *not* accept login credentials, but you can still use your account by setting up the `MEGA_EMAIL` and `MEGA_PWD` enviroment variables
|
|
375
375
|
>
|
|
376
376
|
> It will also read them from an `.env` file (if found)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Async Mega.py
|
|
2
2
|
|
|
3
3
|
[](https://pypi.org/project/async-mega-py/)
|
|
4
|
-
[](https://pypi.org/project/async-mega-py/)
|
|
4
|
+
[](https://pypi.org/project/async-mega-py/)
|
|
5
5
|
[](https://github.com/NTFSvolume/mega.py/actions/workflows/ruff.yml)
|
|
6
6
|
[](https://github.com/NTFSvolume/mega.py/blob/master/LICENSE)
|
|
7
7
|
[](https://github.com/NTFSvolume/mega.py/actions/workflows/ci.yml)
|
|
@@ -63,14 +63,10 @@ Login should always be the first thing you do. Almost all operations require a v
|
|
|
63
63
|
```python
|
|
64
64
|
from mega.client import MegaNzClient
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
await mega.login() # login using a temporary anonymous account
|
|
68
|
-
|
|
69
|
-
# Also works without using it as a context manager,
|
|
66
|
+
# Also works without using it as a context manager, but you have the responsability to close the session at the end
|
|
70
67
|
mega = MegaNzClient()
|
|
71
|
-
await mega.login()
|
|
72
|
-
#
|
|
73
|
-
# mega.close()
|
|
68
|
+
await mega.login() # login using a temporary anonymous account
|
|
69
|
+
# await mega.close()
|
|
74
70
|
|
|
75
71
|
```
|
|
76
72
|
|
|
@@ -97,19 +93,27 @@ contact = "test@mega.nz"
|
|
|
97
93
|
await mega.add_contact(contact)
|
|
98
94
|
await mega.remove_contact(contact)
|
|
99
95
|
|
|
96
|
+
async def view_paths():
|
|
97
|
+
fs = await mega.get_filesystem()
|
|
98
|
+
print (fs.paths.values())
|
|
99
|
+
|
|
100
100
|
# Create a folder
|
|
101
101
|
await mega.create_folder('new_folder')
|
|
102
102
|
await mega.create_folder('new_folder/sub_folder/subsub_folder')
|
|
103
|
+
await view_paths()
|
|
103
104
|
|
|
104
105
|
# Rename a file or a folder
|
|
105
106
|
folder = await mega.find('new_folder/sub_folder/subsub_folder')
|
|
106
107
|
await mega.rename(folder, new_name='my_new_name')
|
|
108
|
+
await view_paths()
|
|
107
109
|
|
|
108
|
-
#
|
|
109
|
-
|
|
110
|
-
await
|
|
111
|
-
await mega.destroy(folder.id) # This removes it completely from your account
|
|
110
|
+
# Send this node to the trash bin (still counts towards your quota)
|
|
111
|
+
await mega.delete(folder.id)
|
|
112
|
+
await view_paths()
|
|
112
113
|
|
|
114
|
+
# This removes it completely from your account
|
|
115
|
+
await mega.destroy(folder.id)
|
|
116
|
+
await view_paths()
|
|
113
117
|
```
|
|
114
118
|
|
|
115
119
|
### Upload / Downloads
|
|
@@ -118,29 +122,29 @@ await mega.destroy(folder.id) # This removes it completely from your account
|
|
|
118
122
|
# Upload a file, and get its public link
|
|
119
123
|
my_real_file = '/home/user/myfile.doc' # Change this to a real file path!
|
|
120
124
|
uploaded_file = await mega.upload(my_real_file) # Upload returns the Node that represents the file you just uploaded
|
|
121
|
-
await mega.export(uploaded_file)
|
|
125
|
+
await mega.export(uploaded_file) # This only works with real accounts. Temp accounts can't create public links
|
|
122
126
|
|
|
123
127
|
# Download a file from your account
|
|
124
128
|
output_dir = "my downloads"
|
|
125
129
|
await mega.download(uploaded_file, output_dir)
|
|
126
130
|
|
|
127
131
|
# Download a public file
|
|
128
|
-
|
|
129
|
-
public_handle, public_key = mega.parse_file_url(
|
|
132
|
+
file_url = "https://mega.nz/file/vJ9EBJBC#vA1XpbngXcnN7lqXEWGQFPDc5ZlG6yltCHwGN-0O2LQ"
|
|
133
|
+
public_handle, public_key = mega.parse_file_url(file_url)
|
|
130
134
|
await mega.download_public_file(public_handle, public_key, output_dir)
|
|
131
135
|
|
|
132
136
|
# Download a public folder
|
|
133
|
-
|
|
134
|
-
public_handle, public_key, selected_node = mega.parse_folder_url(
|
|
135
|
-
|
|
136
|
-
|
|
137
|
+
folder_url = "https://mega.nz/folder/CJ8y1SDJ#KX8YfTd526P4o_hDH02jLQ"
|
|
138
|
+
public_handle, public_key, selected_node = mega.parse_folder_url(folder_url)
|
|
139
|
+
results = await mega.download_public_folder(public_handle, public_key, output_dir, selected_node)
|
|
140
|
+
for node_id, result in results.items():
|
|
141
|
+
print ((node_id, result))
|
|
137
142
|
|
|
138
143
|
# Import a file from URL
|
|
139
|
-
|
|
140
|
-
public_handle, public_key = mega.parse_file_url(url)
|
|
144
|
+
public_handle, public_key = mega.parse_file_url(file_url)
|
|
141
145
|
await mega.import_public_file(public_handle, public_key, dest_node_id=folder.id)
|
|
142
146
|
|
|
143
|
-
# How do you know if an URL is a file or folder?
|
|
147
|
+
# How do you know if an URL is a file or folder?
|
|
144
148
|
result = mega.parse_url(url)
|
|
145
149
|
print (result.is_folder)
|
|
146
150
|
```
|
|
@@ -149,10 +153,8 @@ print (result.is_folder)
|
|
|
149
153
|
> You can show a progress bar on the terminal for each download/upload by calling them within the `progress_bar` context manager (needs optional dependency `rich` to be installed):
|
|
150
154
|
|
|
151
155
|
```python
|
|
152
|
-
url = "https://mega.co.nz/#F!utYjgSTQ!OM4U3V5v_W4N5edSo0wolg1D5H0fwSrLD3oLnLuS9pc"
|
|
153
|
-
public_handle, public_key = mega.parse_folder_url(url)
|
|
154
156
|
with mega.progress_bar:
|
|
155
|
-
|
|
157
|
+
results = await mega.download_public_folder(public_handle, public_key, output_dir / "with_bar")
|
|
156
158
|
```
|
|
157
159
|
|
|
158
160
|
## The filesystem object
|
|
@@ -241,12 +243,11 @@ The output will be:
|
|
|
241
243
|
|
|
242
244
|
```python
|
|
243
245
|
# Get the path to a node
|
|
244
|
-
|
|
245
|
-
# Should be: /tests/scripts/notes.txt
|
|
246
|
+
fs.absolute_path("0fPFklV3") # /tests/scripts/notes.txt
|
|
246
247
|
|
|
247
248
|
# Find a node by its *exact* path
|
|
248
249
|
result = fs.find("/tests/scripts/notes.txt")
|
|
249
|
-
|
|
250
|
+
print (result.id) # "0fPFklV3"
|
|
250
251
|
|
|
251
252
|
```
|
|
252
253
|
|
|
@@ -257,7 +258,7 @@ list(fs.deleted)
|
|
|
257
258
|
# List all the children of a folder (resursive)
|
|
258
259
|
folder = fs.find("/tests")
|
|
259
260
|
for node in fs.iterdir(folder.id, recursive=True):
|
|
260
|
-
print(fs.absolute_path(node))
|
|
261
|
+
print(fs.absolute_path(node.id))
|
|
261
262
|
|
|
262
263
|
```
|
|
263
264
|
Output will be:
|
|
@@ -270,7 +271,9 @@ Output will be:
|
|
|
270
271
|
"/tests/scripts",
|
|
271
272
|
"/tests/scripts/notes.txt",
|
|
272
273
|
"/tests/scripts/script.js",
|
|
273
|
-
"/tests/scripts/styles.css"
|
|
274
|
+
"/tests/scripts/styles.css",
|
|
275
|
+
"/tests/setup.sh",
|
|
276
|
+
"/tests/utils.py",
|
|
274
277
|
]
|
|
275
278
|
```
|
|
276
279
|
|
|
@@ -288,7 +291,7 @@ dict(fs.search("/tests/logo.png"))
|
|
|
288
291
|
## Transfer.it
|
|
289
292
|
|
|
290
293
|
> [!NOTE]
|
|
291
|
-
> The `transfer.it` client does not support uploads (
|
|
294
|
+
> The `transfer.it` client does not support uploads yet (PRs welcome)
|
|
292
295
|
|
|
293
296
|
```python
|
|
294
297
|
from mega.transfer_it import TransferItClient
|
|
@@ -299,8 +302,8 @@ async with TransferItClient() as client:
|
|
|
299
302
|
# but it does not have root, inbox or trash_bin nodes
|
|
300
303
|
fs = await client.get_filesystem(transfer_id)
|
|
301
304
|
output_dir = "My downloads"
|
|
302
|
-
|
|
303
|
-
|
|
305
|
+
results = await client.download_transfer(transfer_id, output_dir)
|
|
306
|
+
print (results)
|
|
304
307
|
```
|
|
305
308
|
|
|
306
309
|
## CLI
|
|
@@ -335,6 +338,6 @@ Usage: mega-py [OPTIONS] COMMAND [ARGS]...
|
|
|
335
338
|
```
|
|
336
339
|
|
|
337
340
|
> [!TIP]
|
|
338
|
-
> The CLI app does *not* accept login credentials, but you can still use your account by setting up the `
|
|
341
|
+
> The CLI app does *not* accept login credentials, but you can still use your account by setting up the `MEGA_EMAIL` and `MEGA_PWD` enviroment variables
|
|
339
342
|
>
|
|
340
343
|
> It will also read them from an `.env` file (if found)
|
|
@@ -20,7 +20,7 @@ classifiers = [
|
|
|
20
20
|
dependencies = [
|
|
21
21
|
"aiohttp>=3.11.18",
|
|
22
22
|
"aiolimiter>=1.2.1",
|
|
23
|
-
"pycryptodome
|
|
23
|
+
"pycryptodome>=3.20.0"
|
|
24
24
|
]
|
|
25
25
|
description = "Python library for the Mega.nz and Transfer.it API"
|
|
26
26
|
keywords = [
|
|
@@ -34,21 +34,17 @@ license = "Apache-2.0"
|
|
|
34
34
|
license-files = ["LICENSE"]
|
|
35
35
|
readme = "README.md"
|
|
36
36
|
requires-python = ">=3.11"
|
|
37
|
-
version = "2.0
|
|
37
|
+
version = "2.1.0"
|
|
38
38
|
|
|
39
39
|
[project.optional-dependencies]
|
|
40
40
|
cli = [
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
]
|
|
44
|
-
default = [
|
|
45
|
-
"python-dotenv>=1.2.1",
|
|
46
|
-
"rich>=14.0.0"
|
|
41
|
+
"cyclopts>=4.10.1",
|
|
42
|
+
"python-dotenv>=1.2.1"
|
|
47
43
|
]
|
|
48
44
|
|
|
49
45
|
[project.scripts]
|
|
50
|
-
async-mega-py = "mega.
|
|
51
|
-
mega-py = "mega.
|
|
46
|
+
async-mega-py = "mega.__main__:app.meta"
|
|
47
|
+
mega-py = "mega.__main__:app.meta"
|
|
52
48
|
|
|
53
49
|
[project.urls]
|
|
54
50
|
Homepage = "https://github.com/NTFSvolume/mega.py"
|
|
@@ -123,17 +119,12 @@ module-name = "mega"
|
|
|
123
119
|
|
|
124
120
|
[build-system]
|
|
125
121
|
build-backend = "uv_build"
|
|
126
|
-
requires = ["uv_build
|
|
122
|
+
requires = ["uv_build<=0.10.11"]
|
|
127
123
|
|
|
128
124
|
[dependency-groups]
|
|
129
125
|
dev = [
|
|
130
|
-
"
|
|
131
|
-
"pre-commit>=4.5.0",
|
|
126
|
+
"prek>=0.3.6",
|
|
132
127
|
"pytest-asyncio>=0.25.0",
|
|
133
|
-
"pytest-cov>=6.0.0",
|
|
134
128
|
"pytest>=8.3.5",
|
|
135
|
-
"
|
|
136
|
-
"rich>=14.0.0",
|
|
137
|
-
"ruff>=0.13.1",
|
|
138
|
-
"typing-extensions>=4.15.0"
|
|
129
|
+
"ruff==0.15.6"
|
|
139
130
|
]
|
async_mega_py-2.0.4.dev0/src/mega/cli/__init__.py → async_mega_py-2.1.0/src/mega/__main__.py
RENAMED
|
@@ -6,12 +6,11 @@ import logging
|
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
from typing import TYPE_CHECKING, Annotated
|
|
8
8
|
|
|
9
|
-
import typer
|
|
10
9
|
import yarl
|
|
10
|
+
from cyclopts import App, Parameter
|
|
11
11
|
|
|
12
|
-
from mega import __version__, env
|
|
12
|
+
from mega import __version__, env
|
|
13
13
|
from mega.api import LOG_HTTP_TRAFFIC
|
|
14
|
-
from mega.cli.app import CLIApp
|
|
15
14
|
from mega.client import MegaNzClient
|
|
16
15
|
from mega.transfer_it import TransferItClient
|
|
17
16
|
from mega.utils import Site, setup_logger
|
|
@@ -21,14 +20,26 @@ if TYPE_CHECKING:
|
|
|
21
20
|
|
|
22
21
|
|
|
23
22
|
logger = logging.getLogger("mega")
|
|
23
|
+
CWD = Path.cwd()
|
|
24
|
+
|
|
25
|
+
app = App(
|
|
26
|
+
help=(
|
|
27
|
+
"CLI app for the [bold black]Mega.nz[/bold black] and [bold black]Transfer.it[/bold black].\n"
|
|
28
|
+
f"Set [bold green]{env.EMAIL.name}[/bold green] and [bold green]{env.PASSWORD.name}[/bold green]\n"
|
|
29
|
+
"enviroment variables to use them as credentials for Mega"
|
|
30
|
+
),
|
|
31
|
+
help_format="rich",
|
|
32
|
+
version=__version__,
|
|
33
|
+
)
|
|
24
34
|
|
|
25
35
|
|
|
36
|
+
@app.meta.default
|
|
26
37
|
def verbose(
|
|
38
|
+
*tokens: Annotated[str, Parameter(show=False, allow_leading_hyphen=True)],
|
|
27
39
|
verbose: Annotated[
|
|
28
40
|
int,
|
|
29
|
-
|
|
30
|
-
"-v",
|
|
31
|
-
"--verbose",
|
|
41
|
+
Parameter(
|
|
42
|
+
name=["-v", "--verbose"],
|
|
32
43
|
count=True,
|
|
33
44
|
help="Increase verbosity (-v shows debug logs, -vv shows HTTP traffic)",
|
|
34
45
|
),
|
|
@@ -39,18 +50,7 @@ def verbose(
|
|
|
39
50
|
|
|
40
51
|
level = logging.DEBUG if verbose else logging.INFO
|
|
41
52
|
setup_logger(level)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
app = CLIApp(
|
|
45
|
-
add_completion=False,
|
|
46
|
-
callback=verbose,
|
|
47
|
-
help=(
|
|
48
|
-
"CLI app for the [bold black]Mega.nz[/bold black] and [bold black]Transfer.it[/bold black].\n"
|
|
49
|
-
f"Set [bold green]{env.NAMES.EMAIL}[/bold green] and [bold green]{env.NAMES.PASSWORD}[/bold green] enviroment variables to use them as credentials for Mega"
|
|
50
|
-
),
|
|
51
|
-
epilog=f"v{__version__}",
|
|
52
|
-
)
|
|
53
|
-
CWD = Path.cwd()
|
|
53
|
+
app(tokens)
|
|
54
54
|
|
|
55
55
|
|
|
56
56
|
@contextlib.asynccontextmanager
|
|
@@ -63,7 +63,7 @@ async def connect() -> AsyncGenerator[MegaNzClient]:
|
|
|
63
63
|
|
|
64
64
|
async def transfer_it(url: str, output_dir: Path) -> None:
|
|
65
65
|
async with TransferItClient() as client:
|
|
66
|
-
with
|
|
66
|
+
with client.progress_bar:
|
|
67
67
|
transfer_id = client.parse_url(url)
|
|
68
68
|
logger.info(f"Downloading '{url}'")
|
|
69
69
|
results = await client.download_transfer(transfer_id, output_dir)
|
|
@@ -75,6 +75,7 @@ async def transfer_it(url: str, output_dir: Path) -> None:
|
|
|
75
75
|
@app.command()
|
|
76
76
|
async def download(url: str, output_dir: Path = CWD) -> None:
|
|
77
77
|
"""Download a public file or folder by its URL (transfer.it / mega.nz)"""
|
|
78
|
+
|
|
78
79
|
site = Site(yarl.URL(url).origin())
|
|
79
80
|
if site is Site.TRANSFER_IT:
|
|
80
81
|
return await transfer_it(url, output_dir)
|
|
@@ -82,13 +83,15 @@ async def download(url: str, output_dir: Path = CWD) -> None:
|
|
|
82
83
|
async with connect() as mega:
|
|
83
84
|
parsed_url = mega.parse_url(url)
|
|
84
85
|
if parsed_url.is_folder:
|
|
85
|
-
|
|
86
|
-
|
|
86
|
+
await download_folder(mega, url, output_dir)
|
|
87
|
+
else:
|
|
88
|
+
await download_file(mega, url, output_dir)
|
|
87
89
|
|
|
88
90
|
|
|
89
91
|
@app.command()
|
|
90
92
|
async def dump(output_dir: Path = CWD) -> None:
|
|
91
93
|
"""Dump a copy of your filesystem to disk"""
|
|
94
|
+
|
|
92
95
|
async with connect() as mega:
|
|
93
96
|
fs = await mega.get_filesystem()
|
|
94
97
|
out = output_dir / "filesystem.json"
|
|
@@ -100,6 +103,7 @@ async def dump(output_dir: Path = CWD) -> None:
|
|
|
100
103
|
@app.command()
|
|
101
104
|
async def stats() -> None:
|
|
102
105
|
"""Show account stats"""
|
|
106
|
+
|
|
103
107
|
async with connect() as mega:
|
|
104
108
|
stats = await mega.get_account_stats()
|
|
105
109
|
logger.info(f"Account stats for {env.EMAIL or 'TEMP ACCOUNT'}:")
|
|
@@ -113,10 +117,19 @@ async def stats() -> None:
|
|
|
113
117
|
@app.command()
|
|
114
118
|
async def upload(file_path: Path) -> None:
|
|
115
119
|
"""Upload a file to your account"""
|
|
120
|
+
|
|
116
121
|
async with connect() as mega:
|
|
122
|
+
if not env.EMAIL:
|
|
123
|
+
logger.warning("Files uploaded by a temp account can not be exported")
|
|
124
|
+
|
|
117
125
|
folder = await mega.create_folder("uploaded by mega.py")
|
|
118
126
|
logger.info(f'Uploading "{file_path!s}"')
|
|
119
127
|
file = await mega.upload(file_path, folder.id)
|
|
128
|
+
path = (await mega.get_filesystem()).absolute_path(file.id)
|
|
129
|
+
logger.info(f'File uploaded to your cloud. Path = "{path}"')
|
|
130
|
+
if not env.EMAIL:
|
|
131
|
+
return
|
|
132
|
+
|
|
120
133
|
link = await mega.export(file)
|
|
121
134
|
logger.info(f'Public link for "{file_path!s}": {link}')
|
|
122
135
|
|
|
@@ -135,5 +148,5 @@ async def download_folder(mega: MegaNzClient, url: str, output: Path) -> None:
|
|
|
135
148
|
logger.info(f"Download of '{url}' finished. Successful = {len(results.success)}, failed = {len(results.fails)}")
|
|
136
149
|
|
|
137
150
|
|
|
138
|
-
|
|
139
|
-
app()
|
|
151
|
+
if __name__ == "__main__":
|
|
152
|
+
app.meta()
|
|
@@ -14,7 +14,7 @@ import aiohttp
|
|
|
14
14
|
import yarl
|
|
15
15
|
from aiolimiter import AsyncLimiter
|
|
16
16
|
|
|
17
|
-
from mega.crypto import
|
|
17
|
+
from mega.crypto import generate_hashcash
|
|
18
18
|
from mega.errors import RequestError, RetryRequestError
|
|
19
19
|
from mega.utils import random_id, random_u32int
|
|
20
20
|
|
|
@@ -38,10 +38,10 @@ def retry(
|
|
|
38
38
|
exceptions: Sequence[type[Exception]] | type[Exception],
|
|
39
39
|
attempts: int = 10,
|
|
40
40
|
delay: float = 0.5,
|
|
41
|
-
min_delay: float = 2.0,
|
|
42
41
|
max_delay: float = 30.0,
|
|
43
42
|
backoff: int = 2,
|
|
44
43
|
) -> Callable[[Callable[_P, Coroutine[None, None, _R]]], Callable[_P, Coroutine[None, None, _R]]]:
|
|
44
|
+
min_delay: float = 2.0
|
|
45
45
|
if not isinstance(exceptions, Sequence):
|
|
46
46
|
exceptions = [exceptions]
|
|
47
47
|
|
|
@@ -86,7 +86,6 @@ class MegaAPI:
|
|
|
86
86
|
self.__session = session
|
|
87
87
|
self._auto_close_session = session is None
|
|
88
88
|
self._rate_limiter = AsyncLimiter(100, 60)
|
|
89
|
-
logger.disabled = not LOG_HTTP_TRAFFIC.get()
|
|
90
89
|
|
|
91
90
|
@property
|
|
92
91
|
def entrypoint(self) -> yarl.URL:
|
|
@@ -148,7 +147,7 @@ class MegaAPI:
|
|
|
148
147
|
msg = f"Login failed. Mega requested a proof of work with xhashcash: {xhashcash_challenge}"
|
|
149
148
|
raise RequestError(msg)
|
|
150
149
|
|
|
151
|
-
headers["X-Hashcash"] = await asyncio.to_thread(
|
|
150
|
+
headers["X-Hashcash"] = await asyncio.to_thread(generate_hashcash, xhashcash_challenge)
|
|
152
151
|
continue
|
|
153
152
|
|
|
154
153
|
return await self._parse_response(resp)
|
|
@@ -176,8 +175,9 @@ class MegaAPI:
|
|
|
176
175
|
**kwargs: Any,
|
|
177
176
|
) -> AsyncGenerator[aiohttp.ClientResponse]:
|
|
178
177
|
kwargs["headers"] = _DEFAULT_HEADERS | (headers or {})
|
|
179
|
-
|
|
180
|
-
|
|
178
|
+
if LOG_HTTP_TRAFFIC.get():
|
|
179
|
+
params = ", ".join(f"{name} = {value!r}" for name, value in kwargs.items())
|
|
180
|
+
logger.debug(f"Making {method} request to {url!s} with {params}")
|
|
181
181
|
async with self._rate_limiter, self._session.request(method, url, **kwargs) as resp:
|
|
182
182
|
yield resp
|
|
183
183
|
|
|
@@ -185,7 +185,8 @@ class MegaAPI:
|
|
|
185
185
|
async def _parse_response(response: aiohttp.ClientResponse) -> Any:
|
|
186
186
|
json_resp = await response.json()
|
|
187
187
|
resp = json_resp
|
|
188
|
-
|
|
188
|
+
if LOG_HTTP_TRAFFIC.get():
|
|
189
|
+
logger.debug(f"Got response [{response.status}] json={json_resp!r}")
|
|
189
190
|
|
|
190
191
|
if isinstance(json_resp, list) and len(json_resp) == 1:
|
|
191
192
|
resp = json_resp[0]
|
|
@@ -197,13 +198,13 @@ class MegaAPI:
|
|
|
197
198
|
if resp == -3:
|
|
198
199
|
msg = "Request failed, retrying"
|
|
199
200
|
logger.warning(msg)
|
|
200
|
-
raise RetryRequestError
|
|
201
|
+
raise RetryRequestError
|
|
201
202
|
raise RequestError(resp)
|
|
202
203
|
|
|
203
204
|
return resp
|
|
204
205
|
|
|
205
206
|
|
|
206
|
-
class
|
|
207
|
+
class APIContextManager:
|
|
207
208
|
__slots__ = ("_api",)
|
|
208
209
|
|
|
209
210
|
def __init__(self, session: aiohttp.ClientSession | None = None) -> None:
|
|
@@ -215,5 +216,7 @@ class AbstractApiClient:
|
|
|
215
216
|
async def __aexit__(self, *_) -> None:
|
|
216
217
|
await self.close()
|
|
217
218
|
|
|
218
|
-
async def
|
|
219
|
+
async def aclose(self) -> None:
|
|
219
220
|
await self._api.close()
|
|
221
|
+
|
|
222
|
+
close = aclose
|