|
import json |
|
import logging |
|
import os |
|
from argparse import ArgumentParser |
|
from logging import INFO |
|
from pathlib import Path |
|
from typing import Any, Dict, List, Optional |
|
|
|
from dropbox import Dropbox |
|
from dropbox.files import FileMetadata, FolderMetadata, Metadata, SharedLink |
|
|
|
|
|
def main(): |
|
logging.basicConfig(format='[%(levelname)s] %(message)s', level=INFO) |
|
logger = logging.getLogger() |
|
|
|
parser = ArgumentParser( |
|
description='List files and folders from a shared link in Dropbox.', |
|
epilog='Requires `$DROPBOX_ACCESS_TOKEN` with `files.metadata.read`' |
|
'permission and `sharing.read` permission. And it outputs ' |
|
'contents from a given shared link as a JSON. A `null` value ' |
|
'means it is a file.', |
|
) |
|
parser.add_argument('url', help='shared link URL') |
|
parser.add_argument('-o', '--output', type=Path, |
|
help='write output to FILE instead of stdout', |
|
metavar='FILE') |
|
args = parser.parse_args() |
|
url: str = args.url |
|
output: Optional[Path] = args.output |
|
|
|
dbx = Dropbox(get_access_token()) |
|
|
|
tree: Dict[str, Any] = {} |
|
stack = [] |
|
|
|
for entry in list_folder(dbx, url, ''): |
|
if not isinstance(entry, FolderMetadata): |
|
raise RuntimeError(type(entry)) |
|
tree[entry.name] = {} |
|
stack.append([entry.name]) |
|
|
|
while stack: |
|
path = stack.pop() |
|
|
|
logger.info(f'Requesting /{"/".join(path)}') |
|
|
|
cur = tree |
|
for segment in path: |
|
cur = cur[segment] |
|
|
|
for entry in list_folder(dbx, url, f'/{"/".join(path)}'): |
|
if isinstance(entry, FileMetadata): |
|
cur[entry.name] = None |
|
elif isinstance(entry, FolderMetadata): |
|
cur[entry.name] = {} |
|
stack.append([*path, entry.name]) |
|
else: |
|
raise RuntimeError(type(entry)) |
|
|
|
content = json.dumps(tree) |
|
|
|
if output is None: |
|
print(content, end='') |
|
else: |
|
with open(output, 'w') as file: |
|
file.write(content) |
|
|
|
|
|
def get_access_token() -> str: |
|
if 'DROPBOX_ACCESS_TOKEN' not in os.environ: |
|
raise RuntimeError('missing `$DROPBOX_ACCESS_TOKEN`') |
|
return os.environ['DROPBOX_ACCESS_TOKEN'] |
|
|
|
|
|
def list_folder(dbx: Dropbox, url: str, path: str) -> List[Metadata]: |
|
result = dbx.files_list_folder(path, shared_link=SharedLink(url=url)) |
|
ret = result.entries |
|
while result.has_more: |
|
result = dbx.files_list_folder_continue(result.cursor) |
|
ret.extend(result.entries) |
|
return ret |
|
|
|
|
|
if __name__ == '__main__': |
|
main() |