aboutsummaryrefslogtreecommitdiffstats
path: root/examples/download.py
blob: b5359d29fe0accb7817c1dfa0d68baed3cd29054 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#!/usr/bin/env python3
"""Download one file from a MeshBay group, as a member, without a browser."""

import asyncio
import math
import sys

from meshbay_common.webcrypto import chunk_key_aes, decrypt_chunk_aes
from meshbay_session import connect

HUB = "https://meshbay.org"
USERNAME = "alice"
PASSPHRASE = "your passphrase"
GROUP = "Family Photos"              # the group's name, as shown in the sidebar
NODE_ID = ""                         # empty: the first node online for this group
FILE_PATH = "shared/2024/beach.jpg"  # root name, then folders, then the file name
OUTPUT = "beach.jpg"

CHUNK_SIZE = 1024 * 1024             # the node serves files in 1 MiB chunks


async def main() -> None:
    session = await connect(HUB, USERNAME, PASSPHRASE, GROUP, NODE_ID)

    entry = next((e for e in await session.index()
                  if f"{e['path']}/{e['name']}" == FILE_PATH), None)
    if entry is None:
        sys.exit(f"No file {FILE_PATH!r} in this group")

    # Each chunk is encrypted under its own key, derived from the group key.
    chunks = max(1, math.ceil(entry["size"] / CHUNK_SIZE))
    tr = await session.open_transfer("download", entry["size"], chunks)
    file_hash = bytes.fromhex(entry["id"])
    with open(OUTPUT, "wb") as out:
        for i in range(chunks):
            session.ch.send({"type": "file_req", "v": "0.1", "file_id": entry["id"],
                             "chunk_index": i, "tr": tr})
            chunk = await session.ch.recv("file_chunk")
            out.write(decrypt_chunk_aes(chunk_key_aes(session.gek, file_hash, i),
                                        chunk["nonce"], chunk["ct"]))
            print(f"\r{i + 1}/{chunks} chunks", end="", flush=True)
    session.close_transfer(tr)
    print(f"\nSaved {OUTPUT} ({entry['size']} bytes)")
    await session.close()


if __name__ == "__main__":
    asyncio.run(main())