|
1 | | -from mega.cli import main |
| 1 | +from __future__ import annotations |
| 2 | + |
| 3 | +import contextlib |
| 4 | +import json |
| 5 | +import logging |
| 6 | +from pathlib import Path |
| 7 | +from typing import TYPE_CHECKING, Annotated |
| 8 | + |
| 9 | +import yarl |
| 10 | +from cyclopts import App, Parameter |
| 11 | + |
| 12 | +from mega import __version__, env |
| 13 | +from mega.api import LOG_HTTP_TRAFFIC |
| 14 | +from mega.client import MegaNzClient |
| 15 | +from mega.transfer_it import TransferItClient |
| 16 | +from mega.utils import Site, setup_logger |
| 17 | + |
| 18 | +if TYPE_CHECKING: |
| 19 | + from collections.abc import AsyncGenerator |
| 20 | + |
| 21 | + |
| 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 | +) |
| 34 | + |
| 35 | + |
| 36 | +@app.meta.default |
| 37 | +def verbose( |
| 38 | + *tokens: Annotated[str, Parameter(show=False, allow_leading_hyphen=True)], |
| 39 | + verbose: Annotated[ |
| 40 | + int, |
| 41 | + Parameter( |
| 42 | + name=["-v", "--verbose"], |
| 43 | + count=True, |
| 44 | + help="Increase verbosity (-v shows debug logs, -vv shows HTTP traffic)", |
| 45 | + ), |
| 46 | + ] = 0, |
| 47 | +) -> None: |
| 48 | + if verbose > 1: |
| 49 | + LOG_HTTP_TRAFFIC.set(True) |
| 50 | + |
| 51 | + level = logging.DEBUG if verbose else logging.INFO |
| 52 | + setup_logger(level) |
| 53 | + app(tokens) |
| 54 | + |
| 55 | + |
| 56 | +@contextlib.asynccontextmanager |
| 57 | +async def connect() -> AsyncGenerator[MegaNzClient]: |
| 58 | + async with MegaNzClient() as mega: |
| 59 | + await mega.login(env.EMAIL, env.PASSWORD) |
| 60 | + with mega.progress_bar: |
| 61 | + yield mega |
| 62 | + |
| 63 | + |
| 64 | +async def transfer_it(url: str, output_dir: Path) -> None: |
| 65 | + async with TransferItClient() as client: |
| 66 | + with client.progress_bar: |
| 67 | + transfer_id = client.parse_url(url) |
| 68 | + logger.info(f"Downloading '{url}'") |
| 69 | + results = await client.download_transfer(transfer_id, output_dir) |
| 70 | + logger.info( |
| 71 | + f"Download of '{url}' finished. Successful = {len(results.success)}, failed = {len(results.fails)}" |
| 72 | + ) |
| 73 | + |
| 74 | + |
| 75 | +@app.command() |
| 76 | +async def download(url: str, output_dir: Path = CWD) -> None: |
| 77 | + """Download a public file or folder by its URL (transfer.it / mega.nz)""" |
| 78 | + |
| 79 | + site = Site(yarl.URL(url).origin()) |
| 80 | + if site is Site.TRANSFER_IT: |
| 81 | + return await transfer_it(url, output_dir) |
| 82 | + |
| 83 | + async with connect() as mega: |
| 84 | + parsed_url = mega.parse_url(url) |
| 85 | + if parsed_url.is_folder: |
| 86 | + await download_folder(mega, url, output_dir) |
| 87 | + else: |
| 88 | + await download_file(mega, url, output_dir) |
| 89 | + |
| 90 | + |
| 91 | +@app.command() |
| 92 | +async def dump(output_dir: Path = CWD) -> None: |
| 93 | + """Dump a copy of your filesystem to disk""" |
| 94 | + |
| 95 | + async with connect() as mega: |
| 96 | + fs = await mega.get_filesystem() |
| 97 | + out = output_dir / "filesystem.json" |
| 98 | + out.parent.mkdir(exist_ok=True) |
| 99 | + logger.info(f"Creating filesystem dump at '{out!s}'") |
| 100 | + out.write_text(json.dumps(fs.dump(), indent=2, ensure_ascii=False)) |
| 101 | + |
| 102 | + |
| 103 | +@app.command() |
| 104 | +async def stats() -> None: |
| 105 | + """Show account stats""" |
| 106 | + |
| 107 | + async with connect() as mega: |
| 108 | + stats = await mega.get_account_stats() |
| 109 | + logger.info(f"Account stats for {env.EMAIL or 'TEMP ACCOUNT'}:") |
| 110 | + logger.info(stats.storage.dump()) |
| 111 | + logger.info(stats.balance.dump()) |
| 112 | + fs = await mega.get_filesystem() |
| 113 | + metrics = {root.attributes.name: stats.metrics[root.id] for root in (fs.root, fs.inbox, fs.trash_bin)} |
| 114 | + logger.info(metrics) |
| 115 | + |
| 116 | + |
| 117 | +@app.command() |
| 118 | +async def upload(file_path: Path) -> None: |
| 119 | + """Upload a file to your account""" |
| 120 | + |
| 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 | + |
| 125 | + folder = await mega.create_folder("uploaded by mega.py") |
| 126 | + logger.info(f'Uploading "{file_path!s}"') |
| 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 | + |
| 133 | + link = await mega.export(file) |
| 134 | + logger.info(f'Public link for "{file_path!s}": {link}') |
| 135 | + |
| 136 | + |
| 137 | +async def download_file(mega: MegaNzClient, url: str, output: Path) -> None: |
| 138 | + public_handle, public_key = mega.parse_file_url(url) |
| 139 | + logger.info(f"Downloading {url}") |
| 140 | + path = await mega.download_public_file(public_handle, public_key, output) |
| 141 | + logger.info(f'Download of {url} finished. File save at "{path!s}"') |
| 142 | + |
| 143 | + |
| 144 | +async def download_folder(mega: MegaNzClient, url: str, output: Path) -> None: |
| 145 | + public_handle, public_key, root_node = mega.parse_folder_url(url) |
| 146 | + logger.info(f"Downloading {url}") |
| 147 | + results = await mega.download_public_folder(public_handle, public_key, output, root_node) |
| 148 | + logger.info(f"Download of '{url}' finished. Successful = {len(results.success)}, failed = {len(results.fails)}") |
| 149 | + |
2 | 150 |
|
3 | 151 | if __name__ == "__main__": |
4 | | - main() |
| 152 | + app.meta() |
0 commit comments