diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-10-05 10:16:28 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-10-05 10:16:28 +0200 |
| commit | 41d015137d6d9c097e462cd0662e855c3253d001 (patch) | |
| tree | e1e5a4e6f1415509c031e079c2b753591975ed05 /examples/download.py | |
| parent | fe64864e8cfd2cca3a70ae375a3ea16d537ae6be (diff) | |
| download | meshbay-41d015137d6d9c097e462cd0662e855c3253d001.tar.gz | |
docs: add Python examples for the hub, a node and its control API
examples/ holds small scripts that talk to MeshBay the way the application
does, with the values to change as variables at the top of each one:
- meshbay_session.py: hub sign-in, WebRTC to a node serving the group, and
the handshake (identity bundle, group key, proofs both ways)
- list_groups.py: the user's groups, up or down
- download.py: one file, decrypted chunk by chunk under a transfer lease
- upload.py: one file, sealed in 48 KiB chunks into a group folder
- create_group.py: creates a group on the hub and hosts it on the local node
through its loopback control API
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'examples/download.py')
| -rwxr-xr-x | examples/download.py | 48 |
1 files changed, 48 insertions, 0 deletions
diff --git a/examples/download.py b/examples/download.py new file mode 100755 index 0000000..b5359d2 --- /dev/null +++ b/examples/download.py @@ -0,0 +1,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()) |