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())
|