From 824728739a62fb8c811621d4bc57ff57ad7493ce Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Mon, 10 Feb 2025 16:24:16 +0000 Subject: [PATCH 001/160] Start fleshing out the docs more - rearrange, separate out modules section, move files over to md (from rst) --- README.md | 138 +--------------- docs/_templates/autoapi/index.rst | 34 ++++ docs/_templates/autoapi/python/attribute.rst | 1 + docs/_templates/autoapi/python/class.rst | 104 ++++++++++++ docs/_templates/autoapi/python/data.rst | 38 +++++ docs/_templates/autoapi/python/exception.rst | 1 + docs/_templates/autoapi/python/function.rst | 21 +++ docs/_templates/autoapi/python/method.rst | 21 +++ docs/_templates/autoapi/python/module.rst | 156 ++++++++++++++++++ docs/_templates/autoapi/python/package.rst | 1 + docs/_templates/autoapi/python/property.rst | 21 +++ docs/source/conf.py | 12 +- docs/source/core_modules.rst | 11 ++ docs/source/developer_guidelines.rst | 6 - .../development/developer_guidelines.md | 34 ++++ docs/source/development/docker_development.md | 5 + docs/source/development/docs.md | 38 +++++ docs/source/development/release.md | 15 ++ docs/source/development/testing.md | 13 ++ docs/source/index.rst | 22 +-- .../{ => installation}/configurations.rst | 7 + docs/source/installation/installation.md | 54 ++++++ docs/source/user_guidelines.md | 12 ++ docs/source/user_guidelines.rst | 11 -- 24 files changed, 606 insertions(+), 170 deletions(-) create mode 100644 docs/_templates/autoapi/index.rst create mode 100644 docs/_templates/autoapi/python/attribute.rst create mode 100644 docs/_templates/autoapi/python/class.rst create mode 100644 docs/_templates/autoapi/python/data.rst create mode 100644 docs/_templates/autoapi/python/exception.rst create mode 100644 docs/_templates/autoapi/python/function.rst create mode 100644 docs/_templates/autoapi/python/method.rst create mode 100644 docs/_templates/autoapi/python/module.rst create mode 100644 docs/_templates/autoapi/python/package.rst create mode 100644 docs/_templates/autoapi/python/property.rst create mode 100644 docs/source/core_modules.rst delete mode 100644 docs/source/developer_guidelines.rst create mode 100644 docs/source/development/developer_guidelines.md create mode 100644 docs/source/development/docker_development.md create mode 100644 docs/source/development/docs.md create mode 100644 docs/source/development/release.md create mode 100644 docs/source/development/testing.md rename docs/source/{ => installation}/configurations.rst (93%) create mode 100644 docs/source/installation/installation.md create mode 100644 docs/source/user_guidelines.md delete mode 100644 docs/source/user_guidelines.rst diff --git a/README.md b/README.md index c52c464..b279a50 100644 --- a/README.md +++ b/README.md @@ -14,72 +14,15 @@ Read the [article about Auto Archiver on bellingcat.com](https://www.bellingcat. Python tool to automatically archive social media posts, videos, and images from a Google Sheets, the console, and more. Uses different archivers depending on the platform, and can save content to local storage, S3 bucket (Digital Ocean Spaces, AWS, ...), and Google Drive. If using Google Sheets as the source for links, it will be updated with information about the archived content. It can be run manually or on an automated basis. -There are 3 ways to use the auto-archiver: -1. (easiest installation) via docker -2. (local python install) `pip install auto-archiver` -3. (legacy/development) clone and manually install from repo (see legacy [tutorial video](https://youtu.be/VfAhcuV2tLQ)) -But **you always need a configuration/orchestration file**, which is where you'll configure where/what/how to archive. Make sure you read [orchestration](#orchestration). +## Installation +For full For instructions on how to install auto-archiver, view the [Installation Guide](docs/source/installation.md) -## How to install and run the auto-archiver +Quick run using docker: -### Option 1 - docker +`docker pull bellingcat/auto-archiver && docker run` -[![dockeri.co](https://dockerico.blankenship.io/image/bellingcat/auto-archiver)](https://hub.docker.com/r/bellingcat/auto-archiver) - -Docker works like a virtual machine running inside your computer, it isolates everything and makes installation simple. Since it is an isolated environment when you need to pass it your orchestration file or get downloaded media out of docker you will need to connect folders on your machine with folders inside docker with the `-v` volume flag. - - -1. install [docker](https://docs.docker.com/get-docker/) -2. pull the auto-archiver docker [image](https://hub.docker.com/r/bellingcat/auto-archiver) with `docker pull bellingcat/auto-archiver` -3. run the docker image locally in a container: `docker run --rm -v $PWD/secrets:/app/secrets -v $PWD/local_archive:/app/local_archive bellingcat/auto-archiver --config secrets/orchestration.yaml` breaking this command down: - 1. `docker run` tells docker to start a new container (an instance of the image) - 2. `--rm` makes sure this container is removed after execution (less garbage locally) - 3. `-v $PWD/secrets:/app/secrets` - your secrets folder - 1. `-v` is a volume flag which means a folder that you have on your computer will be connected to a folder inside the docker container - 2. `$PWD/secrets` points to a `secrets/` folder in your current working directory (where your console points to), we use this folder as a best practice to hold all the secrets/tokens/passwords/... you use - 3. `/app/secrets` points to the path the docker container where this image can be found - 4. `-v $PWD/local_archive:/app/local_archive` - (optional) if you use local_storage - 1. `-v` same as above, this is a volume instruction - 2. `$PWD/local_archive` is a folder `local_archive/` in case you want to archive locally and have the files accessible outside docker - 3. `/app/local_archive` is a folder inside docker that you can reference in your orchestration.yml file - -### Option 2 - python package - -
Python package instructions - -1. make sure you have python 3.10 or higher installed -2. install the package with your preferred package manager: `pip/pipenv/conda install auto-archiver` or `poetry add auto-archiver` -3. test it's installed with `auto-archiver --help` -4. run it with your orchestration file and pass any flags you want in the command line `auto-archiver --config secrets/orchestration.yaml` if your orchestration file is inside a `secrets/`, which we advise - -You will also need [ffmpeg](https://www.ffmpeg.org/), [firefox](https://www.mozilla.org/en-US/firefox/new/) and [geckodriver](https://github.com/mozilla/geckodriver/releases), and optionally [fonts-noto](https://fonts.google.com/noto). Similar to the local installation. - -
- - -### Option 3 - local installation -This can also be used for development. - -
Legacy instructions, only use if docker/package is not an option - - -Install the following locally: -1. [ffmpeg](https://www.ffmpeg.org/) must also be installed locally for this tool to work. -2. [firefox](https://www.mozilla.org/en-US/firefox/new/) and [geckodriver](https://github.com/mozilla/geckodriver/releases) on a path folder like `/usr/local/bin`. -3. [Poetry](https://python-poetry.org/docs/#installation) for dependency management and packaging. -4. (optional) [fonts-noto](https://fonts.google.com/noto) to deal with multiple unicode characters during selenium/geckodriver's screenshots: `sudo apt install fonts-noto -y`. - -Clone and run: -1. `git clone https://github.com/bellingcat/auto-archiver` -2. `poetry install` -3. `poetry run python -m src.auto_archiver --config secrets/orchestration.yaml` - -Note: Add the plugin [poetry-shell-plugin](https://github.com/python-poetry/poetry-plugin-shell) and run `poetry shell` to activate the virtual environment. -This allows you to run the auto-archiver without the `poetry run` prefix. - -

# Orchestration The archiver work is orchestrated by the following workflow (we call each a **step**): @@ -260,76 +203,3 @@ The "archive location" link contains the path of the archived file, in local sto ![The archive result for a link in the demo sheet.](docs/demo-archive.png) --- -## Development -Use `python -m src.auto_archiver --config secrets/orchestration.yaml` to run from the local development environment. - -### Testing - -Tests are split using `pytest.mark` into 'core' and 'download' tests. Download tests will hit the network and make API calls (e.g. Twitter, Bluesky etc.) and should be run regularly to make sure that APIs have not changed. - -Tests can be run as follows: -``` -# run core tests -pytest -ra -v -m "not download" # or poetry run pytest -ra -v -m "not download" -# run download tests -pytest -ra -v -m "download" # or poetry run pytest -ra -v -m "download" -# run all tests -pytest -ra -v # or poetry run pytest -ra -v -``` - -#### Docker development -working with docker locally: - * `docker compose up` to build the first time and run a local image with the settings in `secrets/orchestration.yaml` - * To modify/pass additional command line args, use `docker compose run auto-archiver --config secrets/orchestration.yaml [OTHER ARGUMENTS]` - * To rebuild after code changes, just pass the `--build` flag, e.g. `docker compose up --build` - - -manual release to docker hub - * `docker image tag auto-archiver bellingcat/auto-archiver:latest` - * `docker push bellingcat/auto-archiver` - - -### Building the Docs - -The documentation is built using [Sphinx](https://www.sphinx-doc.org/en/master/) and [AutoAPI](https://sphinx-autoapi.readthedocs.io/en/latest/) and hosted on ReadTheDocs. -To build the documentation locally, run the following commands: - -**Install required dependencies:** -- Install the docs group of dependencies: -```shell -# only the docs dependencies -poetry install --only docs - -# or for all dependencies -poetry install -``` -- Either use [poetry-plugin-shell](https://github.com/python-poetry/poetry-plugin-shell) to activate the virtual environment: `poetry shell` -- Or prepend the following commands with `poetry run` - -**Create the documentation:** -- Build the documentation: -``` -# Using makefile (Linux/macOS): -make -C docs html - -# or using sphinx directly (Windows/Linux/macOS): -sphinx-build -b html docs/source docs/_build/html -``` -- If you make significant changes and want a fresh build run: `make -C docs clean` to remove the old build files. - -**Viewing the documentation:** -```shell -# to open the documentation in your browser. -open docs/_build/html/index.html - -# or run autobuild to automatically update the documentation when you make changes -sphinx-autobuild docs/source docs/_build/html -``` - - - -#### RELEASE -* update version in [version.py](src/auto_archiver/version.py) -* go to github releases > new release > use `vx.y.z` for matching version notation - * package is automatically updated in pypi - * docker image is automatically pushed to dockerhup diff --git a/docs/_templates/autoapi/index.rst b/docs/_templates/autoapi/index.rst new file mode 100644 index 0000000..7d89fdd --- /dev/null +++ b/docs/_templates/autoapi/index.rst @@ -0,0 +1,34 @@ +API Reference +============= + +These pages are intended for developers of the `auto-archiver` package, +and include documentation on the core classes and functions used by +the auto-archiver + + +Core Classes +------------ + + +.. toctree:: + :titlesonly: + + {% for page in pages|selectattr("is_top_level_object") %} + {% if page.name == 'core' %} + {{ page.include_path }} + {% endif %} + {% endfor %} + +Util Functions +-------------- + +.. toctree:: + :titlesonly: + + {% for page in pages|selectattr("is_top_level_object") %} + {% if page.name == 'utils' %} + {{ page.include_path }} + {% endif %} + {% endfor %} + + diff --git a/docs/_templates/autoapi/python/attribute.rst b/docs/_templates/autoapi/python/attribute.rst new file mode 100644 index 0000000..ebaba55 --- /dev/null +++ b/docs/_templates/autoapi/python/attribute.rst @@ -0,0 +1 @@ +{% extends "python/data.rst" %} diff --git a/docs/_templates/autoapi/python/class.rst b/docs/_templates/autoapi/python/class.rst new file mode 100644 index 0000000..379f83a --- /dev/null +++ b/docs/_templates/autoapi/python/class.rst @@ -0,0 +1,104 @@ +{% if obj.display %} + {% if is_own_page %} +{{ obj.id }} +{{ "=" * obj.id | length }} + + {% endif %} + {% set visible_children = obj.children|selectattr("display")|list %} + {% set own_page_children = visible_children|selectattr("type", "in", own_page_types)|list %} + {% if is_own_page and own_page_children %} +.. toctree:: + :hidden: + + {% for child in own_page_children %} + {{ child.include_path }} + {% endfor %} + + {% endif %} +.. py:{{ obj.type }}:: {% if is_own_page %}{{ obj.id }}{% else %}{{ obj.short_name }}{% endif %}{% if obj.args %}({{ obj.args }}){% endif %} + + {% for (args, return_annotation) in obj.overloads %} + {{ " " * (obj.type | length) }} {{ obj.short_name }}{% if args %}({{ args }}){% endif %} + + {% endfor %} + {% if obj.bases %} + {% if "show-inheritance" in autoapi_options %} + + Bases: {% for base in obj.bases %}{{ base|link_objs }}{% if not loop.last %}, {% endif %}{% endfor %} + {% endif %} + + + {% if "show-inheritance-diagram" in autoapi_options and obj.bases != ["object"] %} + .. autoapi-inheritance-diagram:: {{ obj.obj["full_name"] }} + :parts: 1 + {% if "private-members" in autoapi_options %} + :private-bases: + {% endif %} + + {% endif %} + {% endif %} + {% if obj.docstring %} + + {{ obj.docstring|indent(3) }} + {% endif %} + {% for obj_item in visible_children %} + {% if obj_item.type not in own_page_types %} + + {{ obj_item.render()|indent(3) }} + {% endif %} + {% endfor %} + {% if is_own_page and own_page_children %} + {% set visible_attributes = own_page_children|selectattr("type", "equalto", "attribute")|list %} + {% if visible_attributes %} +Attributes +---------- + +.. autoapisummary:: + + {% for attribute in visible_attributes %} + {{ attribute.id }} + {% endfor %} + + + {% endif %} + {% set visible_exceptions = own_page_children|selectattr("type", "equalto", "exception")|list %} + {% if visible_exceptions %} +Exceptions +---------- + +.. autoapisummary:: + + {% for exception in visible_exceptions %} + {{ exception.id }} + {% endfor %} + + + {% endif %} + {% set visible_classes = own_page_children|selectattr("type", "equalto", "class")|list %} + {% if visible_classes %} +Classes +------- + +.. autoapisummary:: + + {% for klass in visible_classes %} + {{ klass.id }} + {% endfor %} + + + {% endif %} + {% set visible_methods = own_page_children|selectattr("type", "equalto", "method")|list %} + {% if visible_methods %} +Methods +------- + +.. autoapisummary:: + + {% for method in visible_methods %} + {{ method.id }} + {% endfor %} + + + {% endif %} + {% endif %} +{% endif %} diff --git a/docs/_templates/autoapi/python/data.rst b/docs/_templates/autoapi/python/data.rst new file mode 100644 index 0000000..1a50250 --- /dev/null +++ b/docs/_templates/autoapi/python/data.rst @@ -0,0 +1,38 @@ +{% if obj.display %} + {% if is_own_page %} +{{ obj.id }} +{{ "=" * obj.id | length }} + + {% endif %} +.. py:{{ obj.type }}:: {% if is_own_page %}{{ obj.id }}{% else %}{{ obj.name }}{% endif %} + {% if obj.annotation is not none %} + + :type: {% if obj.annotation %} {{ obj.annotation }}{% endif %} + {% endif %} + {% if obj.value is not none %} + + {% if obj.value.splitlines()|count > 1 %} + :value: Multiline-String + + .. raw:: html + +
Show Value + + .. code-block:: python + + {{ obj.value|indent(width=6,blank=true) }} + + .. raw:: html + +
+ + {% else %} + :value: {{ obj.value|truncate(100) }} + {% endif %} + {% endif %} + + {% if obj.docstring %} + + {{ obj.docstring|indent(3) }} + {% endif %} +{% endif %} diff --git a/docs/_templates/autoapi/python/exception.rst b/docs/_templates/autoapi/python/exception.rst new file mode 100644 index 0000000..92f3d38 --- /dev/null +++ b/docs/_templates/autoapi/python/exception.rst @@ -0,0 +1 @@ +{% extends "python/class.rst" %} diff --git a/docs/_templates/autoapi/python/function.rst b/docs/_templates/autoapi/python/function.rst new file mode 100644 index 0000000..5dee5aa --- /dev/null +++ b/docs/_templates/autoapi/python/function.rst @@ -0,0 +1,21 @@ +{% if obj.display %} + {% if is_own_page %} +{{ obj.id }} +{{ "=" * obj.id | length }} + + {% endif %} +.. py:function:: {% if is_own_page %}{{ obj.id }}{% else %}{{ obj.short_name }}{% endif %}({{ obj.args }}){% if obj.return_annotation is not none %} -> {{ obj.return_annotation }}{% endif %} + {% for (args, return_annotation) in obj.overloads %} + + {%+ if is_own_page %}{{ obj.id }}{% else %}{{ obj.short_name }}{% endif %}({{ args }}){% if return_annotation is not none %} -> {{ return_annotation }}{% endif %} + {% endfor %} + {% for property in obj.properties %} + + :{{ property }}: + {% endfor %} + + {% if obj.docstring %} + + {{ obj.docstring|indent(3) }} + {% endif %} +{% endif %} diff --git a/docs/_templates/autoapi/python/method.rst b/docs/_templates/autoapi/python/method.rst new file mode 100644 index 0000000..12d42de --- /dev/null +++ b/docs/_templates/autoapi/python/method.rst @@ -0,0 +1,21 @@ +{% if obj.display %} + {% if is_own_page %} +{{ obj.id }} +{{ "=" * obj.id | length }} + + {% endif %} +.. py:method:: {% if is_own_page %}{{ obj.id }}{% else %}{{ obj.short_name }}{% endif %}({{ obj.args }}){% if obj.return_annotation is not none %} -> {{ obj.return_annotation }}{% endif %} + {% for (args, return_annotation) in obj.overloads %} + + {%+ if is_own_page %}{{ obj.id }}{% else %}{{ obj.short_name }}{% endif %}({{ args }}){% if return_annotation is not none %} -> {{ return_annotation }}{% endif %} + {% endfor %} + {% for property in obj.properties %} + + :{{ property }}: + {% endfor %} + + {% if obj.docstring %} + + {{ obj.docstring|indent(3) }} + {% endif %} +{% endif %} diff --git a/docs/_templates/autoapi/python/module.rst b/docs/_templates/autoapi/python/module.rst new file mode 100644 index 0000000..53cc65d --- /dev/null +++ b/docs/_templates/autoapi/python/module.rst @@ -0,0 +1,156 @@ +{% if obj.display %} + {% if is_own_page %} +{{ obj.id }} +{{ "=" * obj.id|length }} + +.. py:module:: {{ obj.name }} + + {% if obj.docstring %} +.. autoapi-nested-parse:: + + {{ obj.docstring|indent(3) }} + + {% endif %} + + {% block submodules %} + {% set visible_subpackages = obj.subpackages|selectattr("display")|list %} + {% set visible_submodules = obj.submodules|selectattr("display")|list %} + {% set visible_submodules = (visible_subpackages + visible_submodules)|sort %} + {% if visible_submodules %} +Submodules +---------- + +.. toctree:: + :maxdepth: 1 + + {% for submodule in visible_submodules %} + {{ submodule.include_path }} + {% endfor %} + + + {% endif %} + {% endblock %} + {% block content %} + {% set visible_children = obj.children|selectattr("display")|list %} + {% if visible_children %} + {% set visible_attributes = visible_children|selectattr("type", "equalto", "data")|list %} + {% if visible_attributes %} + {% if "attribute" in own_page_types or "show-module-summary" in autoapi_options %} +Attributes +---------- + + {% if "attribute" in own_page_types %} +.. toctree:: + :hidden: + + {% for attribute in visible_attributes %} + {{ attribute.include_path }} + {% endfor %} + + {% endif %} +.. autoapisummary:: + + {% for attribute in visible_attributes %} + {{ attribute.id }} + {% endfor %} + {% endif %} + + + {% endif %} + {% set visible_exceptions = visible_children|selectattr("type", "equalto", "exception")|list %} + {% if visible_exceptions %} + {% if "exception" in own_page_types or "show-module-summary" in autoapi_options %} +Exceptions +---------- + + {% if "exception" in own_page_types %} +.. toctree:: + :hidden: + + {% for exception in visible_exceptions %} + {{ exception.include_path }} + {% endfor %} + + {% endif %} +.. autoapisummary:: + + {% for exception in visible_exceptions %} + {{ exception.id }} + {% endfor %} + {% endif %} + + + {% endif %} + {% set visible_classes = visible_children|selectattr("type", "equalto", "class")|list %} + {% if visible_classes %} + {% if "class" in own_page_types or "show-module-summary" in autoapi_options %} +Classes +------- + + {% if "class" in own_page_types %} +.. toctree:: + :hidden: + + {% for klass in visible_classes %} + {{ klass.include_path }} + {% endfor %} + + {% endif %} +.. autoapisummary:: + + {% for klass in visible_classes %} + {{ klass.id }} + {% endfor %} + {% endif %} + + + {% endif %} + {% set visible_functions = visible_children|selectattr("type", "equalto", "function")|list %} + {% if visible_functions %} + {% if "function" in own_page_types or "show-module-summary" in autoapi_options %} +Functions +--------- + + {% if "function" in own_page_types %} +.. toctree:: + :hidden: + + {% for function in visible_functions %} + {{ function.include_path }} + {% endfor %} + + {% endif %} +.. autoapisummary:: + + {% for function in visible_functions %} + {{ function.id }} + {% endfor %} + {% endif %} + + + {% endif %} + {% set this_page_children = visible_children|rejectattr("type", "in", own_page_types)|list %} + {% if this_page_children %} +{{ obj.type|title }} Contents +{{ "-" * obj.type|length }}--------- + + {% for obj_item in this_page_children %} +{{ obj_item.render()|indent(0) }} + {% endfor %} + {% endif %} + {% endif %} + {% endblock %} + {% else %} +.. py:module:: {{ obj.name }} + + {% if obj.docstring %} + .. autoapi-nested-parse:: + + {{ obj.docstring|indent(6) }} + + {% endif %} + {% for obj_item in visible_children %} + {{ obj_item.render()|indent(3) }} + {% endfor %} + {% endif %} +{% endif %} diff --git a/docs/_templates/autoapi/python/package.rst b/docs/_templates/autoapi/python/package.rst new file mode 100644 index 0000000..fb9a649 --- /dev/null +++ b/docs/_templates/autoapi/python/package.rst @@ -0,0 +1 @@ +{% extends "python/module.rst" %} diff --git a/docs/_templates/autoapi/python/property.rst b/docs/_templates/autoapi/python/property.rst new file mode 100644 index 0000000..9165311 --- /dev/null +++ b/docs/_templates/autoapi/python/property.rst @@ -0,0 +1,21 @@ +{% if obj.display %} + {% if is_own_page %} +{{ obj.id }} +{{ "=" * obj.id | length }} + + {% endif %} +.. py:property:: {% if is_own_page %}{{ obj.id}}{% else %}{{ obj.short_name }}{% endif %} + {% if obj.annotation %} + + :type: {{ obj.annotation }} + {% endif %} + {% for property in obj.properties %} + + :{{ property }}: + {% endfor %} + + {% if obj.docstring %} + + {{ obj.docstring|indent(3) }} + {% endif %} +{% endif %} diff --git a/docs/source/conf.py b/docs/source/conf.py index 3168b22..7aac1ec 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -17,6 +17,7 @@ extensions = [ 'sphinxcontrib.mermaid', # Mermaid diagrams "sphinx.ext.viewcode", # Source code links "sphinx.ext.napoleon", # Google-style and NumPy-style docstrings + "sphinx.ext.autosectionlabel", # "sphinx.ext.autodoc", # Include custom docstrings # 'sphinx.ext.autosummary', # Summarize module/class/function docs ] @@ -27,12 +28,13 @@ exclude_patterns = [] # -- AutoAPI Configuration --------------------------------------------------- autoapi_type = 'python' -autoapi_dirs = ["../../src"] +autoapi_dirs = ["../../src/auto_archiver/core/", "../../src/auto_archiver/utils/", "../../src/auto_archiver/modules/"] autodoc_typehints = "signature" # Include type hints in the signature -autoapi_ignore = [] # Ignore specific modules -autoapi_keep_files = True # Option to retain intermediate JSON files for debugging +autoapi_ignore = ["*/version.py", ] # Ignore specific modules +autoapi_keep_files = False # Option to retain intermediate JSON files for debugging autoapi_add_toctree_entry = True # Include API docs in the TOC -autoapi_template_dir = None # Use default templates +autoapi_python_use_implicit_namespaces = True +autoapi_template_dir = "../_templates/autoapi" autoapi_options = [ "members", "undoc-members", @@ -53,6 +55,8 @@ myst_enable_extensions = [ "linkify", # Auto-detect links "substitution", # Text substitutions ] +myst_heading_anchors = 2 + source_suffix = { ".rst": "restructuredtext", ".md": "markdown", diff --git a/docs/source/core_modules.rst b/docs/source/core_modules.rst new file mode 100644 index 0000000..d7b0e66 --- /dev/null +++ b/docs/source/core_modules.rst @@ -0,0 +1,11 @@ +Core Modules +============ + +These pages are intended for developers of the `auto-archiver` package, and include documentation on the core classes and functions used by the auto-archiver + +.. toctree:: + :titlesonly: + + {% for page in pages|selectattr("is_top_level_object") %} + {{ page.include_path }} + {% endfor %} diff --git a/docs/source/developer_guidelines.rst b/docs/source/developer_guidelines.rst deleted file mode 100644 index c0fdee0..0000000 --- a/docs/source/developer_guidelines.rst +++ /dev/null @@ -1,6 +0,0 @@ - -Developer Guidelines -==================== - -This section of the documentation provides guidelines for developers who want to modify or contribute to the tool. - diff --git a/docs/source/development/developer_guidelines.md b/docs/source/development/developer_guidelines.md new file mode 100644 index 0000000..26cf6f4 --- /dev/null +++ b/docs/source/development/developer_guidelines.md @@ -0,0 +1,34 @@ + +# Developer Guidelines + +This section of the documentation provides guidelines for developers who want to modify or contribute to the tool. + + +## Developer Install + +1. Clone the project using `git clone https://github.com/bellingcat/auto-archiver.git` +2. Install poetry using `curl -sSL https://install.python-poetry.org | python3 -` ([other installation methods](https://python-poetry.org/docs/#installation)) +3. Install dependencies with `poetry install` + +## Running +4. Run the code with `poetry run auto-archiver [my args]` + +```{note} +Add the plugin [poetry-shell-plugin](https://github.com/python-poetry/poetry-plugin-shell) and run `poetry shell` to activate the virtual environment. +This allows you to run the auto-archiver without the `poetry run` prefix. +``` + +### Optional Development Packages + +Install development packages (used for unit tests etc.) using: +`poetry install -with dev` + + +```{toctree} +:hidden: + +docker_development +testing +docs +release +``` \ No newline at end of file diff --git a/docs/source/development/docker_development.md b/docs/source/development/docker_development.md new file mode 100644 index 0000000..d770020 --- /dev/null +++ b/docs/source/development/docker_development.md @@ -0,0 +1,5 @@ +## Docker development +working with docker locally: + * `docker compose up` to build the first time and run a local image with the settings in `secrets/orchestration.yaml` + * To modify/pass additional command line args, use `docker compose run auto-archiver --config secrets/orchestration.yaml [OTHER ARGUMENTS]` + * To rebuild after code changes, just pass the `--build` flag, e.g. `docker compose up --build` \ No newline at end of file diff --git a/docs/source/development/docs.md b/docs/source/development/docs.md new file mode 100644 index 0000000..e21f954 --- /dev/null +++ b/docs/source/development/docs.md @@ -0,0 +1,38 @@ + +### Building the Docs + +The documentation is built using [Sphinx](https://www.sphinx-doc.org/en/master/) and [AutoAPI](https://sphinx-autoapi.readthedocs.io/en/latest/) and hosted on ReadTheDocs. +To build the documentation locally, run the following commands: + +**Install required dependencies:** +- Install the docs group of dependencies: +```shell +# only the docs dependencies +poetry install --only docs + +# or for all dependencies +poetry install +``` +- Either use [poetry-plugin-shell](https://github.com/python-poetry/poetry-plugin-shell) to activate the virtual environment: `poetry shell` +- Or prepend the following commands with `poetry run` + +**Create the documentation:** +- Build the documentation: +``` +# Using makefile (Linux/macOS): +make -C docs html + +# or using sphinx directly (Windows/Linux/macOS): +sphinx-build -b html docs/source docs/_build/html +``` +- If you make significant changes and want a fresh build run: `make -C docs clean` to remove the old build files. + +**Viewing the documentation:** +```shell +# to open the documentation in your browser. +open docs/_build/html/index.html + +# or run autobuild to automatically update the documentation when you make changes +sphinx-autobuild docs/source docs/_build/html +``` + diff --git a/docs/source/development/release.md b/docs/source/development/release.md new file mode 100644 index 0000000..6939e97 --- /dev/null +++ b/docs/source/development/release.md @@ -0,0 +1,15 @@ +# Release Process + +```{note} This is a work in progress. +``` + +1. Update the version number in [version.py](src/auto_archiver/version.py) +2. Go to github releases > new release > use `vx.y.z` for matching version notation + 1. package is automatically updated in pypi + 2. docker image is automatically pushed to dockerhup + + + +manual release to docker hub + * `docker image tag auto-archiver bellingcat/auto-archiver:latest` + * `docker push bellingcat/auto-archiver` diff --git a/docs/source/development/testing.md b/docs/source/development/testing.md new file mode 100644 index 0000000..6db7e37 --- /dev/null +++ b/docs/source/development/testing.md @@ -0,0 +1,13 @@ +### Testing + +Tests are split using `pytest.mark` into 'core' and 'download' tests. Download tests will hit the network and make API calls (e.g. Twitter, Bluesky etc.) and should be run regularly to make sure that APIs have not changed. + +Tests can be run as follows: +``` +# run core tests +pytest -ra -v -m "not download" # or poetry run pytest -ra -v -m "not download" +# run download tests +pytest -ra -v -m "download" # or poetry run pytest -ra -v -m "download" +# run all tests +pytest -ra -v # or poetry run pytest -ra -v +``` \ No newline at end of file diff --git a/docs/source/index.rst b/docs/source/index.rst index 52449b8..517cbe4 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -1,26 +1,18 @@ -.. auto-archiver documentation master file, created by - sphinx-quickstart on Sun Jan 12 20:35:50 2025. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -Auto Archiver documentation -=========================== - -.. note:: - - This is a work in progress. - .. include:: ../../README.md :parser: myst .. toctree:: - :maxdepth: 1 + + :maxdepth: 2 :hidden: :caption: Contents: + Overview user_guidelines - developer_guidelines - configurations + installation/installation + development/developer_guidelines + autoapi/index + core_modules diff --git a/docs/source/configurations.rst b/docs/source/installation/configurations.rst similarity index 93% rename from docs/source/configurations.rst rename to docs/source/installation/configurations.rst index 85d7922..e00dd94 100644 --- a/docs/source/configurations.rst +++ b/docs/source/installation/configurations.rst @@ -2,6 +2,13 @@ Configurations ============== + +```{toctree} +:hidden: + +configurations +``` + This section of the documentation provides guidelines for configuring the tool. File Reference diff --git a/docs/source/installation/installation.md b/docs/source/installation/installation.md new file mode 100644 index 0000000..ad4868c --- /dev/null +++ b/docs/source/installation/installation.md @@ -0,0 +1,54 @@ +# Installing Auto Archiver + + +There are 3 main ways to use the auto-archiver: +1. Easiest: [via docker](#installing-with-docker) +2. Local Install: [using pip](#local-installing-with-pip) +3. Developer Install: [see the developer guidelines](../development/developer_guidelines) + + +But **you always need a configuration/orchestration file**, which is where you'll configure where/what/how to archive. Make sure you read [orchestration](#orchestration). + + +## Installing with Docker + +[![dockeri.co](https://dockerico.blankenship.io/image/bellingcat/auto-archiver)](https://hub.docker.com/r/bellingcat/auto-archiver) + +Docker works like a virtual machine running inside your computer, it isolates everything and makes installation simple. Since it is an isolated environment when you need to pass it your orchestration file or get downloaded media out of docker you will need to connect folders on your machine with folders inside docker with the `-v` volume flag. + + +1. install [docker](https://docs.docker.com/get-docker/) +2. pull the auto-archiver docker [image](https://hub.docker.com/r/bellingcat/auto-archiver) with `docker pull bellingcat/auto-archiver` +3. run the docker image locally in a container: `docker run --rm -v $PWD/secrets:/app/secrets -v $PWD/local_archive:/app/local_archive bellingcat/auto-archiver --config secrets/orchestration.yaml` breaking this command down: + 1. `docker run` tells docker to start a new container (an instance of the image) + 2. `--rm` makes sure this container is removed after execution (less garbage locally) + 3. `-v $PWD/secrets:/app/secrets` - your secrets folder + 1. `-v` is a volume flag which means a folder that you have on your computer will be connected to a folder inside the docker container + 2. `$PWD/secrets` points to a `secrets/` folder in your current working directory (where your console points to), we use this folder as a best practice to hold all the secrets/tokens/passwords/... you use + 3. `/app/secrets` points to the path the docker container where this image can be found + 4. `-v $PWD/local_archive:/app/local_archive` - (optional) if you use local_storage + 1. `-v` same as above, this is a volume instruction + 2. `$PWD/local_archive` is a folder `local_archive/` in case you want to archive locally and have the files accessible outside docker + 3. `/app/local_archive` is a folder inside docker that you can reference in your orchestration.yml file + +## Local installing with Pip + +1. make sure you have python 3.10 or higher installed +2. install the package with your preferred package manager: `pip/pipenv/conda install auto-archiver` or `poetry add auto-archiver` +3. test it's installed with `auto-archiver --help` +4. run it with your orchestration file and pass any flags you want in the command line `auto-archiver --config secrets/orchestration.yaml` if your orchestration file is inside a `secrets/`, which we advise + +### Installing Local Requirements + +If using the local installation method, you will also need to install the following dependencies locally: + +1. [ffmpeg](https://www.ffmpeg.org/) must also be installed locally for this tool to work. +2. [firefox](https://www.mozilla.org/en-US/firefox/new/) and [geckodriver](https://github.com/mozilla/geckodriver/releases) on a path folder like `/usr/local/bin`. +3. (optional) [fonts-noto](https://fonts.google.com/noto) to deal with multiple unicode characters during selenium/geckodriver's screenshots: `sudo apt install fonts-noto -y`. + + + + +## Developer Install + +[See the developer guidelines](../development/developer_guidelines) \ No newline at end of file diff --git a/docs/source/user_guidelines.md b/docs/source/user_guidelines.md new file mode 100644 index 0000000..21f64a6 --- /dev/null +++ b/docs/source/user_guidelines.md @@ -0,0 +1,12 @@ + +# User Guidelines + + +This section of the documentation provides guidelines for users who want to use the tool, +without needing to modify the code. +To see the developer guidelines, see the [](development/developer_guidelines) + +```{note} +This is a work in progress. +``` + \ No newline at end of file diff --git a/docs/source/user_guidelines.rst b/docs/source/user_guidelines.rst deleted file mode 100644 index 93fb2f2..0000000 --- a/docs/source/user_guidelines.rst +++ /dev/null @@ -1,11 +0,0 @@ - -User Guidelines -=============== - -This section of the documentation provides guidelines for users who want to use the tool, -without needing to modify the code. -To see the developer guidelines, see :ref:`developer_guidelines`. - -.. note:: - - This is a work in progress. From 2650cd8fb2baab75b1ce925c89c867146c2cb5b4 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Mon, 10 Feb 2025 22:51:04 +0000 Subject: [PATCH 002/160] Use a script to auto-generate documentation for the core modules from the manifest file --- .gitignore | 1 + README.md | 6 +- docs/scripts/__init__.py | 1 + docs/scripts/scripts.py | 79 +++++++++++++++++++ docs/source/conf.py | 21 +++-- docs/source/core_modules.md | 26 ++++++ docs/source/core_modules.rst | 11 --- docs/source/development/testing.md | 20 +++-- docs/source/index.md | 16 ++++ docs/source/index.rst | 18 ----- docs/source/installation/installation.md | 18 ++--- docs/source/modules/database.md | 8 ++ docs/source/modules/enricher.md | 7 ++ docs/source/modules/extractor.md | 11 +++ docs/source/modules/feeder.md | 8 ++ docs/source/modules/formatter.md | 6 ++ docs/source/modules/storage.md | 8 ++ .../modules/wacz_enricher/__manifest__.py | 2 +- .../__manifest__.py | 2 +- 19 files changed, 216 insertions(+), 53 deletions(-) create mode 100644 docs/scripts/__init__.py create mode 100644 docs/scripts/scripts.py create mode 100644 docs/source/core_modules.md delete mode 100644 docs/source/core_modules.rst create mode 100644 docs/source/index.md delete mode 100644 docs/source/index.rst create mode 100644 docs/source/modules/database.md create mode 100644 docs/source/modules/enricher.md create mode 100644 docs/source/modules/extractor.md create mode 100644 docs/source/modules/feeder.md create mode 100644 docs/source/modules/formatter.md create mode 100644 docs/source/modules/storage.md diff --git a/.gitignore b/.gitignore index 7c6bf08..701de43 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,4 @@ archived/ dist* docs/_build/ docs/source/autoapi/ +docs/source/modules/autogen/ diff --git a/README.md b/README.md index b279a50..bffa9e0 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Python tool to automatically archive social media posts, videos, and images from ## Installation -For full For instructions on how to install auto-archiver, view the [Installation Guide](docs/source/installation.md) +For full instructions on how to install auto-archiver, view the [Installation Guide](docs/source/installation.md) Quick run using docker: @@ -65,7 +65,9 @@ auto-archiver --config secrets/orchestration.yaml --cli_feeder.urls="url1,url2,u ``` Here's the complete workflow that the auto-archiver goes through: -```{mermaid} + +```mermaid + graph TD s((start)) --> F(fa:fa-table Feeder) F -->|get and clean URL| D1{fa:fa-database Database} diff --git a/docs/scripts/__init__.py b/docs/scripts/__init__.py new file mode 100644 index 0000000..ba9737c --- /dev/null +++ b/docs/scripts/__init__.py @@ -0,0 +1 @@ +from scripts import generate_module_docs \ No newline at end of file diff --git a/docs/scripts/scripts.py b/docs/scripts/scripts.py new file mode 100644 index 0000000..34acd48 --- /dev/null +++ b/docs/scripts/scripts.py @@ -0,0 +1,79 @@ +# iterate through all the modules in auto_archiver.modules and turn the __manifest__.py file into a markdown table +from pathlib import Path +from auto_archiver.core.module import available_modules +from auto_archiver.core.base_module import BaseModule + +MODULES_FOLDER = Path(__file__).parent.parent.parent.parent / "src" / "auto_archiver" / "modules" +SAVE_FOLDER = Path(__file__).parent.parent / "source" / "modules" / "autogen" + +type_color = { + 'feeder': "[feeder](/core_modules.md#feeder-modules)", + 'extractor': "[extractor](/core_modules.md#extractor-modules)", + 'enricher': "[enricher](/core_modules.md#enricher-modules)", + 'database': "[database](/core_modules.md#database-modules)", + 'storage': "[storage](/core_modules.md#storage-modules)", + 'formatter': "[formatter](/core_modules.md#formatter-modules)", +} + + +def generate_module_docs(): + SAVE_FOLDER.mkdir(exist_ok=True) + modules_by_type = {} + + for module in available_modules(with_manifest=True): + # generate the markdown file from the __manifest__.py file. + + manifest = module.manifest + for type in manifest['type']: + modules_by_type.setdefault(type, []).append(module) + + description = "\n".join(l.lstrip() for l in manifest['description'].split("\n")) + types = ", ".join(type_color[t] for t in manifest['type']) + readme_str = f""" +# {manifest['name']} +```{{admonition}} Module type + +{types} +``` +{description} +""" + if manifest['configs']: + readme_str += "\n## Configuration Options\n" + readme_str += "| Option | Description | Default | Type |\n" + readme_str += "| --- | --- | --- | --- |\n" + for key, value in manifest['configs'].items(): + type = value.get('type', 'string') + if type == 'auto_archiver.utils.json_loader': + value['type'] = 'json' + elif type == 'str': + type = "string" + + help = "**Required**. " if value.get('required', False) else "Optional. " + help += value.get('help', '') + readme_str += f"| `{module.name}.{key}` | {help} | {value.get('default', '')} | {type} |\n" + + + # make type folder if it doesn't exist + + + with open(SAVE_FOLDER / f"{module.name}.md", "w") as f: + print("writing", SAVE_FOLDER) + f.write(readme_str) + + generate_index(modules_by_type) + +def generate_index(modules_by_type): + readme_str = "" + for type in BaseModule.MODULE_TYPES: + modules = modules_by_type.get(type, []) + module_str = f"## {type.capitalize()} Modules\n" + for module in modules: + module_str += f"\n[{module.manifest['name']}](/modules/autogen/{module.name}.md)\n" + with open(SAVE_FOLDER / f"{type}.md", "w") as f: + print("writing", SAVE_FOLDER / f"{type}.md") + f.write(module_str) + readme_str += module_str + + with open(SAVE_FOLDER / "module_list.md", "w") as f: + print("writing", SAVE_FOLDER / "module_list.md") + f.write(readme_str) diff --git a/docs/source/conf.py b/docs/source/conf.py index 7aac1ec..54988ed 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -1,20 +1,29 @@ # Configuration file for the Sphinx documentation builder. # https://www.sphinx-doc.org/en/master/usage/configuration.html - -# -- Project information ----------------------------------------------------- +import sys +import os from importlib.metadata import metadata +sys.path.insert(0, os.path.abspath('../scripts')) +from scripts import generate_module_docs + +# -- Project Hooks ----------------------------------------------------------- +# convert the module __manifest__.py files into markdown files +generate_module_docs() + + +# -- Project information ----------------------------------------------------- package_metadata = metadata("auto-archiver") project = package_metadata["name"] authors = package_metadata["authors"] release = package_metadata["version"] - +language = 'en' # -- General configuration --------------------------------------------------- extensions = [ "autoapi.extension", # Generate API documentation from docstrings + "sphinxcontrib.mermaid", # Mermaid diagrams "myst_parser", # Markdown support - 'sphinxcontrib.mermaid', # Mermaid diagrams "sphinx.ext.viewcode", # Source code links "sphinx.ext.napoleon", # Google-style and NumPy-style docstrings "sphinx.ext.autosectionlabel", @@ -54,8 +63,10 @@ myst_enable_extensions = [ "smartquotes", # Smart quotes "linkify", # Auto-detect links "substitution", # Text substitutions + "attrs_block", ] myst_heading_anchors = 2 +myst_fence_as_directive = ["mermaid"] source_suffix = { ".rst": "restructuredtext", @@ -63,6 +74,6 @@ source_suffix = { } # -- Options for HTML output ------------------------------------------------- -html_theme = 'furo' +html_theme = 'sphinx_book_theme' # html_static_path = ['_static'] diff --git a/docs/source/core_modules.md b/docs/source/core_modules.md new file mode 100644 index 0000000..8fd548e --- /dev/null +++ b/docs/source/core_modules.md @@ -0,0 +1,26 @@ +# Module Documentation + +These pages describe the core modules that come with `auto-archiver` and provide the basic functionality for archiving websites on the internet. There are five core module types: + +1. Feeders - these 'feed' information (the URLs) from various sources to the `auto-archiver` for processing +2. Extractors - these 'extract' the page data for a given URL that is fed in by a feeder +3. Enrichers - these 'enrich' the data extracted in the previous step with additional information +4. Storage - these 'store' the data in a persistent location (on disk, Google Drive etc.) +5. Databases - these 'store' the status of the entire archiving process in a log file or database. + + +```{include} modules/autogen/module_list.md +``` + + +```{toctree} +:maxdepth: 1 +:caption: Core Modules +:hidden: + +modules/feeder +modules/extractor +modules/enricher +modules/storage +modules/database +``` \ No newline at end of file diff --git a/docs/source/core_modules.rst b/docs/source/core_modules.rst deleted file mode 100644 index d7b0e66..0000000 --- a/docs/source/core_modules.rst +++ /dev/null @@ -1,11 +0,0 @@ -Core Modules -============ - -These pages are intended for developers of the `auto-archiver` package, and include documentation on the core classes and functions used by the auto-archiver - -.. toctree:: - :titlesonly: - - {% for page in pages|selectattr("is_top_level_object") %} - {{ page.include_path }} - {% endfor %} diff --git a/docs/source/development/testing.md b/docs/source/development/testing.md index 6db7e37..5de9574 100644 --- a/docs/source/development/testing.md +++ b/docs/source/development/testing.md @@ -1,13 +1,21 @@ -### Testing +# Testing -Tests are split using `pytest.mark` into 'core' and 'download' tests. Download tests will hit the network and make API calls (e.g. Twitter, Bluesky etc.) and should be run regularly to make sure that APIs have not changed. +`pytest` is used for testing. There are two main types of tests: -Tests can be run as follows: +1. 'core' tests which should be run on every change +2. 'download' tests which hit the network. These tests will do things like make API calls (e.g. Twitter, Bluesky etc.) and should be run regularly to make sure that APIs have not changed. + + +## Running Tests + +1. Make sure you've installed the dev dependencies with `pytest install --with dev` +2. Tests can be run as follows: ``` +#### Command prefix of 'poetry run' removed here for simplicity # run core tests -pytest -ra -v -m "not download" # or poetry run pytest -ra -v -m "not download" +pytest -ra -v -m "not download" # run download tests -pytest -ra -v -m "download" # or poetry run pytest -ra -v -m "download" +pytest -ra -v -m "download" # run all tests -pytest -ra -v # or poetry run pytest -ra -v +pytest -ra -v ``` \ No newline at end of file diff --git a/docs/source/index.md b/docs/source/index.md new file mode 100644 index 0000000..0c64a13 --- /dev/null +++ b/docs/source/index.md @@ -0,0 +1,16 @@ + +```{include} ../../README.md +``` + +```{toctree} +:maxdepth: 2 +:hidden: +:caption: Contents: + +Overview +user_guidelines +installation/installation.rst +core_modules.md +development/developer_guidelines +autoapi/index.rst +``` \ No newline at end of file diff --git a/docs/source/index.rst b/docs/source/index.rst deleted file mode 100644 index 517cbe4..0000000 --- a/docs/source/index.rst +++ /dev/null @@ -1,18 +0,0 @@ - -.. include:: ../../README.md - :parser: myst - - -.. toctree:: - - :maxdepth: 2 - :hidden: - :caption: Contents: - - Overview - user_guidelines - installation/installation - development/developer_guidelines - autoapi/index - core_modules - diff --git a/docs/source/installation/installation.md b/docs/source/installation/installation.md index ad4868c..99c6bf6 100644 --- a/docs/source/installation/installation.md +++ b/docs/source/installation/installation.md @@ -3,7 +3,7 @@ There are 3 main ways to use the auto-archiver: 1. Easiest: [via docker](#installing-with-docker) -2. Local Install: [using pip](#local-installing-with-pip) +2. Local Install: [using pip](#installing-locally-with-pip) 3. Developer Install: [see the developer guidelines](../development/developer_guidelines) @@ -17,9 +17,9 @@ But **you always need a configuration/orchestration file**, which is where you'l Docker works like a virtual machine running inside your computer, it isolates everything and makes installation simple. Since it is an isolated environment when you need to pass it your orchestration file or get downloaded media out of docker you will need to connect folders on your machine with folders inside docker with the `-v` volume flag. -1. install [docker](https://docs.docker.com/get-docker/) -2. pull the auto-archiver docker [image](https://hub.docker.com/r/bellingcat/auto-archiver) with `docker pull bellingcat/auto-archiver` -3. run the docker image locally in a container: `docker run --rm -v $PWD/secrets:/app/secrets -v $PWD/local_archive:/app/local_archive bellingcat/auto-archiver --config secrets/orchestration.yaml` breaking this command down: +1. Install [docker](https://docs.docker.com/get-docker/) +2. Pull the auto-archiver docker [image](https://hub.docker.com/r/bellingcat/auto-archiver) with `docker pull bellingcat/auto-archiver` +3. Run the docker image locally in a container: `docker run --rm -v $PWD/secrets:/app/secrets -v $PWD/local_archive:/app/local_archive bellingcat/auto-archiver --config secrets/orchestration.yaml` breaking this command down: 1. `docker run` tells docker to start a new container (an instance of the image) 2. `--rm` makes sure this container is removed after execution (less garbage locally) 3. `-v $PWD/secrets:/app/secrets` - your secrets folder @@ -31,12 +31,12 @@ Docker works like a virtual machine running inside your computer, it isolates ev 2. `$PWD/local_archive` is a folder `local_archive/` in case you want to archive locally and have the files accessible outside docker 3. `/app/local_archive` is a folder inside docker that you can reference in your orchestration.yml file -## Local installing with Pip +## Installing Locally with Pip -1. make sure you have python 3.10 or higher installed -2. install the package with your preferred package manager: `pip/pipenv/conda install auto-archiver` or `poetry add auto-archiver` -3. test it's installed with `auto-archiver --help` -4. run it with your orchestration file and pass any flags you want in the command line `auto-archiver --config secrets/orchestration.yaml` if your orchestration file is inside a `secrets/`, which we advise +1. Make sure you have python 3.10 or higher installed +2. Install the package with your preferred package manager: `pip/pipenv/conda install auto-archiver` or `poetry add auto-archiver` +3. Test it's installed with `auto-archiver --help` +4. Run it with your orchestration file and pass any flags you want in the command line `auto-archiver --config secrets/orchestration.yaml` if your orchestration file is inside a `secrets/`, which we advise ### Installing Local Requirements diff --git a/docs/source/modules/database.md b/docs/source/modules/database.md new file mode 100644 index 0000000..9719a62 --- /dev/null +++ b/docs/source/modules/database.md @@ -0,0 +1,8 @@ +# Database Modules + +Database modules are used to store the status and results of the extraction and enrichment processes somewhere. The database modules are responsible for creating and managing entires for each item that has been processed. + +The default (enabled) databases are the CSV Database and the Console Database. + +```{include} autogen/database.md +``` diff --git a/docs/source/modules/enricher.md b/docs/source/modules/enricher.md new file mode 100644 index 0000000..b27958e --- /dev/null +++ b/docs/source/modules/enricher.md @@ -0,0 +1,7 @@ +# Enricher Modules + +Enricher modules are used to add additional information to the items that have been extracted. Common enrichment tasks include adding metadata to items, such as the hash of the item, a screenshot of the webpage when the item was extracted, or general metadata like the date and time the item was extracted. + + +```{include} autogen/enricher.md +``` \ No newline at end of file diff --git a/docs/source/modules/extractor.md b/docs/source/modules/extractor.md new file mode 100644 index 0000000..ddd09b9 --- /dev/null +++ b/docs/source/modules/extractor.md @@ -0,0 +1,11 @@ +# Extractor Modules + +Extractor modules are used to extract the content of a given URL. Typically, one extractor will work for one website or platform (e.g. a Telegram extractor or an Instagram), however, there are several wide-ranging extractors which work for a wide range of websites. + +Extractors that are able to extract content from a wide range of websites include: +1. Generic Extractor: parses videos and images on sites using the powerful yt-dlp library. +2. Wayback Machine Extractor: sends pages to the Waygback machine for archiving, and stores the link. +3. WACZ Extractor: runs a web browser to 'browse' the URL and save a copy of the page in WACZ format. + +```{include} autogen/extractor.md +``` \ No newline at end of file diff --git a/docs/source/modules/feeder.md b/docs/source/modules/feeder.md new file mode 100644 index 0000000..5ba77a4 --- /dev/null +++ b/docs/source/modules/feeder.md @@ -0,0 +1,8 @@ +# Feeder Modules + +Feeder modules are used to feed URLs into the `auto-archiver` for processing. Feeders can take these URLs from a variety of sources, such as a file, a database, or the command line. + +The default feeder is the command line feeder, which allows you to input URLs directly into the `auto-archiver` from the command line. + +```{include} autogen/feeder.md +``` \ No newline at end of file diff --git a/docs/source/modules/formatter.md b/docs/source/modules/formatter.md new file mode 100644 index 0000000..08bc5fb --- /dev/null +++ b/docs/source/modules/formatter.md @@ -0,0 +1,6 @@ +# Formatter Modules + +Formatter modules are used to format the data extracted from a URL into a specific format. Currently the most widely-used formatter is the HTML formatter, which formats the data into an easily viewable HTML page. + +```{include} autogen/formatter.md +``` \ No newline at end of file diff --git a/docs/source/modules/storage.md b/docs/source/modules/storage.md new file mode 100644 index 0000000..998409a --- /dev/null +++ b/docs/source/modules/storage.md @@ -0,0 +1,8 @@ +# Storage Modules + +Storage modules are used to store the data extracted from a URL in a persistent location. This can be on your local hard disk, or on a remote server (e.g. S3 or Google Drive). + +The default is to store the files downloaded (e.g. images, videos) in a local directory. + +```{include} autogen/storage.md +``` \ No newline at end of file diff --git a/src/auto_archiver/modules/wacz_enricher/__manifest__.py b/src/auto_archiver/modules/wacz_enricher/__manifest__.py index 46ce05e..bebfc9e 100644 --- a/src/auto_archiver/modules/wacz_enricher/__manifest__.py +++ b/src/auto_archiver/modules/wacz_enricher/__manifest__.py @@ -1,6 +1,6 @@ { "name": "WACZ Enricher", - "type": ["enricher", "archiver"], + "type": ["enricher", "extractor"], "entry_point": "wacz_enricher::WaczExtractorEnricher", "requires_setup": True, "dependencies": { diff --git a/src/auto_archiver/modules/wayback_extractor_enricher/__manifest__.py b/src/auto_archiver/modules/wayback_extractor_enricher/__manifest__.py index baecc14..4832265 100644 --- a/src/auto_archiver/modules/wayback_extractor_enricher/__manifest__.py +++ b/src/auto_archiver/modules/wayback_extractor_enricher/__manifest__.py @@ -1,6 +1,6 @@ { "name": "Wayback Machine Enricher", - "type": ["enricher", "archiver"], + "type": ["enricher", "extractor"], "entry_point": "wayback_extractor_enricher::WaybackExtractorEnricher", "requires_setup": True, "dependencies": { From dbc564e18b7add31ae929a7de9147f2993a5bc8e Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Mon, 10 Feb 2025 22:58:52 +0000 Subject: [PATCH 003/160] Add sphinx_book_theme theme to poetry --- README.md | 45 ------------------ docs/source/core_modules.md | 2 +- docs/source/how_to.md | 47 +++++++++++++++++++ docs/source/index.md | 2 +- docs/source/user_guidelines.md | 12 ----- poetry.lock | 86 +++++++++++++++++++++++----------- pyproject.toml | 2 +- 7 files changed, 109 insertions(+), 87 deletions(-) create mode 100644 docs/source/how_to.md delete mode 100644 docs/source/user_guidelines.md diff --git a/README.md b/README.md index bffa9e0..27697e2 100644 --- a/README.md +++ b/README.md @@ -160,48 +160,3 @@ configurations: algorithm: "SHA-256" ``` -## Running on Google Sheets Feeder (gsheet_feeder) -The `--gsheet_feeder.sheet` property is the name of the Google Sheet to check for URLs. -This sheet must have been shared with the Google Service account used by `gspread`. -This sheet must also have specific columns (case-insensitive) in the `header` as specified in [gsheet_feeder.__manifest__.py](src/auto_archiver/modules/gsheet_feeder/__manifest__.py). The default names of these columns and their purpose is: - -Inputs: - -* **Link** *(required)*: the URL of the post to archive -* **Destination folder**: custom folder for archived file (regardless of storage) - -Outputs: -* **Archive status** *(required)*: Status of archive operation -* **Archive location**: URL of archived post -* **Archive date**: Date archived -* **Thumbnail**: Embeds a thumbnail for the post in the spreadsheet -* **Timestamp**: Timestamp of original post -* **Title**: Post title -* **Text**: Post text -* **Screenshot**: Link to screenshot of post -* **Hash**: Hash of archived HTML file (which contains hashes of post media) - for checksums/verification -* **Perceptual Hash**: Perceptual hashes of found images - these can be used for de-duplication of content -* **WACZ**: Link to a WACZ web archive of post -* **ReplayWebpage**: Link to a ReplayWebpage viewer of the WACZ archive - -For example, this is a spreadsheet configured with all of the columns for the auto archiver and a few URLs to archive. (Note that the column names are not case sensitive.) - -![A screenshot of a Google Spreadsheet with column headers defined as above, and several Youtube and Twitter URLs in the "Link" column](docs/demo-before.png) - -Now the auto archiver can be invoked, with this command in this example: `docker run --rm -v $PWD/secrets:/app/secrets -v $PWD/local_archive:/app/local_archive bellingcat/auto-archiver:dockerize --config secrets/orchestration-global.yaml --gsheet_feeder.sheet "Auto archive test 2023-2"`. Note that the sheet name has been overridden/specified in the command line invocation. - -When the auto archiver starts running, it updates the "Archive status" column. - -![A screenshot of a Google Spreadsheet with column headers defined as above, and several Youtube and Twitter URLs in the "Link" column. The auto archiver has added "archive in progress" to one of the status columns.](docs/demo-progress.png) - -The links are downloaded and archived, and the spreadsheet is updated to the following: - -![A screenshot of a Google Spreadsheet with videos archived and metadata added per the description of the columns above.](docs/demo-after.png) - -Note that the first row is skipped, as it is assumed to be a header row (`--gsheet_feeder.header=1` and you can change it if you use more rows above). Rows with an empty URL column, or a non-empty archive column are also skipped. All sheets in the document will be checked. - -The "archive location" link contains the path of the archived file, in local storage, S3, or in Google Drive. - -![The archive result for a link in the demo sheet.](docs/demo-archive.png) - ---- diff --git a/docs/source/core_modules.md b/docs/source/core_modules.md index 8fd548e..bb2b3f1 100644 --- a/docs/source/core_modules.md +++ b/docs/source/core_modules.md @@ -1,6 +1,6 @@ # Module Documentation -These pages describe the core modules that come with `auto-archiver` and provide the basic functionality for archiving websites on the internet. There are five core module types: +These pages describe the core modules that come with `auto-archiver` and provide the main functionality for archiving websites on the internet. There are five core module types: 1. Feeders - these 'feed' information (the URLs) from various sources to the `auto-archiver` for processing 2. Extractors - these 'extract' the page data for a given URL that is fed in by a feeder diff --git a/docs/source/how_to.md b/docs/source/how_to.md new file mode 100644 index 0000000..e8e5d9b --- /dev/null +++ b/docs/source/how_to.md @@ -0,0 +1,47 @@ +# How-To Guides + +## How to use Google Sheets to load and store archive information +The `--gsheet_feeder.sheet` property is the name of the Google Sheet to check for URLs. +This sheet must have been shared with the Google Service account used by `gspread`. +This sheet must also have specific columns (case-insensitive) in the `header` as specified in [gsheet_feeder.__manifest__.py](src/auto_archiver/modules/gsheet_feeder/__manifest__.py). The default names of these columns and their purpose is: + +Inputs: + +* **Link** *(required)*: the URL of the post to archive +* **Destination folder**: custom folder for archived file (regardless of storage) + +Outputs: +* **Archive status** *(required)*: Status of archive operation +* **Archive location**: URL of archived post +* **Archive date**: Date archived +* **Thumbnail**: Embeds a thumbnail for the post in the spreadsheet +* **Timestamp**: Timestamp of original post +* **Title**: Post title +* **Text**: Post text +* **Screenshot**: Link to screenshot of post +* **Hash**: Hash of archived HTML file (which contains hashes of post media) - for checksums/verification +* **Perceptual Hash**: Perceptual hashes of found images - these can be used for de-duplication of content +* **WACZ**: Link to a WACZ web archive of post +* **ReplayWebpage**: Link to a ReplayWebpage viewer of the WACZ archive + +For example, this is a spreadsheet configured with all of the columns for the auto archiver and a few URLs to archive. (Note that the column names are not case sensitive.) + +![A screenshot of a Google Spreadsheet with column headers defined as above, and several Youtube and Twitter URLs in the "Link" column](docs/demo-before.png) + +Now the auto archiver can be invoked, with this command in this example: `docker run --rm -v $PWD/secrets:/app/secrets -v $PWD/local_archive:/app/local_archive bellingcat/auto-archiver:dockerize --config secrets/orchestration-global.yaml --gsheet_feeder.sheet "Auto archive test 2023-2"`. Note that the sheet name has been overridden/specified in the command line invocation. + +When the auto archiver starts running, it updates the "Archive status" column. + +![A screenshot of a Google Spreadsheet with column headers defined as above, and several Youtube and Twitter URLs in the "Link" column. The auto archiver has added "archive in progress" to one of the status columns.](docs/demo-progress.png) + +The links are downloaded and archived, and the spreadsheet is updated to the following: + +![A screenshot of a Google Spreadsheet with videos archived and metadata added per the description of the columns above.](docs/demo-after.png) + +Note that the first row is skipped, as it is assumed to be a header row (`--gsheet_feeder.header=1` and you can change it if you use more rows above). Rows with an empty URL column, or a non-empty archive column are also skipped. All sheets in the document will be checked. + +The "archive location" link contains the path of the archived file, in local storage, S3, or in Google Drive. + +![The archive result for a link in the demo sheet.](docs/demo-archive.png) + +--- diff --git a/docs/source/index.md b/docs/source/index.md index 0c64a13..bcab52a 100644 --- a/docs/source/index.md +++ b/docs/source/index.md @@ -8,7 +8,7 @@ :caption: Contents: Overview -user_guidelines +how_to installation/installation.rst core_modules.md development/developer_guidelines diff --git a/docs/source/user_guidelines.md b/docs/source/user_guidelines.md deleted file mode 100644 index 21f64a6..0000000 --- a/docs/source/user_guidelines.md +++ /dev/null @@ -1,12 +0,0 @@ - -# User Guidelines - - -This section of the documentation provides guidelines for users who want to use the tool, -without needing to modify the code. -To see the developer guidelines, see the [](development/developer_guidelines) - -```{note} -This is a work in progress. -``` - \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index 8fb48ec..8748ea7 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,5 +1,24 @@ # This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand. +[[package]] +name = "accessible-pygments" +version = "0.0.5" +description = "A collection of accessible pygments styles" +optional = false +python-versions = ">=3.9" +groups = ["docs"] +files = [ + {file = "accessible_pygments-0.0.5-py3-none-any.whl", hash = "sha256:88ae3211e68a1d0b011504b2ffc1691feafce124b845bd072ab6f9f66f34d4b7"}, + {file = "accessible_pygments-0.0.5.tar.gz", hash = "sha256:40918d3e6a2b619ad424cb91e556bd3bd8865443d9f22f1dcdf79e33c8046872"}, +] + +[package.dependencies] +pygments = ">=1.5" + +[package.extras] +dev = ["pillow", "pkginfo (>=1.10)", "playwright", "pre-commit", "setuptools", "twine (>=5.0)"] +tests = ["hypothesis", "pytest"] + [[package]] name = "alabaster" version = "1.0.0" @@ -722,24 +741,6 @@ future = "*" [package.extras] dev = ["Sphinx (==2.1.0)", "future (==0.17.1)", "numpy (==1.16.4)", "pytest (==4.6.1)", "pytest-mock (==1.10.4)", "tox (==3.12.1)"] -[[package]] -name = "furo" -version = "2024.8.6" -description = "A clean customisable Sphinx documentation theme." -optional = false -python-versions = ">=3.8" -groups = ["docs"] -files = [ - {file = "furo-2024.8.6-py3-none-any.whl", hash = "sha256:6cd97c58b47813d3619e63e9081169880fbe331f0ca883c871ff1f3f11814f5c"}, - {file = "furo-2024.8.6.tar.gz", hash = "sha256:b63e4cee8abfc3136d3bc03a3d45a76a850bada4d6374d24c1716b0e01394a01"}, -] - -[package.dependencies] -beautifulsoup4 = "*" -pygments = ">=2.7" -sphinx = ">=6.0,<9.0" -sphinx-basic-ng = ">=1.0.0.beta2" - [[package]] name = "future" version = "1.0.0" @@ -1653,6 +1654,34 @@ files = [ {file = "pycryptodomex-3.21.0.tar.gz", hash = "sha256:222d0bd05381dd25c32dd6065c071ebf084212ab79bab4599ba9e6a3e0009e6c"}, ] +[[package]] +name = "pydata-sphinx-theme" +version = "0.16.1" +description = "Bootstrap-based Sphinx theme from the PyData community" +optional = false +python-versions = ">=3.9" +groups = ["docs"] +files = [ + {file = "pydata_sphinx_theme-0.16.1-py3-none-any.whl", hash = "sha256:225331e8ac4b32682c18fcac5a57a6f717c4e632cea5dd0e247b55155faeccde"}, + {file = "pydata_sphinx_theme-0.16.1.tar.gz", hash = "sha256:a08b7f0b7f70387219dc659bff0893a7554d5eb39b59d3b8ef37b8401b7642d7"}, +] + +[package.dependencies] +accessible-pygments = "*" +Babel = "*" +beautifulsoup4 = "*" +docutils = "!=0.17.0" +pygments = ">=2.7" +sphinx = ">=6.1" +typing-extensions = "*" + +[package.extras] +a11y = ["pytest-playwright"] +dev = ["pandoc", "pre-commit", "pydata-sphinx-theme[doc,test]", "pyyaml", "sphinx-theme-builder[cli]", "tox"] +doc = ["ablog (>=0.11.8)", "colorama", "graphviz", "ipykernel", "ipyleaflet", "ipywidgets", "jupyter_sphinx", "jupyterlite-sphinx", "linkify-it-py", "matplotlib", "myst-parser", "nbsphinx", "numpy", "numpydoc", "pandas", "plotly", "rich", "sphinx-autoapi (>=3.0.0)", "sphinx-copybutton", "sphinx-design", "sphinx-favicon (>=1.0.1)", "sphinx-sitemap", "sphinx-togglebutton", "sphinxcontrib-youtube (>=1.4.1)", "sphinxext-rediraffe", "xarray"] +i18n = ["Babel", "jinja2"] +test = ["pytest", "pytest-cov", "pytest-regressions", "sphinx[test]"] + [[package]] name = "pygments" version = "2.19.1" @@ -2359,22 +2388,25 @@ websockets = ">=11" test = ["httpx", "pytest (>=6)"] [[package]] -name = "sphinx-basic-ng" -version = "1.0.0b2" -description = "A modern skeleton for Sphinx themes." +name = "sphinx-book-theme" +version = "1.1.3" +description = "A clean book theme for scientific explanations and documentation with Sphinx" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" groups = ["docs"] files = [ - {file = "sphinx_basic_ng-1.0.0b2-py3-none-any.whl", hash = "sha256:eb09aedbabfb650607e9b4b68c9d240b90b1e1be221d6ad71d61c52e29f7932b"}, - {file = "sphinx_basic_ng-1.0.0b2.tar.gz", hash = "sha256:9ec55a47c90c8c002b5960c57492ec3021f5193cb26cebc2dc4ea226848651c9"}, + {file = "sphinx_book_theme-1.1.3-py3-none-any.whl", hash = "sha256:a554a9a7ac3881979a87a2b10f633aa2a5706e72218a10f71be38b3c9e831ae9"}, + {file = "sphinx_book_theme-1.1.3.tar.gz", hash = "sha256:1f25483b1846cb3d353a6bc61b3b45b031f4acf845665d7da90e01ae0aef5b4d"}, ] [package.dependencies] -sphinx = ">=4.0" +pydata-sphinx-theme = ">=0.15.2" +sphinx = ">=5" [package.extras] -docs = ["furo", "ipython", "myst-parser", "sphinx-copybutton", "sphinx-inline-tabs"] +code-style = ["pre-commit"] +doc = ["ablog", "folium", "ipywidgets", "matplotlib", "myst-nb", "nbclient", "numpy", "numpydoc", "pandas", "plotly", "sphinx-copybutton", "sphinx-design", "sphinx-examples", "sphinx-tabs", "sphinx-thebe", "sphinx-togglebutton", "sphinxcontrib-bibtex", "sphinxcontrib-youtube", "sphinxext-opengraph"] +test = ["beautifulsoup4", "coverage", "defusedxml", "myst-nb", "pytest", "pytest-cov", "pytest-regressions", "sphinx_thebe"] [[package]] name = "sphinx-copybutton" @@ -3100,4 +3132,4 @@ test = ["pytest (>=8.1,<9.0)", "pytest-rerunfailures (>=14.0,<15.0)"] [metadata] lock-version = "2.1" python-versions = ">=3.10,<3.13" -content-hash = "9ca114395e73af8982abbccc25b385bbca62e50ba7cca8239e52e5c1227cb4b0" +content-hash = "432fe98be0e17791a047396646177cb8aaf6590c6d6247829664ed6fc1f84428" diff --git a/pyproject.toml b/pyproject.toml index f1be273..f025842 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,7 +72,7 @@ sphinxcontrib-mermaid = "^1.0.0" sphinx-autobuild = "^2024.10.3" sphinx-copybutton = "^0.5.2" myst-parser = "^4.0.0" -furo = "^2024.8.6" +sphinx-book-theme = "^1.1.3" [project.scripts] From e7273bc7416600476b4fc3e306a7c64c42a41132 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Mon, 10 Feb 2025 23:04:41 +0000 Subject: [PATCH 004/160] Fix link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 27697e2..e0ef0f5 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Python tool to automatically archive social media posts, videos, and images from ## Installation -For full instructions on how to install auto-archiver, view the [Installation Guide](docs/source/installation.md) +For full instructions on how to install auto-archiver, view the [Installation Guide](installation/installation.md) Quick run using docker: From 2f51d3917a7debf206e237cb8dbc356276236924 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Tue, 11 Feb 2025 13:49:30 +0000 Subject: [PATCH 005/160] Further addition to docs: creating modules, configurations, installation --- docs/source/conf.py | 5 +- docs/source/development/creating_modules.md | 59 +++++++ .../development/developer_guidelines.md | 2 +- docs/source/example.orchestration.yaml | 79 +++++++++ docs/source/installation/configurations.md | 100 +++++++++++ docs/source/installation/configurations.rst | 41 ----- docs/source/installation/installation.md | 5 + example.orchestration.yaml | 156 ------------------ src/auto_archiver/core/config.py | 1 + .../example_module/__manifest__.py | 22 ++- 10 files changed, 266 insertions(+), 204 deletions(-) create mode 100644 docs/source/development/creating_modules.md create mode 100644 docs/source/example.orchestration.yaml create mode 100644 docs/source/installation/configurations.md delete mode 100644 docs/source/installation/configurations.rst delete mode 100644 example.orchestration.yaml diff --git a/docs/source/conf.py b/docs/source/conf.py index 54988ed..a749dfd 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -21,13 +21,12 @@ language = 'en' # -- General configuration --------------------------------------------------- extensions = [ + "myst_parser", # Markdown support "autoapi.extension", # Generate API documentation from docstrings "sphinxcontrib.mermaid", # Mermaid diagrams - "myst_parser", # Markdown support "sphinx.ext.viewcode", # Source code links "sphinx.ext.napoleon", # Google-style and NumPy-style docstrings "sphinx.ext.autosectionlabel", - # "sphinx.ext.autodoc", # Include custom docstrings # 'sphinx.ext.autosummary', # Summarize module/class/function docs ] @@ -55,7 +54,6 @@ autoapi_options = [ # -- Markdown Support -------------------------------------------------------- myst_enable_extensions = [ - "colon_fence", # ::: fences "deflist", # Definition lists "html_admonition", # HTML-style admonitions "html_image", # Inline HTML images @@ -63,7 +61,6 @@ myst_enable_extensions = [ "smartquotes", # Smart quotes "linkify", # Auto-detect links "substitution", # Text substitutions - "attrs_block", ] myst_heading_anchors = 2 myst_fence_as_directive = ["mermaid"] diff --git a/docs/source/development/creating_modules.md b/docs/source/development/creating_modules.md new file mode 100644 index 0000000..7ee65ef --- /dev/null +++ b/docs/source/development/creating_modules.md @@ -0,0 +1,59 @@ +# Creating Your Own Modules + +Modules are what's used to extend `auto-archiver` to process different websites or media, and/or transform the data in a way that suits your needs. In most cases, the [](../core_modules.md) should be sufficient for every day use, but the most common use-cases for making your own Modules include: + +1. Extracting data from a website which doesn't work with the current core extractors. +2. Enriching or altering the data before saving with additional information that the core enrichers do not offer. +3. Storing your data in a different format/location from what the core storage providers offer. + +## Setting up the folder structure + +1. First, decide what type of module you wish to create. Check the types of modules on the [](../core_modules.md) page to decide what type you need. (Note: a module can be more than one type, more on that below) +2. Create a new python package (a folder) with the name of your module (in this tutorial, we'll call it `awesome_extractor`). +3. Create the `__manifest__.py` and an the `awesome_extractor.py` files in this folder. + +When done, you should have a module structure as follows: + +``` +. +├── awesome_extractor +│ ├── __manifest__.py +│ └── awesome_extractor.py +``` + +Check out the [core modules](https://github.com/bellingcat/auto-archiver/tree/main/src/auto_archiver/modules) in the `auto-archiver` repository for examples of the folder structure for real-world modules. + +## Populating the Manifest File + +The manifest file is where you define the core information of your module. It is a python dict containing important information, here's an example file: + +```{code} python +:filename: myfile.py + +def setup(): + pass +``` + +```{include} ../../../tests/data/test_modules/example_module/__manifest__.py +:name: __manifest__.py +:literal: +:parser: python +``` + +## Creating the Python Code + +The next step is to create your module code. First, create a class which should subclass the base module types from `auto_archiver.core`, here's an example class for the `awesome_extractor` module which is an `extractor`: + +```{code-block} python +:filename: awesome_extractor.py + +from auto_archiver.core import Extractor, Metadata + +def AwesomeExtractor(Extractor): + + def download(self, item: Metadata) -> Metadata | False: + url = item.get_url() + # download the content and create the metadata object + metadata = ... + return metadata +``` diff --git a/docs/source/development/developer_guidelines.md b/docs/source/development/developer_guidelines.md index 26cf6f4..e72193a 100644 --- a/docs/source/development/developer_guidelines.md +++ b/docs/source/development/developer_guidelines.md @@ -26,7 +26,7 @@ Install development packages (used for unit tests etc.) using: ```{toctree} :hidden: - +creating_modules docker_development testing docs diff --git a/docs/source/example.orchestration.yaml b/docs/source/example.orchestration.yaml new file mode 100644 index 0000000..48d354d --- /dev/null +++ b/docs/source/example.orchestration.yaml @@ -0,0 +1,79 @@ +# Auto Archiver Configuration +# Steps are the modules that will be run in the order they are defined + +steps: + feeders: + - cli_feeder + extractors: + - generic_extractor + - telegram_extractor + enrichers: + - thumbnail_enricher + - meta_enricher + - pdq_hash_enricher + - ssl_enricher + - hash_enricher + databases: + - console_db + - csv_db + storages: + - local_storage + formatters: + - html_formatter + +# Global configuration + +# Authentication +# a dictionary of authentication information that can be used by extractors to login to website. +# you can use a comma separated list for multiple domains on the same line (common usecase: x.com,twitter.com) +# Common login 'types' are username/password, cookie, api key/token. +# There are two special keys for using cookies, they are: cookies_file and cookies_from_browser. +# Some Examples: +# facebook.com: +# username: "my_username" +# password: "my_password" +# or for a site that uses an API key: +# twitter.com,x.com: +# api_key +# api_secret +# youtube.com: +# cookie: "login_cookie=value ; other_cookie=123" # multiple 'key=value' pairs should be separated by ; + +authentication: {} + +# Logging settings for your project. See the logging settings with --help + +logging: + level: INFO + +# These are the global configurations that are used by the modules + + file: + rotation: +local_storage: + path_generator: flat + filename_generator: static + save_to: ./local_archive + save_absolute: false +html_formatter: + detect_thumbnails: true +thumbnail_enricher: + thumbnails_per_minute: 60 + max_thumbnails: 16 +generic_extractor: + subtitles: true + comments: false + livestreams: false + live_from_start: false + proxy: '' + end_means_success: true + allow_playlist: false + max_downloads: inf +csv_db: + csv_file: db.csv +ssl_enricher: + skip_when_nothing_archived: true +hash_enricher: + algorithm: SHA-256 + chunksize: 16000000 + diff --git a/docs/source/installation/configurations.md b/docs/source/installation/configurations.md new file mode 100644 index 0000000..ef82e2a --- /dev/null +++ b/docs/source/installation/configurations.md @@ -0,0 +1,100 @@ + +# Configuring + + +```{toctree} +:hidden: + +configurations +``` + +This section of the documentation provides guidelines for configuring the tool. + +## Configuring from the Command Line + +You can run auto-archiver directy from the command line, without the need for a configuration file, command line arguments are parsed using the format `module_name.config_value`. For example, a config value of `api_key` in the `instagram_extractor` module would be passed on the command line with the flag `--instagram_extractor.api_key=API_KEY`. + +The command line arguments are useful for testing or editing config values and enabling/disabling modules on the fly. When you are happy with your settings, you can store them back in your configuration file by passing the `-s/--store` flag on the command line. + +```bash +auto-archiver --instagram_extractor.api_key=123 --other_module.setting --store +# will store the new settings into the configuration file (default: orchestration.yaml) +``` + +## Configuring using a file + +The recommended way to configure auto-archiver for long-term and deployed projects is a configuration file, typically called `orchestration.yaml`. This is a YAML file containing all the settings for your entire workflow. + +A default `orchestration.yaml` will be created for you the first time you run auto-archiver (without any arguments). Here's what it looks like: + +
+View example.orchestration.yaml + +```{literalinclude} ../example.orchestration.yaml + :language: yaml + :caption: example.orchestration.yaml +``` + +
+ +## Core Module Configuration + +View the configurable settings for the core modules on the individual doc pages for each [](../core_modules.md). +You can also view all settings available for the modules you have on your system using the `--help` flag in auto-archiver. + +```{code-block} console +:caption: Example output when using the --help flag with auto-archiver +$ auto-archiver --help +... +Positional Arguments: + urls URL(s) to archive, either a single URL or a list of urls, should not come from config.yaml + +Options: + --help, -h show a full help message and exit + --version show program's version number and exit + --config CONFIG_FILE the filename of the YAML configuration file (defaults to 'config.yaml') + --mode {simple,full} the mode to run the archiver in + -s, --store, --no-store + Store the created config in the config file + --module_paths MODULE_PATHS [MODULE_PATHS ...] + additional paths to search for modules + --feeders STEPS.FEEDERS [STEPS.FEEDERS ...] + the feeders to use + --enrichers STEPS.ENRICHERS [STEPS.ENRICHERS ...] + the enrichers to use + --extractors STEPS.EXTRACTORS [STEPS.EXTRACTORS ...] + the extractors to use + --databases STEPS.DATABASES [STEPS.DATABASES ...] + the databases to use + --storages STEPS.STORAGES [STEPS.STORAGES ...] + the storages to use + --formatters STEPS.FORMATTERS [STEPS.FORMATTERS ...] + the formatter to use + --authentication AUTHENTICATION + A dictionary of sites and their authentication methods (token, username etc.) that extractors can use to log into a website. If passing this on the command line, use a JSON string. You may + also pass a path to a valid JSON/YAML file which will be parsed. + --logging.level {INFO,DEBUG,ERROR,WARNING} + the logging level to use + --logging.file LOGGING.FILE + the logging file to write to + --logging.rotation LOGGING.ROTATION + the logging rotation to use + +Wayback Machine Enricher: + Submits the current URL to the Wayback Machine for archiving and returns either a job ID or the... + + --wayback_extractor_enricher.timeout TIMEOUT + seconds to wait for successful archive confirmation from wayback, if more than this passes the result contains the job_id so the status can later be checked manually. + --wayback_extractor_enricher.if_not_archived_within IF_NOT_ARCHIVED_WITHIN + only tell wayback to archive if no archive is available before the number of seconds specified, use None to ignore this option. For more information: + https://docs.google.com/document/d/1Nsv52MvSjbLb2PCpHlat0gkzw0EvtSgpKHu4mk0MnrA + --wayback_extractor_enricher.key KEY + wayback API key. to get credentials visit https://archive.org/account/s3.php + --wayback_extractor_enricher.secret SECRET + wayback API secret. to get credentials visit https://archive.org/account/s3.php + --wayback_extractor_enricher.proxy_http PROXY_HTTP + http proxy to use for wayback requests, eg http://proxy-user:password@proxy-ip:port + --wayback_extractor_enricher.proxy_https PROXY_HTTPS + https proxy to use for wayback requests, eg https://proxy-user:password@proxy-ip:port +``` + diff --git a/docs/source/installation/configurations.rst b/docs/source/installation/configurations.rst deleted file mode 100644 index e00dd94..0000000 --- a/docs/source/installation/configurations.rst +++ /dev/null @@ -1,41 +0,0 @@ - -Configurations -============== - - -```{toctree} -:hidden: - -configurations -``` - -This section of the documentation provides guidelines for configuring the tool. - -File Reference --------------- - - -Below is the content of the `example.orchestration.yaml` file: - -.. raw:: html - -
- View example.orchestration.yaml - -.. literalinclude:: ../../example.orchestration.yaml - :language: yaml - :caption: example.orchestration.yaml - -.. raw:: html - -
- - -Configs -------- - -This section of the documentation will show the custom configurations for the individual steps of the tool. - -.. include:: _auto/configs.rst - - diff --git a/docs/source/installation/installation.md b/docs/source/installation/installation.md index 99c6bf6..3744d0a 100644 --- a/docs/source/installation/installation.md +++ b/docs/source/installation/installation.md @@ -1,5 +1,10 @@ # Installing Auto Archiver +```{toctree} +:depth: 1 + +configurations.md +``` There are 3 main ways to use the auto-archiver: 1. Easiest: [via docker](#installing-with-docker) diff --git a/example.orchestration.yaml b/example.orchestration.yaml deleted file mode 100644 index f1eed2a..0000000 --- a/example.orchestration.yaml +++ /dev/null @@ -1,156 +0,0 @@ -steps: - # only 1 feeder allowed - feeder: gsheet_feeder # defaults to cli_feeder - archivers: # order matters, uncomment to activate - - bluesky_archiver - # - vk_archiver - # - telethon_archiver - # - telegram_archiver - # - twitter_archiver - # - twitter_api_archiver - # - instagram_api_archiver - # - instagram_tbot_archiver - # - instagram_archiver - # - tiktok_archiver - - youtubedl_archiver - # - wayback_archiver_enricher - # - wacz_archiver_enricher - enrichers: - - hash_enricher - # - meta_enricher - # - metadata_enricher - # - screenshot_enricher - # - pdq_hash_enricher - # - ssl_enricher - # - timestamping_enricher - # - whisper_enricher - # - thumbnail_enricher - # - wayback_archiver_enricher - # - wacz_archiver_enricher - # - pdq_hash_enricher # if you want to calculate hashes for thumbnails, include this after thumbnail_enricher - formatter: html_formatter # defaults to mute_formatter - storages: - - local_storage - # - s3_storage - # - gdrive_storage - databases: - - console_db - # - csv_db - # - gsheet_db - # - mongo_db - -configurations: - gsheet_feeder: - sheet: "your sheet name" - header: 1 - service_account: "secrets/service_account.json" - # allow_worksheets: "only parse this worksheet" - # block_worksheets: "blocked sheet 1,blocked sheet 2" - use_sheet_names_in_stored_paths: false - columns: - url: link - status: archive status - folder: destination folder - archive: archive location - date: archive date - thumbnail: thumbnail - timestamp: upload timestamp - title: upload title - text: textual content - screenshot: screenshot - hash: hash - pdq_hash: perceptual hashes - wacz: wacz - replaywebpage: replaywebpage - instagram_tbot_archiver: - api_id: "TELEGRAM_BOT_API_ID" - api_hash: "TELEGRAM_BOT_API_HASH" - # session_file: "secrets/anon" - telethon_archiver: - api_id: "TELEGRAM_BOT_API_ID" - api_hash: "TELEGRAM_BOT_API_HASH" - # session_file: "secrets/anon" - join_channels: false - channel_invites: # if you want to archive from private channels - - invite: https://t.me/+123456789 - id: 0000000001 - - invite: https://t.me/+123456788 - id: 0000000002 - - twitter_api_archiver: - # either bearer_token only - bearer_token: "TWITTER_BEARER_TOKEN" - # OR all of the below - # consumer_key: "" - # consumer_secret: "" - # access_token: "" - # access_secret: "" - instagram_archiver: - username: "INSTAGRAM_USERNAME" - password: "INSTAGRAM_PASSWORD" - # session_file: "secrets/instaloader.session" - - vk_archiver: - username: "or phone number" - password: "vk pass" - session_file: "secrets/vk_config.v2.json" - - youtubedl_archiver: - subtitles: true - # use one of the following two methods to authenticate in youtube - either provide a cookies file or use the cookies of the given browser - # for more information, see https://github.com/yt-dlp/yt-dlp/wiki/FAQ#how-do-i-pass-cookies-to-yt-dlp - # cookie_file: "secrets/youtube_cookies.txt" - # cookies_from_browser: firefox - # proxy: socks5://proxy-user:password@proxy-ip:port - - screenshot_enricher: - width: 1280 - height: 2300 - # to save as pdf, uncomment the following lines and adjust the print options - # save_to_pdf: true - # print_options: - # for all options see https://www.selenium.dev/selenium/docs/api/py/webdriver/selenium.webdriver.common.print_page_options.html - # background: true - # orientation: "portrait" - # scale: 1 - # page_width: 8.5in - # page_height: 11in - # margin_top: 0.4in - # margin_bottom: 0.4in - # margin_left: 0.4in - # margin_right: 0.4in - # page_ranges: "" - # shrink_to_fit: true - - wayback_archiver_enricher: - timeout: 10 - key: "wayback key" - secret: "wayback secret" - hash_enricher: - algorithm: "SHA3-512" # can also be SHA-256 - wacz_archiver_enricher: - profile: secrets/profile.tar.gz - local_storage: - save_to: "./local_archive" - save_absolute: true - filename_generator: static - path_generator: flat - s3_storage: - bucket: your-bucket-name - region: reg1 - key: S3_KEY - secret: S3_SECRET - endpoint_url: "https://{region}.digitaloceanspaces.com" - cdn_url: "https://{bucket}.{region}.cdn.digitaloceanspaces.com/{key}" - # if private:true S3 urls will not be readable online - private: false - # with 'random' you can generate a random UUID for the URL instead of a predictable path, useful to still have public but unlisted files, alternative is 'default' or not omitted from config - key_path: random - gdrive_storage: - path_generator: url - filename_generator: random - root_folder_id: folder_id_from_url - oauth_token: secrets/gd-token.json # needs to be generated with scripts/create_update_gdrive_oauth_token.py - service_account: "secrets/service_account.json" - csv_db: - csv_file: "./local_archive/db.csv" diff --git a/src/auto_archiver/core/config.py b/src/auto_archiver/core/config.py index 9bb080f..c2d38ee 100644 --- a/src/auto_archiver/core/config.py +++ b/src/auto_archiver/core/config.py @@ -48,6 +48,7 @@ authentication: {} logging: level: INFO + """) # note: 'logging' is explicitly added above in order to better format the config file diff --git a/tests/data/test_modules/example_module/__manifest__.py b/tests/data/test_modules/example_module/__manifest__.py index f2ebdbf..e3a26bb 100644 --- a/tests/data/test_modules/example_module/__manifest__.py +++ b/tests/data/test_modules/example_module/__manifest__.py @@ -1,11 +1,29 @@ { + # Display Name of your module "name": "Example Module", + # The author of your module (optional) + "author": "John Doe", + # Optional version number, for your own versioning purposes + "version": 2.0, + # The type of the module, must be one (or more) of the built in module types "type": ["extractor", "feeder", "formatter", "storage", "enricher", "database"], + # a boolean indicating whether or not a module requires additional user setup before it can be used + # for example: adding API keys, installing additional software etc. "requires_setup": False, - "dependencies": {"python": ["loguru"] - }, + # a dictionary of dependencies for this module, that must be installed before the module is loaded. + # Can be python dependencies (external packages, or other auto-archiver modules), or you can + # provide external bin dependencies (e.g. ffmpeg, docker etc.) + "dependencies": { + "python": ["loguru"], + "bin": ["bash"], + }, + # configurations that this module takes. These are argparse-compliant dicationaries, that are + # used to create command line arguments when the programme is run. + # The full name of the config option will become: `module_name.config_name` "configs": { "csv_file": {"default": "db.csv", "help": "CSV file name"}, "required_field": {"required": True, "help": "required field in the CSV file"}, }, + # A description of the module, used for documentation + "description": "This is an example module", } \ No newline at end of file From 895c843f042ee7b4a753b7cf7297cf53f3d26f58 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Tue, 11 Feb 2025 14:06:53 +0000 Subject: [PATCH 006/160] Add a cheat sheet for configs and better folder structure for core modules --- docs/scripts/scripts.py | 24 +++++++++++++------ docs/source/core_modules.md | 1 + docs/source/installation/config_cheatsheet.md | 6 +++++ docs/source/installation/configurations.md | 2 +- docs/source/installation/installation.md | 1 + docs/source/modules/database.md | 6 +++++ docs/source/modules/enricher.md | 6 +++++ docs/source/modules/extractor.md | 6 +++++ docs/source/modules/feeder.md | 6 +++++ docs/source/modules/formatter.md | 6 +++++ 10 files changed, 56 insertions(+), 8 deletions(-) create mode 100644 docs/source/installation/config_cheatsheet.md diff --git a/docs/scripts/scripts.py b/docs/scripts/scripts.py index 34acd48..3b8b17b 100644 --- a/docs/scripts/scripts.py +++ b/docs/scripts/scripts.py @@ -15,12 +15,17 @@ type_color = { 'formatter': "[formatter](/core_modules.md#formatter-modules)", } +TABLE_HEADER = ("Option", "Description", "Default", "Type") def generate_module_docs(): SAVE_FOLDER.mkdir(exist_ok=True) modules_by_type = {} - for module in available_modules(with_manifest=True): + header_row = "| " + " | ".join(TABLE_HEADER) + "|\n" + "| --- " * len(TABLE_HEADER) + "|\n" + configs_cheatsheet = "\n## Configuration Options\n" + configs_cheatsheet += header_row + + for module in sorted(available_modules(with_manifest=True), key=lambda x: (x.requires_setup, x.name)): # generate the markdown file from the __manifest__.py file. manifest = module.manifest @@ -39,8 +44,7 @@ def generate_module_docs(): """ if manifest['configs']: readme_str += "\n## Configuration Options\n" - readme_str += "| Option | Description | Default | Type |\n" - readme_str += "| --- | --- | --- | --- |\n" + readme_str += header_row for key, value in manifest['configs'].items(): type = value.get('type', 'string') if type == 'auto_archiver.utils.json_loader': @@ -51,24 +55,30 @@ def generate_module_docs(): help = "**Required**. " if value.get('required', False) else "Optional. " help += value.get('help', '') readme_str += f"| `{module.name}.{key}` | {help} | {value.get('default', '')} | {type} |\n" + configs_cheatsheet += f"| `{module.name}.{key}` | {help} | {value.get('default', '')} | {type} |\n" # make type folder if it doesn't exist - - with open(SAVE_FOLDER / f"{module.name}.md", "w") as f: + # create the module.type folder, use the first type just for where to store the file + type_folder = SAVE_FOLDER / module.type[0] + type_folder.mkdir(exist_ok=True) + with open(type_folder / f"{module.name}.md", "w") as f: print("writing", SAVE_FOLDER) f.write(readme_str) - generate_index(modules_by_type) + with open(SAVE_FOLDER / "configs_cheatsheet.md", "w") as f: + f.write(configs_cheatsheet) + + def generate_index(modules_by_type): readme_str = "" for type in BaseModule.MODULE_TYPES: modules = modules_by_type.get(type, []) module_str = f"## {type.capitalize()} Modules\n" for module in modules: - module_str += f"\n[{module.manifest['name']}](/modules/autogen/{module.name}.md)\n" + module_str += f"\n[{module.manifest['name']}](/modules/autogen/{module.type[0]}/{module.name}.md)\n" with open(SAVE_FOLDER / f"{type}.md", "w") as f: print("writing", SAVE_FOLDER / f"{type}.md") f.write(module_str) diff --git a/docs/source/core_modules.md b/docs/source/core_modules.md index bb2b3f1..4ee3bfc 100644 --- a/docs/source/core_modules.md +++ b/docs/source/core_modules.md @@ -18,6 +18,7 @@ These pages describe the core modules that come with `auto-archiver` and provide :caption: Core Modules :hidden: +modules/config_cheatsheet modules/feeder modules/extractor modules/enricher diff --git a/docs/source/installation/config_cheatsheet.md b/docs/source/installation/config_cheatsheet.md new file mode 100644 index 0000000..820e30c --- /dev/null +++ b/docs/source/installation/config_cheatsheet.md @@ -0,0 +1,6 @@ +# Configuration Cheat Sheet + +Below is a list of all configurations for the core modules in Auto Archiver + +```{include} ../modules/autogen/configs_cheatsheet.md +``` diff --git a/docs/source/installation/configurations.md b/docs/source/installation/configurations.md index ef82e2a..6dab1ab 100644 --- a/docs/source/installation/configurations.md +++ b/docs/source/installation/configurations.md @@ -1,5 +1,5 @@ -# Configuring +# Configuration ```{toctree} diff --git a/docs/source/installation/installation.md b/docs/source/installation/installation.md index 3744d0a..8abff21 100644 --- a/docs/source/installation/installation.md +++ b/docs/source/installation/installation.md @@ -4,6 +4,7 @@ :depth: 1 configurations.md +config_cheatsheet.md ``` There are 3 main ways to use the auto-archiver: diff --git a/docs/source/modules/database.md b/docs/source/modules/database.md index 9719a62..3346455 100644 --- a/docs/source/modules/database.md +++ b/docs/source/modules/database.md @@ -6,3 +6,9 @@ The default (enabled) databases are the CSV Database and the Console Database. ```{include} autogen/database.md ``` + +```{toctree} +:depth: 1 +:glob: +autogen/database/* +``` \ No newline at end of file diff --git a/docs/source/modules/enricher.md b/docs/source/modules/enricher.md index b27958e..b480cc3 100644 --- a/docs/source/modules/enricher.md +++ b/docs/source/modules/enricher.md @@ -4,4 +4,10 @@ Enricher modules are used to add additional information to the items that have ```{include} autogen/enricher.md +``` + +```{toctree} +:depth: 1 +:glob: +autogen/enricher/* ``` \ No newline at end of file diff --git a/docs/source/modules/extractor.md b/docs/source/modules/extractor.md index ddd09b9..2a6a181 100644 --- a/docs/source/modules/extractor.md +++ b/docs/source/modules/extractor.md @@ -8,4 +8,10 @@ Extractors that are able to extract content from a wide range of websites includ 3. WACZ Extractor: runs a web browser to 'browse' the URL and save a copy of the page in WACZ format. ```{include} autogen/extractor.md +``` + +```{toctree} +:depth: 1 +:glob: +autogen/extractor/* ``` \ No newline at end of file diff --git a/docs/source/modules/feeder.md b/docs/source/modules/feeder.md index 5ba77a4..55fbde8 100644 --- a/docs/source/modules/feeder.md +++ b/docs/source/modules/feeder.md @@ -5,4 +5,10 @@ Feeder modules are used to feed URLs into the `auto-archiver` for processing. Fe The default feeder is the command line feeder, which allows you to input URLs directly into the `auto-archiver` from the command line. ```{include} autogen/feeder.md +``` + +```{toctree} +:depth: 1 +:glob: +autogen/feeder/* ``` \ No newline at end of file diff --git a/docs/source/modules/formatter.md b/docs/source/modules/formatter.md index 08bc5fb..607a52f 100644 --- a/docs/source/modules/formatter.md +++ b/docs/source/modules/formatter.md @@ -3,4 +3,10 @@ Formatter modules are used to format the data extracted from a URL into a specific format. Currently the most widely-used formatter is the HTML formatter, which formats the data into an easily viewable HTML page. ```{include} autogen/formatter.md +``` + +```{toctree} +:depth: 1 +:glob: +autogen/formatter/* ``` \ No newline at end of file From 62154ddfef94bd2c287dd3a4c49c48788fb225c8 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Tue, 11 Feb 2025 14:37:29 +0000 Subject: [PATCH 007/160] Further tweaks and fixes --- docs/_templates/autoapi/index.rst | 12 + docs/scripts/scripts.py | 4 +- docs/source/_auto/configs.rst | 742 ------------------------------ docs/source/conf.py | 7 +- docs/source/modules/database.md | 1 + docs/source/modules/enricher.md | 1 + docs/source/modules/extractor.md | 1 + docs/source/modules/feeder.md | 1 + docs/source/modules/formatter.md | 1 + docs/source/modules/storage.md | 7 + 10 files changed, 30 insertions(+), 747 deletions(-) delete mode 100644 docs/source/_auto/configs.rst diff --git a/docs/_templates/autoapi/index.rst b/docs/_templates/autoapi/index.rst index 7d89fdd..a6f712b 100644 --- a/docs/_templates/autoapi/index.rst +++ b/docs/_templates/autoapi/index.rst @@ -32,3 +32,15 @@ Util Functions {% endfor %} +Core Modules +------------ + +.. toctree:: + :titlesonly: + + {% for page in pages|selectattr("is_top_level_object") %} + {% if page.name != 'core' and page.name != 'utils' %} + {{ page.include_path }} + {% endif %} + {% endfor %} + diff --git a/docs/scripts/scripts.py b/docs/scripts/scripts.py index 3b8b17b..7439611 100644 --- a/docs/scripts/scripts.py +++ b/docs/scripts/scripts.py @@ -58,8 +58,8 @@ def generate_module_docs(): configs_cheatsheet += f"| `{module.name}.{key}` | {help} | {value.get('default', '')} | {type} |\n" - # make type folder if it doesn't exist - + # add a link to the autodoc refs + readme_str += f"\n[API Reference](../../../autoapi/{module.name}/index)\n" # create the module.type folder, use the first type just for where to store the file type_folder = SAVE_FOLDER / module.type[0] type_folder.mkdir(exist_ok=True) diff --git a/docs/source/_auto/configs.rst b/docs/source/_auto/configs.rst deleted file mode 100644 index f6e81f0..0000000 --- a/docs/source/_auto/configs.rst +++ /dev/null @@ -1,742 +0,0 @@ - -Configs -------- - -This section documents all configuration options available for various components. - -InstagramAPIArchiver --------------------- - -The following table lists all configuration options for this component: - -.. list-table:: Configuration Options - :header-rows: 1 - :widths: 25 20 55 - - * - **Key** - - **Default** - - **Description** - * - access_token - - None - - a valid instagrapi-api token - * - api_endpoint - - None - - API endpoint to use - * - full_profile - - False - - if true, will download all posts, tagged posts, stories, and highlights for a profile, if false, will only download the profile pic and information. - * - full_profile_max_posts - - 0 - - Use to limit the number of posts to download when full_profile is true. 0 means no limit. limit is applied softly since posts are fetched in batch, once to: posts, tagged posts, and highlights - * - minimize_json_output - - True - - if true, will remove empty values from the json output - -InstagramArchiver ------------------ - -The following table lists all configuration options for this component: - -.. list-table:: Configuration Options - :header-rows: 1 - :widths: 25 20 55 - - * - **Key** - - **Default** - - **Description** - * - username - - None - - a valid Instagram username - * - password - - None - - the corresponding Instagram account password - * - download_folder - - instaloader - - name of a folder to temporarily download content to - * - session_file - - secrets/instaloader.session - - path to the instagram session which saves session credentials - -InstagramTbotArchiver ---------------------- - -The following table lists all configuration options for this component: - -.. list-table:: Configuration Options - :header-rows: 1 - :widths: 25 20 55 - - * - **Key** - - **Default** - - **Description** - * - api_id - - None - - telegram API_ID value, go to https://my.telegram.org/apps - * - api_hash - - None - - telegram API_HASH value, go to https://my.telegram.org/apps - * - session_file - - secrets/anon-insta - - optional, records the telegram login session for future usage, '.session' will be appended to the provided value. - * - timeout - - 45 - - timeout to fetch the instagram content in seconds. - -TelethonArchiver ----------------- - -The following table lists all configuration options for this component: - -.. list-table:: Configuration Options - :header-rows: 1 - :widths: 25 20 55 - - * - **Key** - - **Default** - - **Description** - * - api_id - - None - - telegram API_ID value, go to https://my.telegram.org/apps - * - api_hash - - None - - telegram API_HASH value, go to https://my.telegram.org/apps - * - bot_token - - None - - optional, but allows access to more content such as large videos, talk to @botfather - * - session_file - - secrets/anon - - optional, records the telegram login session for future usage, '.session' will be appended to the provided value. - * - join_channels - - True - - disables the initial setup with channel_invites config, useful if you have a lot and get stuck - * - channel_invites - - {} - - (JSON string) private channel invite links (format: t.me/joinchat/HASH OR t.me/+HASH) and (optional but important to avoid hanging for minutes on startup) channel id (format: CHANNEL_ID taken from a post url like https://t.me/c/CHANNEL_ID/1), the telegram account will join any new channels on setup - -TwitterApiArchiver ------------------- - -The following table lists all configuration options for this component: - -.. list-table:: Configuration Options - :header-rows: 1 - :widths: 25 20 55 - - * - **Key** - - **Default** - - **Description** - * - bearer_token - - None - - [deprecated: see bearer_tokens] twitter API bearer_token which is enough for archiving, if not provided you will need consumer_key, consumer_secret, access_token, access_secret - * - bearer_tokens - - [] - - a list of twitter API bearer_token which is enough for archiving, if not provided you will need consumer_key, consumer_secret, access_token, access_secret, if provided you can still add those for better rate limits. CSV of bearer tokens if provided via the command line - * - consumer_key - - None - - twitter API consumer_key - * - consumer_secret - - None - - twitter API consumer_secret - * - access_token - - None - - twitter API access_token - * - access_secret - - None - - twitter API access_secret - -VkArchiver ----------- - -The following table lists all configuration options for this component: - -.. list-table:: Configuration Options - :header-rows: 1 - :widths: 25 20 55 - - * - **Key** - - **Default** - - **Description** - * - username - - None - - valid VKontakte username - * - password - - None - - valid VKontakte password - * - session_file - - secrets/vk_config.v2.json - - valid VKontakte password - -YoutubeDLArchiver ------------------ - -The following table lists all configuration options for this component: - -.. list-table:: Configuration Options - :header-rows: 1 - :widths: 25 20 55 - - * - **Key** - - **Default** - - **Description** - * - facebook_cookie - - None - - optional facebook cookie to have more access to content, from browser, looks like 'cookie: datr= xxxx' - * - subtitles - - True - - download subtitles if available - * - comments - - False - - download all comments if available, may lead to large metadata - * - livestreams - - False - - if set, will download live streams, otherwise will skip them; see --max-filesize for more control - * - live_from_start - - False - - if set, will download live streams from their earliest available moment, otherwise starts now. - * - proxy - - - - http/socks (https seems to not work atm) proxy to use for the webdriver, eg https://proxy- user:password@proxy-ip:port - * - end_means_success - - True - - if True, any archived content will mean a 'success', if False this archiver will not return a 'success' stage; this is useful for cases when the yt-dlp will archive a video but ignore other types of content like images or text only pages that the subsequent archivers can retrieve. - * - allow_playlist - - False - - If True will also download playlists, set to False if the expectation is to download a single video. - * - max_downloads - - inf - - Use to limit the number of videos to download when a channel or long page is being extracted. 'inf' means no limit. - * - cookies_from_browser - - None - - optional browser for ytdl to extract cookies from, can be one of: brave, chrome, chromium, edge, firefox, opera, safari, vivaldi, whale - * - cookie_file - - None - - optional cookie file to use for Youtube, see instructions here on how to export from your browser: https://github.com/yt-dlp/yt- dlp/wiki/FAQ#how-do-i-pass-cookies-to-yt-dlp - -AAApiDb -------- - -The following table lists all configuration options for this component: - -.. list-table:: Configuration Options - :header-rows: 1 - :widths: 25 20 55 - - * - **Key** - - **Default** - - **Description** - * - api_endpoint - - None - - API endpoint where calls are made to - * - api_token - - None - - API Bearer token. - * - public - - False - - whether the URL should be publicly available via the API - * - author_id - - None - - which email to assign as author - * - group_id - - None - - which group of users have access to the archive in case public=false as author - * - allow_rearchive - - True - - if False then the API database will be queried prior to any archiving operations and stop if the link has already been archived - * - store_results - - True - - when set, will send the results to the API database. - * - tags - - [] - - what tags to add to the archived URL - -AtlosDb -------- - -The following table lists all configuration options for this component: - -.. list-table:: Configuration Options - :header-rows: 1 - :widths: 25 20 55 - - * - **Key** - - **Default** - - **Description** - * - api_token - - None - - An Atlos API token. For more information, see https://docs.atlos.org/technical/api/ - * - atlos_url - - https://platform.atlos.org - - The URL of your Atlos instance (e.g., https://platform.atlos.org), without a trailing slash. - -CSVDb ------ - -The following table lists all configuration options for this component: - -.. list-table:: Configuration Options - :header-rows: 1 - :widths: 25 20 55 - - * - **Key** - - **Default** - - **Description** - * - csv_file - - db.csv - - CSV file name - -HashEnricher ------------- - -The following table lists all configuration options for this component: - -.. list-table:: Configuration Options - :header-rows: 1 - :widths: 25 20 55 - - * - **Key** - - **Default** - - **Description** - * - algorithm - - SHA-256 - - hash algorithm to use - * - chunksize - - 16000000 - - number of bytes to use when reading files in chunks (if this value is too large you will run out of RAM), default is 16MB - -ScreenshotEnricher ------------------- - -The following table lists all configuration options for this component: - -.. list-table:: Configuration Options - :header-rows: 1 - :widths: 25 20 55 - - * - **Key** - - **Default** - - **Description** - * - width - - 1280 - - width of the screenshots - * - height - - 720 - - height of the screenshots - * - timeout - - 60 - - timeout for taking the screenshot - * - sleep_before_screenshot - - 4 - - seconds to wait for the pages to load before taking screenshot - * - http_proxy - - - - http proxy to use for the webdriver, eg http://proxy-user:password@proxy-ip:port - * - save_to_pdf - - False - - save the page as pdf along with the screenshot. PDF saving options can be adjusted with the 'print_options' parameter - * - print_options - - {} - - options to pass to the pdf printer - -SSLEnricher ------------ - -The following table lists all configuration options for this component: - -.. list-table:: Configuration Options - :header-rows: 1 - :widths: 25 20 55 - - * - **Key** - - **Default** - - **Description** - * - skip_when_nothing_archived - - True - - if true, will skip enriching when no media is archived - -ThumbnailEnricher ------------------ - -The following table lists all configuration options for this component: - -.. list-table:: Configuration Options - :header-rows: 1 - :widths: 25 20 55 - - * - **Key** - - **Default** - - **Description** - * - thumbnails_per_minute - - 60 - - how many thumbnails to generate per minute of video, can be limited by max_thumbnails - * - max_thumbnails - - 16 - - limit the number of thumbnails to generate per video, 0 means no limit - -TimestampingEnricher --------------------- - -The following table lists all configuration options for this component: - -.. list-table:: Configuration Options - :header-rows: 1 - :widths: 25 20 55 - - * - **Key** - - **Default** - - **Description** - * - tsa_urls - - ['http://timestamp.digicert.com', 'http://timestamp.identrust.com', 'http://timestamp.globalsign.com/tsa/r6advanced1', 'http://tss.accv.es:8318/tsa'] - - List of RFC3161 Time Stamp Authorities to use, separate with commas if passed via the command line. - -WaczArchiverEnricher --------------------- - -The following table lists all configuration options for this component: - -.. list-table:: Configuration Options - :header-rows: 1 - :widths: 25 20 55 - - * - **Key** - - **Default** - - **Description** - * - profile - - None - - browsertrix-profile (for profile generation see https://github.com/webrecorder/browsertrix- crawler#creating-and-using-browser-profiles). - * - docker_commands - - None - - if a custom docker invocation is needed - * - timeout - - 120 - - timeout for WACZ generation in seconds - * - extract_media - - False - - If enabled all the images/videos/audio present in the WACZ archive will be extracted into separate Media and appear in the html report. The .wacz file will be kept untouched. - * - extract_screenshot - - True - - If enabled the screenshot captured by browsertrix will be extracted into separate Media and appear in the html report. The .wacz file will be kept untouched. - * - socks_proxy_host - - None - - SOCKS proxy host for browsertrix-crawler, use in combination with socks_proxy_port. eg: user:password@host - * - socks_proxy_port - - None - - SOCKS proxy port for browsertrix-crawler, use in combination with socks_proxy_host. eg 1234 - * - proxy_server - - None - - SOCKS server proxy URL, in development - -WaybackArchiverEnricher ------------------------ - -The following table lists all configuration options for this component: - -.. list-table:: Configuration Options - :header-rows: 1 - :widths: 25 20 55 - - * - **Key** - - **Default** - - **Description** - * - timeout - - 15 - - seconds to wait for successful archive confirmation from wayback, if more than this passes the result contains the job_id so the status can later be checked manually. - * - if_not_archived_within - - None - - only tell wayback to archive if no archive is available before the number of seconds specified, use None to ignore this option. For more information: https://docs.google.com/document/d/1N sv52MvSjbLb2PCpHlat0gkzw0EvtSgpKHu4mk0MnrA - * - key - - None - - wayback API key. to get credentials visit https://archive.org/account/s3.php - * - secret - - None - - wayback API secret. to get credentials visit https://archive.org/account/s3.php - * - proxy_http - - None - - http proxy to use for wayback requests, eg http://proxy-user:password@proxy-ip:port - * - proxy_https - - None - - https proxy to use for wayback requests, eg https://proxy-user:password@proxy-ip:port - -WhisperEnricher ---------------- - -The following table lists all configuration options for this component: - -.. list-table:: Configuration Options - :header-rows: 1 - :widths: 25 20 55 - - * - **Key** - - **Default** - - **Description** - * - api_endpoint - - None - - WhisperApi api endpoint, eg: https://whisperbox- api.com/api/v1, a deployment of https://github.com/bellingcat/whisperbox- transcribe. - * - api_key - - None - - WhisperApi api key for authentication - * - include_srt - - False - - Whether to include a subtitle SRT (SubRip Subtitle file) for the video (can be used in video players). - * - timeout - - 90 - - How many seconds to wait at most for a successful job completion. - * - action - - translate - - which Whisper operation to execute - -AtlosFeeder ------------ - -The following table lists all configuration options for this component: - -.. list-table:: Configuration Options - :header-rows: 1 - :widths: 25 20 55 - - * - **Key** - - **Default** - - **Description** - * - api_token - - None - - An Atlos API token. For more information, see https://docs.atlos.org/technical/api/ - * - atlos_url - - https://platform.atlos.org - - The URL of your Atlos instance (e.g., https://platform.atlos.org), without a trailing slash. - -CLIFeeder ---------- - -The following table lists all configuration options for this component: - -.. list-table:: Configuration Options - :header-rows: 1 - :widths: 25 20 55 - - * - **Key** - - **Default** - - **Description** - * - urls - - None - - URL(s) to archive, either a single URL or a list of urls, should not come from config.yaml - -GsheetsFeeder -------------- - -The following table lists all configuration options for this component: - -.. list-table:: Configuration Options - :header-rows: 1 - :widths: 25 20 55 - - * - **Key** - - **Default** - - **Description** - * - sheet - - None - - name of the sheet to archive - * - sheet_id - - None - - (alternative to sheet name) the id of the sheet to archive - * - header - - 1 - - index of the header row (starts at 1) - * - service_account - - secrets/service_account.json - - service account JSON file path - * - columns - - {'url': 'link', 'status': 'archive status', 'folder': 'destination folder', 'archive': 'archive location', 'date': 'archive date', 'thumbnail': 'thumbnail', 'timestamp': 'upload timestamp', 'title': 'upload title', 'text': 'text content', 'screenshot': 'screenshot', 'hash': 'hash', 'pdq_hash': 'perceptual hashes', 'wacz': 'wacz', 'replaywebpage': 'replaywebpage'} - - names of columns in the google sheet (stringified JSON object) - * - allow_worksheets - - set() - - (CSV) only worksheets whose name is included in allow are included (overrides worksheet_block), leave empty so all are allowed - * - block_worksheets - - set() - - (CSV) explicitly block some worksheets from being processed - * - use_sheet_names_in_stored_paths - - True - - if True the stored files path will include 'workbook_name/worksheet_name/...' - -HtmlFormatter -------------- - -The following table lists all configuration options for this component: - -.. list-table:: Configuration Options - :header-rows: 1 - :widths: 25 20 55 - - * - **Key** - - **Default** - - **Description** - * - detect_thumbnails - - True - - if true will group by thumbnails generated by thumbnail enricher by id 'thumbnail_00' - -AtlosStorage ------------- - -The following table lists all configuration options for this component: - -.. list-table:: Configuration Options - :header-rows: 1 - :widths: 25 20 55 - - * - **Key** - - **Default** - - **Description** - * - path_generator - - url - - how to store the file in terms of directory structure: 'flat' sets to root; 'url' creates a directory based on the provided URL; 'random' creates a random directory. - * - filename_generator - - random - - how to name stored files: 'random' creates a random string; 'static' uses a replicable strategy such as a hash. - * - api_token - - None - - An Atlos API token. For more information, see https://docs.atlos.org/technical/api/ - * - atlos_url - - https://platform.atlos.org - - The URL of your Atlos instance (e.g., https://platform.atlos.org), without a trailing slash. - -GDriveStorage -------------- - -The following table lists all configuration options for this component: - -.. list-table:: Configuration Options - :header-rows: 1 - :widths: 25 20 55 - - * - **Key** - - **Default** - - **Description** - * - path_generator - - url - - how to store the file in terms of directory structure: 'flat' sets to root; 'url' creates a directory based on the provided URL; 'random' creates a random directory. - * - filename_generator - - random - - how to name stored files: 'random' creates a random string; 'static' uses a replicable strategy such as a hash. - * - root_folder_id - - None - - root google drive folder ID to use as storage, found in URL: 'https://drive.google.com/drive/folders/FOLDER_ID' - * - oauth_token - - None - - JSON filename with Google Drive OAuth token: check auto-archiver repository scripts folder for create_update_gdrive_oauth_token.py. NOTE: storage used will count towards owner of GDrive folder, therefore it is best to use oauth_token_filename over service_account. - * - service_account - - secrets/service_account.json - - service account JSON file path, same as used for Google Sheets. NOTE: storage used will count towards the developer account. - -LocalStorage ------------- - -The following table lists all configuration options for this component: - -.. list-table:: Configuration Options - :header-rows: 1 - :widths: 25 20 55 - - * - **Key** - - **Default** - - **Description** - * - path_generator - - url - - how to store the file in terms of directory structure: 'flat' sets to root; 'url' creates a directory based on the provided URL; 'random' creates a random directory. - * - filename_generator - - random - - how to name stored files: 'random' creates a random string; 'static' uses a replicable strategy such as a hash. - * - save_to - - ./archived - - folder where to save archived content - * - save_absolute - - False - - whether the path to the stored file is absolute or relative in the output result inc. formatters (WARN: leaks the file structure) - -S3Storage ---------- - -The following table lists all configuration options for this component: - -.. list-table:: Configuration Options - :header-rows: 1 - :widths: 25 20 55 - - * - **Key** - - **Default** - - **Description** - * - path_generator - - url - - how to store the file in terms of directory structure: 'flat' sets to root; 'url' creates a directory based on the provided URL; 'random' creates a random directory. - * - filename_generator - - random - - how to name stored files: 'random' creates a random string; 'static' uses a replicable strategy such as a hash. - * - bucket - - None - - S3 bucket name - * - region - - None - - S3 region name - * - key - - None - - S3 API key - * - secret - - None - - S3 API secret - * - random_no_duplicate - - False - - if set, it will override `path_generator`, `filename_generator` and `folder`. It will check if the file already exists and if so it will not upload it again. Creates a new root folder path `no-dups/` - * - endpoint_url - - https://{region}.digitaloceanspaces.com - - S3 bucket endpoint, {region} are inserted at runtime - * - cdn_url - - https://{bucket}.{region}.cdn.digitaloceanspaces.com/{key} - - S3 CDN url, {bucket}, {region} and {key} are inserted at runtime - * - private - - False - - if true S3 files will not be readable online - -Storage -------- - -The following table lists all configuration options for this component: - -.. list-table:: Configuration Options - :header-rows: 1 - :widths: 25 20 55 - - * - **Key** - - **Default** - - **Description** - * - path_generator - - url - - how to store the file in terms of directory structure: 'flat' sets to root; 'url' creates a directory based on the provided URL; 'random' creates a random directory. - * - filename_generator - - random - - how to name stored files: 'random' creates a random string; 'static' uses a replicable strategy such as a hash. - -Gsheets -------- - -The following table lists all configuration options for this component: - -.. list-table:: Configuration Options - :header-rows: 1 - :widths: 25 20 55 - - * - **Key** - - **Default** - - **Description** - * - sheet - - None - - name of the sheet to archive - * - sheet_id - - None - - (alternative to sheet name) the id of the sheet to archive - * - header - - 1 - - index of the header row (starts at 1) - * - service_account - - secrets/service_account.json - - service account JSON file path - * - columns - - {'url': 'link', 'status': 'archive status', 'folder': 'destination folder', 'archive': 'archive location', 'date': 'archive date', 'thumbnail': 'thumbnail', 'timestamp': 'upload timestamp', 'title': 'upload title', 'text': 'text content', 'screenshot': 'screenshot', 'hash': 'hash', 'pdq_hash': 'perceptual hashes', 'wacz': 'wacz', 'replaywebpage': 'replaywebpage'} - - names of columns in the google sheet (stringified JSON object) - diff --git a/docs/source/conf.py b/docs/source/conf.py index a749dfd..ab88bdf 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -36,10 +36,12 @@ exclude_patterns = [] # -- AutoAPI Configuration --------------------------------------------------- autoapi_type = 'python' -autoapi_dirs = ["../../src/auto_archiver/core/", "../../src/auto_archiver/utils/", "../../src/auto_archiver/modules/"] +autoapi_dirs = ["../../src/auto_archiver/core/", "../../src/auto_archiver/utils/"] +# get all the modules and add them to the autoapi_dirs +autoapi_dirs.extend([f"../../src/auto_archiver/modules/{m}" for m in os.listdir("../../src/auto_archiver/modules")]) autodoc_typehints = "signature" # Include type hints in the signature autoapi_ignore = ["*/version.py", ] # Ignore specific modules -autoapi_keep_files = False # Option to retain intermediate JSON files for debugging +autoapi_keep_files = True # Option to retain intermediate JSON files for debugging autoapi_add_toctree_entry = True # Include API docs in the TOC autoapi_python_use_implicit_namespaces = True autoapi_template_dir = "../_templates/autoapi" @@ -47,7 +49,6 @@ autoapi_options = [ "members", "undoc-members", "show-inheritance", - "show-module-summary", "imported-members", ] diff --git a/docs/source/modules/database.md b/docs/source/modules/database.md index 3346455..9acecda 100644 --- a/docs/source/modules/database.md +++ b/docs/source/modules/database.md @@ -9,6 +9,7 @@ The default (enabled) databases are the CSV Database and the Console Database. ```{toctree} :depth: 1 +:hidden: :glob: autogen/database/* ``` \ No newline at end of file diff --git a/docs/source/modules/enricher.md b/docs/source/modules/enricher.md index b480cc3..30568c3 100644 --- a/docs/source/modules/enricher.md +++ b/docs/source/modules/enricher.md @@ -8,6 +8,7 @@ Enricher modules are used to add additional information to the items that have ```{toctree} :depth: 1 +:hidden: :glob: autogen/enricher/* ``` \ No newline at end of file diff --git a/docs/source/modules/extractor.md b/docs/source/modules/extractor.md index 2a6a181..7f218fb 100644 --- a/docs/source/modules/extractor.md +++ b/docs/source/modules/extractor.md @@ -12,6 +12,7 @@ Extractors that are able to extract content from a wide range of websites includ ```{toctree} :depth: 1 +:hidden: :glob: autogen/extractor/* ``` \ No newline at end of file diff --git a/docs/source/modules/feeder.md b/docs/source/modules/feeder.md index 55fbde8..9b6c403 100644 --- a/docs/source/modules/feeder.md +++ b/docs/source/modules/feeder.md @@ -10,5 +10,6 @@ The default feeder is the command line feeder, which allows you to input URLs di ```{toctree} :depth: 1 :glob: +:hidden: autogen/feeder/* ``` \ No newline at end of file diff --git a/docs/source/modules/formatter.md b/docs/source/modules/formatter.md index 607a52f..b7ae77e 100644 --- a/docs/source/modules/formatter.md +++ b/docs/source/modules/formatter.md @@ -7,6 +7,7 @@ Formatter modules are used to format the data extracted from a URL into a specif ```{toctree} :depth: 1 +:hidden: :glob: autogen/formatter/* ``` \ No newline at end of file diff --git a/docs/source/modules/storage.md b/docs/source/modules/storage.md index 998409a..427213c 100644 --- a/docs/source/modules/storage.md +++ b/docs/source/modules/storage.md @@ -5,4 +5,11 @@ Storage modules are used to store the data extracted from a URL in a persistent The default is to store the files downloaded (e.g. images, videos) in a local directory. ```{include} autogen/storage.md +``` + +```{toctree} +:depth: 1 +:hidden: +:glob: +autogen/storage/* ``` \ No newline at end of file From 47d1dc9d476db7f452d5aada273c1e90b9e08ac4 Mon Sep 17 00:00:00 2001 From: msramalho <19508417+msramalho@users.noreply.github.com> Date: Tue, 11 Feb 2025 15:01:37 +0000 Subject: [PATCH 008/160] typing warnings fixed --- .../modules/generic_extractor/generic_extractor.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/auto_archiver/modules/generic_extractor/generic_extractor.py b/src/auto_archiver/modules/generic_extractor/generic_extractor.py index 86e978f..afbe91b 100644 --- a/src/auto_archiver/modules/generic_extractor/generic_extractor.py +++ b/src/auto_archiver/modules/generic_extractor/generic_extractor.py @@ -1,6 +1,6 @@ import datetime, os, yt_dlp, pysubs2 import importlib -from typing import Type +from typing import Generator, Type from yt_dlp.extractor.common import InfoExtractor from loguru import logger @@ -11,7 +11,7 @@ from auto_archiver.core import Metadata, Media class GenericExtractor(Extractor): _dropins = {} - def suitable_extractors(self, url: str) -> list[str]: + def suitable_extractors(self, url: str) -> Generator[str, None, None]: """ Returns a list of valid extractors for the given URL""" for info_extractor in yt_dlp.YoutubeDL()._ies.values(): @@ -116,7 +116,7 @@ class GenericExtractor(Extractor): def get_metadata_for_post(self, info_extractor: Type[InfoExtractor], url: str, ydl: yt_dlp.YoutubeDL) -> Metadata: """ - Calls into the ytdlp InfoExtract subclass to use the prive _extract_post method to get the post metadata. + Calls into the ytdlp InfoExtract subclass to use the private _extract_post method to get the post metadata. """ ie_instance = info_extractor(downloader=ydl) From 5478ed3860396b9e4b1862495cd17444399e01b7 Mon Sep 17 00:00:00 2001 From: msramalho <19508417+msramalho@users.noreply.github.com> Date: Tue, 11 Feb 2025 15:02:00 +0000 Subject: [PATCH 009/160] bsky fix media fetching --- src/auto_archiver/modules/generic_extractor/bluesky.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/auto_archiver/modules/generic_extractor/bluesky.py b/src/auto_archiver/modules/generic_extractor/bluesky.py index f2086b0..5eef520 100644 --- a/src/auto_archiver/modules/generic_extractor/bluesky.py +++ b/src/auto_archiver/modules/generic_extractor/bluesky.py @@ -39,11 +39,11 @@ class Bluesky(GenericDropin): for image_media in image_medias: url = media_url.format(image_media['image']['ref']['$link'], post['author']['did']) image_media = archiver.download_from_url(url) - media.append(image_media) + media.append(Media(image_media)) for video_media in video_medias: url = media_url.format(video_media['ref']['$link'], post['author']['did']) video_media = archiver.download_from_url(url) - media.append(video_media) + media.append(Media(video_media)) return media From e507fc81d2ed8c175a79891308e7a78bd397d213 Mon Sep 17 00:00:00 2001 From: msramalho <19508417+msramalho@users.noreply.github.com> Date: Tue, 11 Feb 2025 15:02:49 +0000 Subject: [PATCH 010/160] improves mimetype guessing, previously file.sub.something would not have an extension --- src/auto_archiver/core/extractor.py | 4 ++-- src/auto_archiver/modules/html_formatter/html_formatter.py | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/auto_archiver/core/extractor.py b/src/auto_archiver/core/extractor.py index 794c06c..2792184 100644 --- a/src/auto_archiver/core/extractor.py +++ b/src/auto_archiver/core/extractor.py @@ -80,8 +80,8 @@ class Extractor(BaseModule): d.raise_for_status() # get mimetype from the response headers - if not Path(to_filename).suffix: - content_type = d.headers.get('Content-Type') + if not mimetypes.guess_type(to_filename)[0]: + content_type = d.headers.get('Content-Type') or self._guess_file_type(url) extension = mimetypes.guess_extension(content_type) if extension: to_filename += extension diff --git a/src/auto_archiver/modules/html_formatter/html_formatter.py b/src/auto_archiver/modules/html_formatter/html_formatter.py index 3691735..ce4e67b 100644 --- a/src/auto_archiver/modules/html_formatter/html_formatter.py +++ b/src/auto_archiver/modules/html_formatter/html_formatter.py @@ -9,7 +9,6 @@ import base64 from auto_archiver.version import __version__ from auto_archiver.core import Metadata, Media from auto_archiver.core import Formatter -from auto_archiver.modules.hash_enricher import HashEnricher from auto_archiver.utils.misc import random_str from auto_archiver.core.module import get_module From c720541de20a6b045c9f26a453e900d86bd41607 Mon Sep 17 00:00:00 2001 From: msramalho <19508417+msramalho@users.noreply.github.com> Date: Tue, 11 Feb 2025 15:22:06 +0000 Subject: [PATCH 011/160] merge conflicts --- src/auto_archiver/modules/wacz_enricher/wacz_enricher.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/auto_archiver/modules/wacz_enricher/wacz_enricher.py b/src/auto_archiver/modules/wacz_enricher/wacz_enricher.py index c324c62..ff7314a 100644 --- a/src/auto_archiver/modules/wacz_enricher/wacz_enricher.py +++ b/src/auto_archiver/modules/wacz_enricher/wacz_enricher.py @@ -221,4 +221,4 @@ class WaczExtractorEnricher(Enricher, Extractor): to_enrich.add_media(m, warc_fn) counter += 1 seen_urls.add(record_url) - logger.info(f"WACZ extract_media/extract_screenshot finished, found {counter} relevant media file(s)") + logger.info(f"WACZ extract_media/extract_screenshot finished, found {counter} relevant media file(s)") \ No newline at end of file From 91f1ebf7b39556e9f070657332a9436af300989d Mon Sep 17 00:00:00 2001 From: msramalho <19508417+msramalho@users.noreply.github.com> Date: Tue, 11 Feb 2025 15:23:16 +0000 Subject: [PATCH 012/160] fix temp for yandex new shortlink --- .../modules/generic_extractor/generic_extractor.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/auto_archiver/modules/generic_extractor/generic_extractor.py b/src/auto_archiver/modules/generic_extractor/generic_extractor.py index afbe91b..6bcb249 100644 --- a/src/auto_archiver/modules/generic_extractor/generic_extractor.py +++ b/src/auto_archiver/modules/generic_extractor/generic_extractor.py @@ -266,6 +266,11 @@ class GenericExtractor(Extractor): def download(self, item: Metadata) -> Metadata: url = item.get_url() + #TODO: this is a temporary hack until this issue is closed: https://github.com/yt-dlp/yt-dlp/issues/11025 + if url.startswith("https://ya.ru"): + url = url.replace("https://ya.ru", "https://yandex.ru") + item.set("replaced_url", url) + ydl_options = {'outtmpl': os.path.join(self.tmp_dir, f'%(id)s.%(ext)s'), 'quiet': False, 'noplaylist': not self.allow_playlist , From ea728a7a97e8ec6b70e904a4054a2439c2d11385 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Tue, 11 Feb 2025 15:55:19 +0000 Subject: [PATCH 013/160] TODO on facebook dropin not working --- src/auto_archiver/modules/generic_extractor/facebook.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/auto_archiver/modules/generic_extractor/facebook.py b/src/auto_archiver/modules/generic_extractor/facebook.py index 352d44e..fed8e09 100644 --- a/src/auto_archiver/modules/generic_extractor/facebook.py +++ b/src/auto_archiver/modules/generic_extractor/facebook.py @@ -8,7 +8,8 @@ class Facebook(GenericDropin): url.replace('://m.facebook.com/', '://www.facebook.com/'), video_id) webpage = ie_instance._download_webpage(url, ie_instance._match_valid_url(url).group('id')) - post_data = ie_instance._extract_from_url.extract_metadata(webpage) + # TODO: fix once https://github.com/yt-dlp/yt-dlp/pull/12275 is merged + post_data = ie_instance._extract_metadata(webpage) return post_data def create_metadata(self, post: dict, ie_instance, archiver, url): From 3787577a961a6dbac9d1145b165413348471b5ec Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Tue, 11 Feb 2025 18:18:52 +0000 Subject: [PATCH 014/160] Screenshot enricher depends on geckodriver not chromedriver --- src/auto_archiver/modules/screenshot_enricher/__manifest__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/auto_archiver/modules/screenshot_enricher/__manifest__.py b/src/auto_archiver/modules/screenshot_enricher/__manifest__.py index 52842c9..831959e 100644 --- a/src/auto_archiver/modules/screenshot_enricher/__manifest__.py +++ b/src/auto_archiver/modules/screenshot_enricher/__manifest__.py @@ -4,7 +4,7 @@ "requires_setup": True, "dependencies": { "python": ["loguru", "selenium"], - "bin": ["chromedriver"] + "bin": ["geckodriver"] }, "configs": { "width": {"default": 1280, "help": "width of the screenshots"}, From e65a99078f78a0e7d9ff956ce44fe72feb7e1764 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Tue, 11 Feb 2025 18:28:21 +0000 Subject: [PATCH 015/160] Slight tweaks of toctrees + reordering --- docs/source/index.md | 2 +- docs/source/installation/configurations.md | 7 ------- docs/source/installation/installation.md | 1 + docs/source/modules/feeder.md | 7 ++++++- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/docs/source/index.md b/docs/source/index.md index bcab52a..57e8d60 100644 --- a/docs/source/index.md +++ b/docs/source/index.md @@ -8,9 +8,9 @@ :caption: Contents: Overview -how_to installation/installation.rst core_modules.md +how_to development/developer_guidelines autoapi/index.rst ``` \ No newline at end of file diff --git a/docs/source/installation/configurations.md b/docs/source/installation/configurations.md index 6dab1ab..b83a064 100644 --- a/docs/source/installation/configurations.md +++ b/docs/source/installation/configurations.md @@ -1,13 +1,6 @@ # Configuration - -```{toctree} -:hidden: - -configurations -``` - This section of the documentation provides guidelines for configuring the tool. ## Configuring from the Command Line diff --git a/docs/source/installation/installation.md b/docs/source/installation/installation.md index 8abff21..337a392 100644 --- a/docs/source/installation/installation.md +++ b/docs/source/installation/installation.md @@ -2,6 +2,7 @@ ```{toctree} :depth: 1 +:hidden: configurations.md config_cheatsheet.md diff --git a/docs/source/modules/feeder.md b/docs/source/modules/feeder.md index 9b6c403..be51aa8 100644 --- a/docs/source/modules/feeder.md +++ b/docs/source/modules/feeder.md @@ -2,7 +2,12 @@ Feeder modules are used to feed URLs into the `auto-archiver` for processing. Feeders can take these URLs from a variety of sources, such as a file, a database, or the command line. -The default feeder is the command line feeder, which allows you to input URLs directly into the `auto-archiver` from the command line. +The default feeder is the command line feeder (`cli_feeder`), which allows you to input URLs directly into the `auto-archiver` from the command line. + +Command line feeder usage: +```{code} bash +auto-archiver [options] URL1 URL2 ... +``` ```{include} autogen/feeder.md ``` From 2c8747418640ab00fc413362aa313888c43b87c6 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Tue, 11 Feb 2025 18:37:03 +0000 Subject: [PATCH 016/160] Change path for scripts --- docs/source/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index ab88bdf..9ce7e13 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -4,7 +4,7 @@ import sys import os from importlib.metadata import metadata -sys.path.insert(0, os.path.abspath('../scripts')) +sys.path.append(os.path.abspath('../scripts')) from scripts import generate_module_docs # -- Project Hooks ----------------------------------------------------------- From a307d09e67c9e48a8ae22d90aac2c81597114a68 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Tue, 11 Feb 2025 18:38:41 +0000 Subject: [PATCH 017/160] Readthedocs now requires all packages for running the pre-build scripts --- .readthedocs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 9f67835..434f805 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -15,7 +15,7 @@ build: # https://python-poetry.org/docs/managing-dependencies/#dependency-groups # VIRTUAL_ENV needs to be set manually for now. # See https://github.com/readthedocs/readthedocs.org/pull/11152/ - - VIRTUAL_ENV=$READTHEDOCS_VIRTUALENV_PATH poetry install --only docs + - VIRTUAL_ENV=$READTHEDOCS_VIRTUALENV_PATH poetry install --with docs sphinx: From 40a95f7348a2f7f5538e715dd79746f185c58245 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Tue, 11 Feb 2025 18:40:44 +0000 Subject: [PATCH 018/160] Add likify deps --- poetry.lock | 38 +++++++++++++++++++++++++++++++++++++- pyproject.toml | 1 + 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index 8748ea7..3c943cd 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1020,6 +1020,27 @@ files = [ [package.dependencies] attrs = ">=19.2.0" +[[package]] +name = "linkify-it-py" +version = "2.0.3" +description = "Links recognition library with FULL unicode support." +optional = false +python-versions = ">=3.7" +groups = ["docs"] +files = [ + {file = "linkify-it-py-2.0.3.tar.gz", hash = "sha256:68cda27e162e9215c17d786649d1da0021a451bdc436ef9e0fa0ba5234b9b048"}, + {file = "linkify_it_py-2.0.3-py3-none-any.whl", hash = "sha256:6bcbc417b0ac14323382aef5c5192c0075bf8a9d6b41820a2b66371eac6b6d79"}, +] + +[package.dependencies] +uc-micro-py = "*" + +[package.extras] +benchmark = ["pytest", "pytest-benchmark"] +dev = ["black", "flake8", "isort", "pre-commit", "pyproject-flake8"] +doc = ["myst-parser", "sphinx", "sphinx-book-theme"] +test = ["coverage", "pytest", "pytest-cov"] + [[package]] name = "loguru" version = "0.7.3" @@ -2777,6 +2798,21 @@ tzdata = {version = "*", markers = "platform_system == \"Windows\""} [package.extras] devenv = ["check-manifest", "pytest (>=4.3)", "pytest-cov", "pytest-mock (>=3.3)", "zest.releaser"] +[[package]] +name = "uc-micro-py" +version = "1.0.3" +description = "Micro subset of unicode data files for linkify-it-py projects." +optional = false +python-versions = ">=3.7" +groups = ["docs"] +files = [ + {file = "uc-micro-py-1.0.3.tar.gz", hash = "sha256:d321b92cff673ec58027c04015fcaa8bb1e005478643ff4a500882eaab88c48a"}, + {file = "uc_micro_py-1.0.3-py3-none-any.whl", hash = "sha256:db1dffff340817673d7b466ec86114a9dc0e9d4d9b5ba229d9d60e5c12600cd5"}, +] + +[package.extras] +test = ["coverage", "pytest", "pytest-cov"] + [[package]] name = "uritemplate" version = "4.1.1" @@ -3132,4 +3168,4 @@ test = ["pytest (>=8.1,<9.0)", "pytest-rerunfailures (>=14.0,<15.0)"] [metadata] lock-version = "2.1" python-versions = ">=3.10,<3.13" -content-hash = "432fe98be0e17791a047396646177cb8aaf6590c6d6247829664ed6fc1f84428" +content-hash = "c2503c982b9362c3757f39432cdaa8375b45e2d4a0497fa80c2b82a65d1eedf7" diff --git a/pyproject.toml b/pyproject.toml index f025842..c217ce2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,6 +73,7 @@ sphinx-autobuild = "^2024.10.3" sphinx-copybutton = "^0.5.2" myst-parser = "^4.0.0" sphinx-book-theme = "^1.1.3" +linkify-it-py = "^2.0.3" [project.scripts] From 1d69053dd5871e9706a53f6a60abe69be169253a Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Tue, 11 Feb 2025 18:41:37 +0000 Subject: [PATCH 019/160] Upgrade certifi --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 3c943cd..cca7024 100644 --- a/poetry.lock +++ b/poetry.lock @@ -374,14 +374,14 @@ files = [ [[package]] name = "certifi" -version = "2024.12.14" +version = "2025.1.31" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" groups = ["main", "docs"] files = [ - {file = "certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56"}, - {file = "certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db"}, + {file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"}, + {file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"}, ] [[package]] From 756f46012bf1347036dc4f32e5d51cb8b9d89e97 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Tue, 11 Feb 2025 18:47:54 +0000 Subject: [PATCH 020/160] Remove empty file --- src/auto_archiver/utils/gsheet.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/auto_archiver/utils/gsheet.py diff --git a/src/auto_archiver/utils/gsheet.py b/src/auto_archiver/utils/gsheet.py deleted file mode 100644 index e69de29..0000000 From 1b976f4c09a003c25d102e7425487bd5e70a64b2 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Tue, 11 Feb 2025 18:49:54 +0000 Subject: [PATCH 021/160] Remove unused atlos util functions --- src/auto_archiver/modules/atlos_db/base_configs.py | 13 ------------- src/auto_archiver/utils/__init__.py | 1 - src/auto_archiver/utils/atlos.py | 13 ------------- 3 files changed, 27 deletions(-) delete mode 100644 src/auto_archiver/modules/atlos_db/base_configs.py delete mode 100644 src/auto_archiver/utils/atlos.py diff --git a/src/auto_archiver/modules/atlos_db/base_configs.py b/src/auto_archiver/modules/atlos_db/base_configs.py deleted file mode 100644 index f672f82..0000000 --- a/src/auto_archiver/modules/atlos_db/base_configs.py +++ /dev/null @@ -1,13 +0,0 @@ -def get_atlos_config_options(): - return { - "api_token": { - "default": None, - "help": "An Atlos API token. For more information, see https://docs.atlos.org/technical/api/", - "type": str - }, - "atlos_url": { - "default": "https://platform.atlos.org", - "help": "The URL of your Atlos instance (e.g., https://platform.atlos.org), without a trailing slash.", - "type": str - }, - } \ No newline at end of file diff --git a/src/auto_archiver/utils/__init__.py b/src/auto_archiver/utils/__init__.py index ed2d3bb..46ca191 100644 --- a/src/auto_archiver/utils/__init__.py +++ b/src/auto_archiver/utils/__init__.py @@ -2,7 +2,6 @@ # we need to explicitly expose the available imports here from .misc import * from .webdriver import Webdriver -from .atlos import get_atlos_config_options # handy utils from ytdlp from yt_dlp.utils import (clean_html, traverse_obj, strip_or_none, url_or_none) \ No newline at end of file diff --git a/src/auto_archiver/utils/atlos.py b/src/auto_archiver/utils/atlos.py deleted file mode 100644 index c47c711..0000000 --- a/src/auto_archiver/utils/atlos.py +++ /dev/null @@ -1,13 +0,0 @@ -def get_atlos_config_options(): - return { - "api_token": { - "default": None, - "help": "An Atlos API token. For more information, see https://docs.atlos.org/technical/api/", - "cli_set": lambda cli_val, _: cli_val - }, - "atlos_url": { - "default": "https://platform.atlos.org", - "help": "The URL of your Atlos instance (e.g., https://platform.atlos.org), without a trailing slash.", - "cli_set": lambda cli_val, _: cli_val - }, - } \ No newline at end of file From 22fa9ba456a76a09e4510019ea350bf3d8dc6264 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Tue, 11 Feb 2025 18:52:28 +0000 Subject: [PATCH 022/160] further tweaks --- docs/source/development/creating_modules.md | 7 ------- docs/source/installation/configurations.md | 4 ++-- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/docs/source/development/creating_modules.md b/docs/source/development/creating_modules.md index 7ee65ef..87137ca 100644 --- a/docs/source/development/creating_modules.md +++ b/docs/source/development/creating_modules.md @@ -27,13 +27,6 @@ Check out the [core modules](https://github.com/bellingcat/auto-archiver/tree/ma The manifest file is where you define the core information of your module. It is a python dict containing important information, here's an example file: -```{code} python -:filename: myfile.py - -def setup(): - pass -``` - ```{include} ../../../tests/data/test_modules/example_module/__manifest__.py :name: __manifest__.py :literal: diff --git a/docs/source/installation/configurations.md b/docs/source/installation/configurations.md index b83a064..d534106 100644 --- a/docs/source/installation/configurations.md +++ b/docs/source/installation/configurations.md @@ -21,11 +21,11 @@ The recommended way to configure auto-archiver for long-term and deployed projec A default `orchestration.yaml` will be created for you the first time you run auto-archiver (without any arguments). Here's what it looks like:
-View example.orchestration.yaml +View example orchestration.yaml ```{literalinclude} ../example.orchestration.yaml :language: yaml - :caption: example.orchestration.yaml + :caption: orchestration.yaml ```
From 1ee7981c6e9243f2fc593588e1d0a1b33eab4d00 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Tue, 11 Feb 2025 19:42:03 +0000 Subject: [PATCH 023/160] Add YAML config to the module docs --- docs/scripts/scripts.py | 31 ++++++++++++++----- docs/source/conf.py | 1 + .../telethon_extractor/__manifest__.py | 2 +- 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/docs/scripts/scripts.py b/docs/scripts/scripts.py index 7439611..f2bf709 100644 --- a/docs/scripts/scripts.py +++ b/docs/scripts/scripts.py @@ -2,6 +2,8 @@ from pathlib import Path from auto_archiver.core.module import available_modules from auto_archiver.core.base_module import BaseModule +from ruamel.yaml import YAML +import io MODULES_FOLDER = Path(__file__).parent.parent.parent.parent / "src" / "auto_archiver" / "modules" SAVE_FOLDER = Path(__file__).parent.parent / "source" / "modules" / "autogen" @@ -18,6 +20,7 @@ type_color = { TABLE_HEADER = ("Option", "Description", "Default", "Type") def generate_module_docs(): + yaml = YAML() SAVE_FOLDER.mkdir(exist_ok=True) modules_by_type = {} @@ -41,22 +44,34 @@ def generate_module_docs(): {types} ``` {description} -""" - if manifest['configs']: - readme_str += "\n## Configuration Options\n" - readme_str += header_row +""" + if not manifest['configs']: + readme_str += "\n*This module has no configuration options.*\n" + else: + config_yaml = {} + config_table = header_row for key, value in manifest['configs'].items(): type = value.get('type', 'string') if type == 'auto_archiver.utils.json_loader': value['type'] = 'json' elif type == 'str': type = "string" - + + default = value.get('default', '') + config_yaml[key] = default help = "**Required**. " if value.get('required', False) else "Optional. " help += value.get('help', '') - readme_str += f"| `{module.name}.{key}` | {help} | {value.get('default', '')} | {type} |\n" - configs_cheatsheet += f"| `{module.name}.{key}` | {help} | {value.get('default', '')} | {type} |\n" - + config_table += f"| `{module.name}.{key}` | {help} | {value.get('default', '')} | {type} |\n" + configs_cheatsheet += f"| `{module.name}.{key}` | {help} | {default} | {type} |\n" + readme_str += "\n## Configuration Options\n" + readme_str += "\n### YAML\n" + yaml_string = io.BytesIO() + yaml.dump({module.name: config_yaml}, yaml_string) + + readme_str += f"```{{code}} yaml\n{yaml_string.getvalue().decode('utf-8')}\n```\n" + + readme_str += "\n### Command Line:\n" + readme_str += config_table # add a link to the autodoc refs readme_str += f"\n[API Reference](../../../autoapi/{module.name}/index)\n" diff --git a/docs/source/conf.py b/docs/source/conf.py index 9ce7e13..d5bd71b 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -25,6 +25,7 @@ extensions = [ "autoapi.extension", # Generate API documentation from docstrings "sphinxcontrib.mermaid", # Mermaid diagrams "sphinx.ext.viewcode", # Source code links + "sphinx_copybutton", "sphinx.ext.napoleon", # Google-style and NumPy-style docstrings "sphinx.ext.autosectionlabel", # 'sphinx.ext.autosummary', # Summarize module/class/function docs diff --git a/src/auto_archiver/modules/telethon_extractor/__manifest__.py b/src/auto_archiver/modules/telethon_extractor/__manifest__.py index 6b37654..1de9035 100644 --- a/src/auto_archiver/modules/telethon_extractor/__manifest__.py +++ b/src/auto_archiver/modules/telethon_extractor/__manifest__.py @@ -1,5 +1,5 @@ { - "name": "telethon_extractor", + "name": "Telethon Extractor", "type": ["extractor"], "requires_setup": True, "dependencies": { From d8f47ff9e4d56e32b55a647061a54391261871a0 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Tue, 11 Feb 2025 19:46:57 +0000 Subject: [PATCH 024/160] Add multi-type modules to all headings on TOC --- docs/scripts/scripts.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/scripts/scripts.py b/docs/scripts/scripts.py index f2bf709..f73315b 100644 --- a/docs/scripts/scripts.py +++ b/docs/scripts/scripts.py @@ -76,11 +76,12 @@ def generate_module_docs(): # add a link to the autodoc refs readme_str += f"\n[API Reference](../../../autoapi/{module.name}/index)\n" # create the module.type folder, use the first type just for where to store the file - type_folder = SAVE_FOLDER / module.type[0] - type_folder.mkdir(exist_ok=True) - with open(type_folder / f"{module.name}.md", "w") as f: - print("writing", SAVE_FOLDER) - f.write(readme_str) + for type in manifest['type']: + type_folder = SAVE_FOLDER / type + type_folder.mkdir(exist_ok=True) + with open(type_folder / f"{module.name}.md", "w") as f: + print("writing", SAVE_FOLDER) + f.write(readme_str) generate_index(modules_by_type) with open(SAVE_FOLDER / "configs_cheatsheet.md", "w") as f: From 4c119b4db859cbaef13c544c6e62fad4a482e93f Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Tue, 11 Feb 2025 20:03:45 +0000 Subject: [PATCH 025/160] Add missing manifest for atlos_storage --- .../modules/atlos_storage/__manifest__.py | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 src/auto_archiver/modules/atlos_storage/__manifest__.py diff --git a/src/auto_archiver/modules/atlos_storage/__manifest__.py b/src/auto_archiver/modules/atlos_storage/__manifest__.py new file mode 100644 index 0000000..55b5120 --- /dev/null +++ b/src/auto_archiver/modules/atlos_storage/__manifest__.py @@ -0,0 +1,32 @@ +{ + "name": "Atlos Storage", + "type": ["storage"], + "requires_setup": True, + "dependencies": { + "python": ["loguru", "boto3"], + "bin": [] + }, + "description": """ + Stores media files in a [Atlos](https://www.atlos.org/). + + ### Features + - Saves media files to Atlos, organizing them into folders based on the provided path structure. + + ### Notes + - Requires setup with Atlos credentials. + - Files are uploaded to the specified `root_folder_id` and organized by the `media.key` structure. + """, + "configs": { + "api_token": { + "default": None, + "help": "An Atlos API token. For more information, see https://docs.atlos.org/technical/api/", + "required": True, + "type": "str" + }, + "atlos_url": { + "default": "https://platform.atlos.org", + "help": "The URL of your Atlos instance (e.g., https://platform.atlos.org), without a trailing slash.", + "type": "str" + }, + } +} \ No newline at end of file From 5b481f72abefa6045bbaf7da5dd3121e1b9362db Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Tue, 11 Feb 2025 20:03:53 +0000 Subject: [PATCH 026/160] Tidy ups to manifests for docs --- README.md | 43 +------------------ .../modules/atlos_db/__manifest__.py | 2 + .../modules/gdrive_storage/__manifest__.py | 1 - .../telegram_extractor/__manifest__.py | 1 + .../telethon_extractor/__manifest__.py | 4 ++ 5 files changed, 8 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index e0ef0f5..37da9e2 100644 --- a/README.md +++ b/README.md @@ -118,45 +118,4 @@ auto-archiver --config secrets/orchestration.yaml auto-archiver --config secrets/orchestration.yaml --gsheet_feeder.sheet="use it on another sheets doc" --gsheet_feeder.header=2 --gsheet_feeder.columns='{"url": "link"}' # all the configurations come from orchestration.yaml and specifies that s3 files should be private auto-archiver --config secrets/orchestration.yaml --s3_storage.private=1 -``` - -### Extra notes on configuration -#### Google Drive -To use Google Drive storage you need the id of the shared folder in the `config.yaml` file which must be shared with the service account eg `autoarchiverservice@auto-archiver-111111.iam.gserviceaccount.com` and then you can use `--storage=gd` - -#### Telethon + Instagram with telegram bot -The first time you run, you will be prompted to do a authentication with the phone number associated, alternatively you can put your `anon.session` in the root. - -#### Atlos -When integrating with [Atlos](https://atlos.org), you will need to provide an API token in your configuration. You can learn more about Atlos and how to get an API token [here](https://docs.atlos.org/technical/api). You will have to provide this token to the `atlos_feeder`, `atlos_storage`, and `atlos_db` steps in your orchestration file. If you use a custom or self-hosted Atlos instance, you can also specify the `atlos_url` option to point to your custom instance's URL. For example: - -```yaml -# orchestration.yaml content -steps: - feeder: atlos_feeder - archivers: # order matters - - youtubedl_archiver - enrichers: - - thumbnail_enricher - - hash_enricher - formatter: html_formatter - storages: - - atlos_storage - databases: - - console_db - - atlos_db - -configurations: - atlos_feeder: - atlos_url: "https://platform.atlos.org" # optional - api_token: "...your API token..." - atlos_db: - atlos_url: "https://platform.atlos.org" # optional - api_token: "...your API token..." - atlos_storage: - atlos_url: "https://platform.atlos.org" # optional - api_token: "...your API token..." - hash_enricher: - algorithm: "SHA-256" -``` - +``` \ No newline at end of file diff --git a/src/auto_archiver/modules/atlos_db/__manifest__.py b/src/auto_archiver/modules/atlos_db/__manifest__.py index b9cabf2..d23ff23 100644 --- a/src/auto_archiver/modules/atlos_db/__manifest__.py +++ b/src/auto_archiver/modules/atlos_db/__manifest__.py @@ -11,6 +11,8 @@ "api_token": { "default": None, "help": "An Atlos API token. For more information, see https://docs.atlos.org/technical/api/", + "required": True, + "type": "str", }, "atlos_url": { "default": "https://platform.atlos.org", diff --git a/src/auto_archiver/modules/gdrive_storage/__manifest__.py b/src/auto_archiver/modules/gdrive_storage/__manifest__.py index 632e52b..73784b8 100644 --- a/src/auto_archiver/modules/gdrive_storage/__manifest__.py +++ b/src/auto_archiver/modules/gdrive_storage/__manifest__.py @@ -32,7 +32,6 @@ GDriveStorage: A storage module for saving archived content to Google Drive. - Author: Dave Mateer, (And maintained by: ) Source Documentation: https://davemateer.com/2022/04/28/google-drive-with-python ### Features diff --git a/src/auto_archiver/modules/telegram_extractor/__manifest__.py b/src/auto_archiver/modules/telegram_extractor/__manifest__.py index cb0ee1e..0b94582 100644 --- a/src/auto_archiver/modules/telegram_extractor/__manifest__.py +++ b/src/auto_archiver/modules/telegram_extractor/__manifest__.py @@ -20,5 +20,6 @@ - Processes HTML content of messages to retrieve embedded media. - Sets structured metadata, including timestamps, content, and media details. - Does not require user authentication for Telegram. + """, } diff --git a/src/auto_archiver/modules/telethon_extractor/__manifest__.py b/src/auto_archiver/modules/telethon_extractor/__manifest__.py index 1de9035..e16d9db 100644 --- a/src/auto_archiver/modules/telethon_extractor/__manifest__.py +++ b/src/auto_archiver/modules/telethon_extractor/__manifest__.py @@ -40,5 +40,9 @@ To use the `TelethonExtractor`, you must configure the following: - **Bot Token**: Optional, allows access to additional content (e.g., large videos) but limits private channel archiving. - **Channel Invites**: Optional, specify a JSON string of invite links to join channels during setup. +### First Time Login +The first time you run, you will be prompted to do a authentication with the phone number associated, alternatively you can put your `anon.session` in the root. + + """ } \ No newline at end of file From d28d99daa680455e943af34459c545ad5bd367c4 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Wed, 12 Feb 2025 11:16:17 +0000 Subject: [PATCH 027/160] Docs tidy ups and re-organising --- CONTRIBUTING.md | 49 +++++++++ README.md | 110 +++------------------ docs/_static/custom.css | 4 + docs/source/conf.py | 5 +- docs/source/contributing.md | 2 + docs/source/flow_overview.md | 30 ++++++ docs/source/index.md | 1 + docs/source/installation/configurations.md | 35 ++++--- docs/source/installation/installation.md | 39 +++++++- docs/source/overview.md | 16 +++ 10 files changed, 173 insertions(+), 118 deletions(-) create mode 100644 CONTRIBUTING.md create mode 100644 docs/_static/custom.css create mode 100644 docs/source/contributing.md create mode 100644 docs/source/flow_overview.md create mode 100644 docs/source/overview.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..81977b5 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,49 @@ +# Contributing to Auto Archiver + +Thank you for your interest in contributing to Auto Archiver! Your contributions help improve the project and make it more useful for everyone. Please follow the guidelines below to ensure a smooth collaboration. + +### 1. Reporting a Bug + +If you encounter a bug, please create an issue on GitHub with the following details: + +* Describe the bug: Provide a clear and concise description of the issue. +* Steps to reproduce: Include the steps needed to reproduce the bug. +* Expected behavior: Describe what you expected to happen. +* Actual behavior: Explain what actually happened. +* Screenshots/logs: If applicable, attach screenshots or logs to help diagnose the problem. +* Environment: Mention the OS, Ruby version, and any other relevant details. + +### 2. Writing a Patch/Fix and Submitting Pull Requests + +If you’d like to fix a bug or improve existing code: + +1. Open a pull request on GitHub and link it to the relevant issue. +2. Make sure to document your pull request with a clear description of what changes were made and why. +3. Wait for review and make any requested changes. + +### 3. Creating New Modules + +If you want to add a new module to Auto Archiver: + +1. Ensure your module follows the existing [coding style and project structure](https://auto-archiver.readthedocs.io/en/development/creating_modules.html). +2. Write clear documentation explaining what your module does and how to use it. +3. Ideally, include unit tests for your module! +4. Follow the steps in Section 2 to submit a pull request. + +### 4. Do You Have Questions About the Source Code? + +If you have any questions about how the source code works or need help using Auto Archiver + +📝 Check the [Auto Archiver](https://auto-archiver.readthedocs.io/en/latest/) documentation. + +👉 Ask your questions in the [Bellingcat Discord](https://www.bellingcat.com/follow-bellingcat-on-social-media/). + +### 5. Do You Want to Contribute to the Documentation? + +We welcome contributions to the documentation! + +📖 Please read [Contributing to the Auto Archiver Documentation](https://auto-archiver.readthedocs.io/en/development/docs.html) to learn how you can help improve the project's documentation. + +------------------ + +Thank you for contributing to Auto Archiver! 🚀 \ No newline at end of file diff --git a/README.md b/README.md index 37da9e2..76ee789 100644 --- a/README.md +++ b/README.md @@ -9,113 +9,29 @@ + +Auto Archiver is a Python tool to automatically archive content on the web. It takes URLs from different sources (e.g. a CSV file, Google Sheets, command line etc.) and archives the content of each one. It can archive social media posts, videos, images and webpages. Content can enriched, then saved either locally or remotely (S3 bucket, Google Drive). The status of the archiving process can be appended to a CSV report, or if using Google Sheets – back to the original sheet. + +
+**[See the Auto Arciver documentation for more information.](https://auto-archiver.readthedocs.io/en/latest/)** +
+ Read the [article about Auto Archiver on bellingcat.com](https://www.bellingcat.com/resources/2022/09/22/preserve-vital-online-content-with-bellingcats-auto-archiver-tool/). -Python tool to automatically archive social media posts, videos, and images from a Google Sheets, the console, and more. Uses different archivers depending on the platform, and can save content to local storage, S3 bucket (Digital Ocean Spaces, AWS, ...), and Google Drive. If using Google Sheets as the source for links, it will be updated with information about the archived content. It can be run manually or on an automated basis. - - ## Installation -For full instructions on how to install auto-archiver, view the [Installation Guide](installation/installation.md) +View the [Installation Guide](installation/installation.md) for full instructions -Quick run using docker: +To get started quickly using Docker: `docker pull bellingcat/auto-archiver && docker run` +Or pip: -# Orchestration -The archiver work is orchestrated by the following workflow (we call each a **step**): -1. **Feeder** gets the links (from a spreadsheet, from the console, ...) -2. **Archiver** tries to archive the link (twitter, youtube, ...) -3. **Enricher** adds more info to the content (hashes, thumbnails, ...) -4. **Formatter** creates a report from all the archived content (HTML, PDF, ...) -5. **Database** knows what's been archived and also stores the archive result (spreadsheet, CSV, or just the console) +`pip install auto-archiver && auto-archiver --help` -To setup an auto-archiver instance create an `orchestration.yaml` which contains the workflow you would like. We advise you put this file into a `secrets/` folder and do not share it with others because it will contain passwords and other secrets. +## Contributing -The structure of orchestration file is split into 2 parts: `steps` (what **steps** to use) and `configurations` (how those steps should behave), here's a simplification: -```yaml -# orchestration.yaml content -steps: - feeder: gsheet_feeder - archivers: # order matters - - youtubedl_archiver - enrichers: - - thumbnail_enricher - formatter: html_formatter - storages: - - local_storage - databases: - - gsheet_db +We welcome contributions to the Auto Archiver project! See the [Contributing Guide](https://auto-archiver.readthedocs.io/en/contributing.html) for how to get involved! -configurations: - gsheet_feeder: - sheet: "your google sheet name" - header: 2 # row with header for your sheet - # ... configurations for the other steps here ... -``` - -To see all available `steps` (which archivers, storages, databases, ...) exist check the [example.orchestration.yaml](example.orchestration.yaml). - -All the `configurations` in the `orchestration.yaml` file (you can name it differently but need to pass it in the `--config FILENAME` argument) can be seen in the console by using the `--help` flag. They can also be overwritten, for example if you are using the `cli_feeder` to archive from the command line and want to provide the URLs you should do: - -```bash -auto-archiver --config secrets/orchestration.yaml --cli_feeder.urls="url1,url2,url3" -``` - -Here's the complete workflow that the auto-archiver goes through: - -```mermaid - -graph TD - s((start)) --> F(fa:fa-table Feeder) - F -->|get and clean URL| D1{fa:fa-database Database} - D1 -->|is already archived| e((end)) - D1 -->|not yet archived| a(fa:fa-download Archivers) - a -->|got media| E(fa:fa-chart-line Enrichers) - E --> S[fa:fa-box-archive Storages] - E --> Fo(fa:fa-code Formatter) - Fo --> S - Fo -->|update database| D2(fa:fa-database Database) - D2 --> e -``` - -## Orchestration checklist -Use this to make sure you help making sure you did all the required steps: -* [ ] you have a `/secrets` folder with all your configuration files including - * [ ] a orchestration file eg: `orchestration.yaml` pointing to the correct location of other files - * [ ] (optional if you use GoogleSheets) you have a `service_account.json` (see [how-to](https://gspread.readthedocs.io/en/latest/oauth2.html#for-bots-using-service-account)) - * [ ] (optional for telegram) a `anon.session` which appears after the 1st run where you login to telegram - * if you use private channels you need to add `channel_invites` and set `join_channels=true` at least once - * [ ] (optional for VK) a `vk_config.v2.json` - * [ ] (optional for using GoogleDrive storage) `gd-token.json` (see [help script](scripts/create_update_gdrive_oauth_token.py)) - * [ ] (optional for instagram) `instaloader.session` file which appears after the 1st run and login in instagram - * [ ] (optional for browsertrix) `profile.tar.gz` file - -#### Example invocations -The recommended way to run the auto-archiver is through Docker. The invocations below will run the auto-archiver Docker image using a configuration file that you have specified - -```bash -# all the configurations come from ./secrets/orchestration.yaml -docker run --rm -v $PWD/secrets:/app/secrets -v $PWD/local_archive:/app/local_archive bellingcat/auto-archiver --config secrets/orchestration.yaml -# uses the same configurations but for another google docs sheet -# with a header on row 2 and with some different column names -# notice that columns is a dictionary so you need to pass it as JSON and it will override only the values provided -docker run --rm -v $PWD/secrets:/app/secrets -v $PWD/local_archive:/app/local_archive bellingcat/auto-archiver --config secrets/orchestration.yaml --gsheet_feeder.sheet="use it on another sheets doc" --gsheet_feeder.header=2 --gsheet_feeder.columns='{"url": "link"}' -# all the configurations come from orchestration.yaml and specifies that s3 files should be private -docker run --rm -v $PWD/secrets:/app/secrets -v $PWD/local_archive:/app/local_archive bellingcat/auto-archiver --config secrets/orchestration.yaml --s3_storage.private=1 -``` - -The auto-archiver can also be run locally, if pre-requisites are correctly configured. Equivalent invocations are below. - -```bash -# all the configurations come from ./secrets/orchestration.yaml -auto-archiver --config secrets/orchestration.yaml -# uses the same configurations but for another google docs sheet -# with a header on row 2 and with some different column names -# notice that columns is a dictionary so you need to pass it as JSON and it will override only the values provided -auto-archiver --config secrets/orchestration.yaml --gsheet_feeder.sheet="use it on another sheets doc" --gsheet_feeder.header=2 --gsheet_feeder.columns='{"url": "link"}' -# all the configurations come from orchestration.yaml and specifies that s3 files should be private -auto-archiver --config secrets/orchestration.yaml --s3_storage.private=1 -``` \ No newline at end of file diff --git a/docs/_static/custom.css b/docs/_static/custom.css new file mode 100644 index 0000000..d77ab9d --- /dev/null +++ b/docs/_static/custom.css @@ -0,0 +1,4 @@ +.hidden_rtd { + display:none; +} + diff --git a/docs/source/conf.py b/docs/source/conf.py index d5bd71b..e7093c4 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -15,7 +15,7 @@ generate_module_docs() # -- Project information ----------------------------------------------------- package_metadata = metadata("auto-archiver") project = package_metadata["name"] -authors = package_metadata["authors"] +authors = "Bellingcat" release = package_metadata["version"] language = 'en' @@ -74,5 +74,6 @@ source_suffix = { # -- Options for HTML output ------------------------------------------------- html_theme = 'sphinx_book_theme' -# html_static_path = ['_static'] +html_static_path = ["../_static"] +html_css_files = ["custom.css"] diff --git a/docs/source/contributing.md b/docs/source/contributing.md new file mode 100644 index 0000000..e75758c --- /dev/null +++ b/docs/source/contributing.md @@ -0,0 +1,2 @@ +```{include} ../../CONTRIBUTING.md +``` \ No newline at end of file diff --git a/docs/source/flow_overview.md b/docs/source/flow_overview.md new file mode 100644 index 0000000..5ffa3a8 --- /dev/null +++ b/docs/source/flow_overview.md @@ -0,0 +1,30 @@ + +# Archiving Overview + +The archiver archives web pages using the following workflow +1. **Feeder** gets the links (from a spreadsheet, from the console, ...) +2. **Extractor** tries to extract content from the given link (e.g. videos from youtube, images from Twitter...) +3. **Enricher** adds more info to the content (hashes, thumbnails, ...) +4. **Formatter** creates a report from all the archived content (HTML, PDF, ...) +5. **Database** knows what's been archived and also stores the archive result (spreadsheet, CSV, or just the console) + +Each step in the workflow is handled by 'modules' that interact with the data in different ways. For example, the Twitter Extractor Module would extract information from the Twitter website. The Screenshot Enricher Module will take screenshots of the given page. See the [core modules page](core_modules.md) for an overview of all the modules that are available. + +Auto-archiver must have at least one module defined for each step of the workflow. This is done by setting the [configuration](installation/configurations.md) for your auto-archiver instance. + +Here's the complete workflow that the auto-archiver goes through: + +```mermaid + +graph TD + s((start)) --> F(fa:fa-table Feeder) + F -->|get and clean URL| D1{fa:fa-database Database} + D1 -->|is already archived| e((end)) + D1 -->|not yet archived| a(fa:fa-download Archivers) + a -->|got media| E(fa:fa-chart-line Enrichers) + E --> S[fa:fa-box-archive Storages] + E --> Fo(fa:fa-code Formatter) + Fo --> S + Fo -->|update database| D2(fa:fa-database Database) + D2 --> e +``` \ No newline at end of file diff --git a/docs/source/index.md b/docs/source/index.md index 57e8d60..6a7f769 100644 --- a/docs/source/index.md +++ b/docs/source/index.md @@ -8,6 +8,7 @@ :caption: Contents: Overview +contributing installation/installation.rst core_modules.md how_to diff --git a/docs/source/installation/configurations.md b/docs/source/installation/configurations.md index d534106..705b6c5 100644 --- a/docs/source/installation/configurations.md +++ b/docs/source/installation/configurations.md @@ -3,6 +3,24 @@ This section of the documentation provides guidelines for configuring the tool. +## Configuring using a file + +The recommended way to configure auto-archiver for long-term and deployed projects is a configuration file, typically called `orchestration.yaml`. This is a YAML file containing all the settings for your entire workflow. + +The structure of orchestration file is split into 2 parts: `steps` (what [steps](../flow_overview.md) to use) and `configurations` (settings for different modules), here's a simplification: + +A default `orchestration.yaml` will be created for you the first time you run auto-archiver (without any arguments). Here's what it looks like: + +
+View exampleorchestration.yaml + +```{literalinclude} ../example.orchestration.yaml + :language: yaml + :caption: orchestration.yaml +``` + +
+ ## Configuring from the Command Line You can run auto-archiver directy from the command line, without the need for a configuration file, command line arguments are parsed using the format `module_name.config_value`. For example, a config value of `api_key` in the `instagram_extractor` module would be passed on the command line with the flag `--instagram_extractor.api_key=API_KEY`. @@ -14,23 +32,10 @@ auto-archiver --instagram_extractor.api_key=123 --other_module.setting --store # will store the new settings into the configuration file (default: orchestration.yaml) ``` -## Configuring using a file - -The recommended way to configure auto-archiver for long-term and deployed projects is a configuration file, typically called `orchestration.yaml`. This is a YAML file containing all the settings for your entire workflow. - -A default `orchestration.yaml` will be created for you the first time you run auto-archiver (without any arguments). Here's what it looks like: - -
-View example orchestration.yaml - -```{literalinclude} ../example.orchestration.yaml - :language: yaml - :caption: orchestration.yaml +```{note} Arguments passed on the command line override those saved in your settings file. Save them to your config file using the -s or --store flag ``` -
- -## Core Module Configuration +## Seeing all Configuration Options View the configurable settings for the core modules on the individual doc pages for each [](../core_modules.md). You can also view all settings available for the modules you have on your system using the `--help` flag in auto-archiver. diff --git a/docs/source/installation/installation.md b/docs/source/installation/installation.md index 337a392..fdd3184 100644 --- a/docs/source/installation/installation.md +++ b/docs/source/installation/installation.md @@ -38,21 +38,52 @@ Docker works like a virtual machine running inside your computer, it isolates ev 2. `$PWD/local_archive` is a folder `local_archive/` in case you want to archive locally and have the files accessible outside docker 3. `/app/local_archive` is a folder inside docker that you can reference in your orchestration.yml file +### Example invocations + +The invocations below will run the auto-archiver Docker image using a configuration file that you have specified + +```bash +# all the configurations come from ./secrets/orchestration.yaml +docker run --rm -v $PWD/secrets:/app/secrets -v $PWD/local_archive:/app/local_archive bellingcat/auto-archiver --config secrets/orchestration.yaml +# uses the same configurations but for another google docs sheet +# with a header on row 2 and with some different column names +# notice that columns is a dictionary so you need to pass it as JSON and it will override only the values provided +docker run --rm -v $PWD/secrets:/app/secrets -v $PWD/local_archive:/app/local_archive bellingcat/auto-archiver --config secrets/orchestration.yaml --gsheet_feeder.sheet="use it on another sheets doc" --gsheet_feeder.header=2 --gsheet_feeder.columns='{"url": "link"}' +# all the configurations come from orchestration.yaml and specifies that s3 files should be private +docker run --rm -v $PWD/secrets:/app/secrets -v $PWD/local_archive:/app/local_archive bellingcat/auto-archiver --config secrets/orchestration.yaml --s3_storage.private=1 +``` + ## Installing Locally with Pip 1. Make sure you have python 3.10 or higher installed 2. Install the package with your preferred package manager: `pip/pipenv/conda install auto-archiver` or `poetry add auto-archiver` 3. Test it's installed with `auto-archiver --help` -4. Run it with your orchestration file and pass any flags you want in the command line `auto-archiver --config secrets/orchestration.yaml` if your orchestration file is inside a `secrets/`, which we advise +4. Install other local dependency requirements (for ) +5. Run it with your orchestration file and pass any flags you want in the command line `auto-archiver --config secrets/orchestration.yaml` if your orchestration file is inside a `secrets/`, which we advise + +### Example invocations + +Once all your [local requirements](#installing-local-requirements) are correctly installed, the + +```bash +# all the configurations come from ./secrets/orchestration.yaml +auto-archiver --config secrets/orchestration.yaml +# uses the same configurations but for another google docs sheet +# with a header on row 2 and with some different column names +# notice that columns is a dictionary so you need to pass it as JSON and it will override only the values provided +auto-archiver --config secrets/orchestration.yaml --gsheet_feeder.sheet="use it on another sheets doc" --gsheet_feeder.header=2 --gsheet_feeder.columns='{"url": "link"}' +# all the configurations come from orchestration.yaml and specifies that s3 files should be private +auto-archiver --config secrets/orchestration.yaml --s3_storage.private=1 +``` ### Installing Local Requirements If using the local installation method, you will also need to install the following dependencies locally: -1. [ffmpeg](https://www.ffmpeg.org/) must also be installed locally for this tool to work. -2. [firefox](https://www.mozilla.org/en-US/firefox/new/) and [geckodriver](https://github.com/mozilla/geckodriver/releases) on a path folder like `/usr/local/bin`. +1.[ffmpeg](https://www.ffmpeg.org/) - for handling of downloaded videos +2. [firefox](https://www.mozilla.org/en-US/firefox/new/) and [geckodriver](https://github.com/mozilla/geckodriver/releases) on a path folder like `/usr/local/bin` - for taking webpage screenshots with the screenshot enricher 3. (optional) [fonts-noto](https://fonts.google.com/noto) to deal with multiple unicode characters during selenium/geckodriver's screenshots: `sudo apt install fonts-noto -y`. - +4. [Browsertrix Crawler docker image](https://hub.docker.com/r/webrecorder/browsertrix-crawler) for the WACZ enricher/archiver diff --git a/docs/source/overview.md b/docs/source/overview.md new file mode 100644 index 0000000..57e8d60 --- /dev/null +++ b/docs/source/overview.md @@ -0,0 +1,16 @@ + +```{include} ../../README.md +``` + +```{toctree} +:maxdepth: 2 +:hidden: +:caption: Contents: + +Overview +installation/installation.rst +core_modules.md +how_to +development/developer_guidelines +autoapi/index.rst +``` \ No newline at end of file From de79e17128cb99f14a8222d91cc3da966912e35b Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Wed, 12 Feb 2025 11:19:21 +0000 Subject: [PATCH 028/160] better wording in link --- docs/source/development/creating_modules.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/development/creating_modules.md b/docs/source/development/creating_modules.md index 87137ca..0950251 100644 --- a/docs/source/development/creating_modules.md +++ b/docs/source/development/creating_modules.md @@ -1,6 +1,6 @@ # Creating Your Own Modules -Modules are what's used to extend `auto-archiver` to process different websites or media, and/or transform the data in a way that suits your needs. In most cases, the [](../core_modules.md) should be sufficient for every day use, but the most common use-cases for making your own Modules include: +Modules are what's used to extend `auto-archiver` to process different websites or media, and/or transform the data in a way that suits your needs. In most cases, the [Core Modules](../core_modules.md) should be sufficient for every day use, but the most common use-cases for making your own Modules include: 1. Extracting data from a website which doesn't work with the current core extractors. 2. Enriching or altering the data before saving with additional information that the core enrichers do not offer. From 8054ea96b396b77f01d8bee5d9422ac26ecc2359 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Wed, 12 Feb 2025 11:20:15 +0000 Subject: [PATCH 029/160] Document double dash between command line args and feeder urls on command line --- docs/source/modules/feeder.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/modules/feeder.md b/docs/source/modules/feeder.md index be51aa8..ce5f7ca 100644 --- a/docs/source/modules/feeder.md +++ b/docs/source/modules/feeder.md @@ -6,7 +6,7 @@ The default feeder is the command line feeder (`cli_feeder`), which allows you t Command line feeder usage: ```{code} bash -auto-archiver [options] URL1 URL2 ... +auto-archiver [options] -- URL1 URL2 ... ``` ```{include} autogen/feeder.md From a0c4a828257fadcf9f44a139f0d581e9b93d6064 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Wed, 12 Feb 2025 11:32:13 +0000 Subject: [PATCH 030/160] Improved docstrings for base modules --- src/auto_archiver/core/database.py | 10 ++++++++++ src/auto_archiver/core/enricher.py | 15 ++++++++++++--- src/auto_archiver/core/extractor.py | 14 ++++++++++++-- src/auto_archiver/core/feeder.py | 18 +++++++++++++++++- src/auto_archiver/core/formatter.py | 17 ++++++++++++++++- src/auto_archiver/core/storage.py | 22 ++++++++++++++++++++-- 6 files changed, 87 insertions(+), 9 deletions(-) diff --git a/src/auto_archiver/core/database.py b/src/auto_archiver/core/database.py index 0eb5d81..a6e76e5 100644 --- a/src/auto_archiver/core/database.py +++ b/src/auto_archiver/core/database.py @@ -1,3 +1,8 @@ +""" +Database module for the auto-archiver that defines the interface for implementing database modules +in the media archiving framework. +""" + from __future__ import annotations from abc import abstractmethod from typing import Union @@ -5,6 +10,11 @@ from typing import Union from auto_archiver.core import Metadata, BaseModule class Database(BaseModule): + """ + Base class for implementing database modules in the media archiving framework. + + Subclasses must implement the `fetch` and `done` methods to define platform-specific behavior. + """ def started(self, item: Metadata) -> None: """signals the DB that the given item archival has started""" diff --git a/src/auto_archiver/core/enricher.py b/src/auto_archiver/core/enricher.py index 0e50fa9..45e75d7 100644 --- a/src/auto_archiver/core/enricher.py +++ b/src/auto_archiver/core/enricher.py @@ -1,5 +1,5 @@ """ -Enrichers are modular components that enhance archived content by adding +Base module for Enrichers – modular components that enhance archived content by adding context, metadata, or additional processing. These add additional information to the context, such as screenshots, hashes, and metadata. @@ -13,7 +13,16 @@ from abc import abstractmethod from auto_archiver.core import Metadata, BaseModule class Enricher(BaseModule): - """Base classes and utilities for enrichers in the Auto-Archiver system.""" + """Base classes and utilities for enrichers in the Auto-Archiver system. + + Enricher modules must implement the `enrich` method to define their behavior. + """ @abstractmethod - def enrich(self, to_enrich: Metadata) -> None: pass + def enrich(self, to_enrich: Metadata) -> None: + """ + Enriches a Metadata object with additional information or context. + + Takes the metadata object to enrich as an argument and modifies it in place, returning None. + """ + pass diff --git a/src/auto_archiver/core/extractor.py b/src/auto_archiver/core/extractor.py index 794c06c..3b05ef7 100644 --- a/src/auto_archiver/core/extractor.py +++ b/src/auto_archiver/core/extractor.py @@ -29,14 +29,24 @@ class Extractor(BaseModule): valid_url: re.Pattern = None def cleanup(self) -> None: - # called when extractors are done, or upon errors, cleanup any resources + """ + Called when extractors are done, or upon errors, cleanup any resources + """ pass def sanitize_url(self, url: str) -> str: - # used to clean unnecessary URL parameters OR unfurl redirect links + """ + Used to clean unnecessary URL parameters OR unfurl redirect links + """ return url def match_link(self, url: str) -> re.Match: + """ + Returns a match object if the given URL matches the valid_url pattern or False/None if not. + + Normally used in the `suitable` method to check if the URL is supported by this extractor. + + """ return self.valid_url.match(url) def suitable(self, url: str) -> bool: diff --git a/src/auto_archiver/core/feeder.py b/src/auto_archiver/core/feeder.py index 352cfd9..e8302e6 100644 --- a/src/auto_archiver/core/feeder.py +++ b/src/auto_archiver/core/feeder.py @@ -1,3 +1,7 @@ +""" +The feeder base module defines the interface for implementing feeders in the media archiving framework. +""" + from __future__ import annotations from abc import abstractmethod from auto_archiver.core import Metadata @@ -5,5 +9,17 @@ from auto_archiver.core import BaseModule class Feeder(BaseModule): + """ + Base class for implementing feeders in the media archiving framework. + + Subclasses must implement the `__iter__` method to define platform-specific behavior. + """ + @abstractmethod - def __iter__(self) -> Metadata: return None \ No newline at end of file + def __iter__(self) -> Metadata: + """ + Returns an iterator (use `yield`) over the items to be archived. + + These should be instances of Metadata, typically created with Metadata().set_url(url). + """ + return None \ No newline at end of file diff --git a/src/auto_archiver/core/formatter.py b/src/auto_archiver/core/formatter.py index cf27cb3..3bfc250 100644 --- a/src/auto_archiver/core/formatter.py +++ b/src/auto_archiver/core/formatter.py @@ -1,9 +1,24 @@ +""" +Base module for formatters – modular components that format metadata into media objects for storage. + +The most commonly used formatter is the HTML formatter, which takes metadata and formats it into an HTML file for storage. +""" + from __future__ import annotations from abc import abstractmethod from auto_archiver.core import Metadata, Media, BaseModule class Formatter(BaseModule): + """ + Base class for implementing formatters in the media archiving framework. + + Subclasses must implement the `format` method to define their behavior. + """ @abstractmethod - def format(self, item: Metadata) -> Media: return None \ No newline at end of file + def format(self, item: Metadata) -> Media: + """ + Formats a Metadata object into a user-viewable format (e.g. HTML) and stores it if needed. + """ + return None \ No newline at end of file diff --git a/src/auto_archiver/core/storage.py b/src/auto_archiver/core/storage.py index 5dfa39d..15d4705 100644 --- a/src/auto_archiver/core/storage.py +++ b/src/auto_archiver/core/storage.py @@ -1,3 +1,7 @@ +""" +Base module for Storage modules – modular components that store media objects in various locations. +""" + from __future__ import annotations from abc import abstractmethod from typing import IO @@ -12,6 +16,12 @@ from auto_archiver.core import Media, BaseModule, Metadata from auto_archiver.modules.hash_enricher.hash_enricher import HashEnricher from auto_archiver.core.module import get_module class Storage(BaseModule): + + """ + Base class for implementing storage modules in the media archiving framework. + + Subclasses must implement the `get_cdn_url` and `uploadf` methods to define their behavior. + """ def store(self, media: Media, url: str, metadata: Metadata=None) -> None: if media.is_stored(in_storage=self): @@ -22,10 +32,18 @@ class Storage(BaseModule): media.add_url(self.get_cdn_url(media)) @abstractmethod - def get_cdn_url(self, media: Media) -> str: pass + def get_cdn_url(self, media: Media) -> str: + """ + Returns the URL of the media object stored in the CDN. + """ + pass @abstractmethod - def uploadf(self, file: IO[bytes], key: str, **kwargs: dict) -> bool: pass + def uploadf(self, file: IO[bytes], key: str, **kwargs: dict) -> bool: + """ + Uploads (or saves) a file to the storage service/location. + """ + pass def upload(self, media: Media, **kwargs) -> bool: logger.debug(f'[{self.__class__.__name__}] storing file {media.filename} with key {media.key}') From d776be8a817683c4e2b5db4bb2756eda6001f373 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Wed, 12 Feb 2025 11:41:54 +0000 Subject: [PATCH 031/160] Fix links to docs --- CONTRIBUTING.md | 4 ++-- README.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 81977b5..7e71f82 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -25,7 +25,7 @@ If you’d like to fix a bug or improve existing code: If you want to add a new module to Auto Archiver: -1. Ensure your module follows the existing [coding style and project structure](https://auto-archiver.readthedocs.io/en/development/creating_modules.html). +1. Ensure your module follows the existing [coding style and project structure](https://auto-archiver.readthedocs.io/en/latest/development/creating_modules.html). 2. Write clear documentation explaining what your module does and how to use it. 3. Ideally, include unit tests for your module! 4. Follow the steps in Section 2 to submit a pull request. @@ -42,7 +42,7 @@ If you have any questions about how the source code works or need help using Aut We welcome contributions to the documentation! -📖 Please read [Contributing to the Auto Archiver Documentation](https://auto-archiver.readthedocs.io/en/development/docs.html) to learn how you can help improve the project's documentation. +📖 Please read [Contributing to the Auto Archiver Documentation](https://auto-archiver.readthedocs.io/en/latest/development/docs.html) to learn how you can help improve the project's documentation. ------------------ diff --git a/README.md b/README.md index 76ee789..2ef3aea 100644 --- a/README.md +++ b/README.md @@ -33,5 +33,5 @@ Or pip: ## Contributing -We welcome contributions to the Auto Archiver project! See the [Contributing Guide](https://auto-archiver.readthedocs.io/en/contributing.html) for how to get involved! +We welcome contributions to the Auto Archiver project! See the [Contributing Guide](https://auto-archiver.readthedocs.io/en/latest/contributing.html) for how to get involved! From 17f13db56ca9b5b3cb825bcacae2c3f6b3eb3d44 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Wed, 12 Feb 2025 11:45:09 +0000 Subject: [PATCH 032/160] Make that code block a shell --- docs/source/development/docs.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/development/docs.md b/docs/source/development/docs.md index e21f954..bb389ff 100644 --- a/docs/source/development/docs.md +++ b/docs/source/development/docs.md @@ -18,7 +18,7 @@ poetry install **Create the documentation:** - Build the documentation: -``` +```shell # Using makefile (Linux/macOS): make -C docs html From 86254bdd4ece973ffca431419dd25a158e206814 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Wed, 12 Feb 2025 11:48:01 +0000 Subject: [PATCH 033/160] Fix link in how to --- docs/source/how_to.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/how_to.md b/docs/source/how_to.md index e8e5d9b..5cef626 100644 --- a/docs/source/how_to.md +++ b/docs/source/how_to.md @@ -3,7 +3,7 @@ ## How to use Google Sheets to load and store archive information The `--gsheet_feeder.sheet` property is the name of the Google Sheet to check for URLs. This sheet must have been shared with the Google Service account used by `gspread`. -This sheet must also have specific columns (case-insensitive) in the `header` as specified in [gsheet_feeder.__manifest__.py](src/auto_archiver/modules/gsheet_feeder/__manifest__.py). The default names of these columns and their purpose is: +This sheet must also have specific columns (case-insensitive) in the `header` - see the [Gsheet Feeder Docs](modules/autogen/feeder/gsheet_feeder.md) for more info. The default names of these columns and their purpose is: Inputs: From 70f155dfce2c51623a0c52f86587207310018162 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Wed, 12 Feb 2025 11:48:51 +0000 Subject: [PATCH 034/160] add more of the USPs to the readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2ef3aea..20ad1de 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ -Auto Archiver is a Python tool to automatically archive content on the web. It takes URLs from different sources (e.g. a CSV file, Google Sheets, command line etc.) and archives the content of each one. It can archive social media posts, videos, images and webpages. Content can enriched, then saved either locally or remotely (S3 bucket, Google Drive). The status of the archiving process can be appended to a CSV report, or if using Google Sheets – back to the original sheet. +Auto Archiver is a Python tool to automatically archive content on the web in a secure and verifiable way. It takes URLs from different sources (e.g. a CSV file, Google Sheets, command line etc.) and archives the content of each one. It can archive social media posts, videos, images and webpages. Content can enriched, then saved either locally or remotely (S3 bucket, Google Drive). The status of the archiving process can be appended to a CSV report, or if using Google Sheets – back to the original sheet.
**[See the Auto Arciver documentation for more information.](https://auto-archiver.readthedocs.io/en/latest/)** From da267f20d769c1360b49de0c3326b0b6dd996546 Mon Sep 17 00:00:00 2001 From: erinhmclark Date: Wed, 12 Feb 2025 11:54:40 +0000 Subject: [PATCH 035/160] Update screenshot refs --- docs/source/how_to.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/source/how_to.md b/docs/source/how_to.md index 5cef626..bf3b9fc 100644 --- a/docs/source/how_to.md +++ b/docs/source/how_to.md @@ -26,22 +26,22 @@ Outputs: For example, this is a spreadsheet configured with all of the columns for the auto archiver and a few URLs to archive. (Note that the column names are not case sensitive.) -![A screenshot of a Google Spreadsheet with column headers defined as above, and several Youtube and Twitter URLs in the "Link" column](docs/demo-before.png) +![A screenshot of a Google Spreadsheet with column headers defined as above, and several Youtube and Twitter URLs in the "Link" column](../demo-before.png) Now the auto archiver can be invoked, with this command in this example: `docker run --rm -v $PWD/secrets:/app/secrets -v $PWD/local_archive:/app/local_archive bellingcat/auto-archiver:dockerize --config secrets/orchestration-global.yaml --gsheet_feeder.sheet "Auto archive test 2023-2"`. Note that the sheet name has been overridden/specified in the command line invocation. When the auto archiver starts running, it updates the "Archive status" column. -![A screenshot of a Google Spreadsheet with column headers defined as above, and several Youtube and Twitter URLs in the "Link" column. The auto archiver has added "archive in progress" to one of the status columns.](docs/demo-progress.png) +![A screenshot of a Google Spreadsheet with column headers defined as above, and several Youtube and Twitter URLs in the "Link" column. The auto archiver has added "archive in progress" to one of the status columns.](../demo-progress.png) The links are downloaded and archived, and the spreadsheet is updated to the following: -![A screenshot of a Google Spreadsheet with videos archived and metadata added per the description of the columns above.](docs/demo-after.png) +![A screenshot of a Google Spreadsheet with videos archived and metadata added per the description of the columns above.](../demo-after.png) Note that the first row is skipped, as it is assumed to be a header row (`--gsheet_feeder.header=1` and you can change it if you use more rows above). Rows with an empty URL column, or a non-empty archive column are also skipped. All sheets in the document will be checked. The "archive location" link contains the path of the archived file, in local storage, S3, or in Google Drive. -![The archive result for a link in the demo sheet.](docs/demo-archive.png) +![The archive result for a link in the demo sheet.](../demo-archive.png) --- From d9d936c2cae621a3576968268ccf665bc3d31a8d Mon Sep 17 00:00:00 2001 From: erinhmclark Date: Wed, 12 Feb 2025 12:22:27 +0000 Subject: [PATCH 036/160] Thumbnail enricher fix seconds to minutes. --- poetry.lock | 113 ++++++------- .../thumbnail_enricher/__manifest__.py | 8 +- .../thumbnail_enricher/thumbnail_enricher.py | 2 +- tests/enrichers/test_thumbnail_enricher.py | 155 ++++++++++++++++++ 4 files changed, 219 insertions(+), 59 deletions(-) create mode 100644 tests/enrichers/test_thumbnail_enricher.py diff --git a/poetry.lock b/poetry.lock index 8fb48ec..6bfa62c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -84,14 +84,14 @@ tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] [[package]] name = "authlib" -version = "1.4.0" +version = "1.4.1" description = "The ultimate Python library in building OAuth and OpenID Connect servers and clients." optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "Authlib-1.4.0-py2.py3-none-any.whl", hash = "sha256:4bb20b978c8b636222b549317c1815e1fe62234fc1c5efe8855d84aebf3a74e3"}, - {file = "authlib-1.4.0.tar.gz", hash = "sha256:1c1e6608b5ed3624aeeee136ca7f8c120d6f51f731aa152b153d54741840e1f2"}, + {file = "Authlib-1.4.1-py2.py3-none-any.whl", hash = "sha256:edc29c3f6a3e72cd9e9f45fff67fc663a2c364022eb0371c003f22d5405915c1"}, + {file = "authlib-1.4.1.tar.gz", hash = "sha256:30ead9ea4993cdbab821dc6e01e818362f92da290c04c7f6a1940f86507a790d"}, ] [package.dependencies] @@ -115,33 +115,34 @@ tomli = {version = "*", markers = "python_version < \"3.11\""} [[package]] name = "babel" -version = "2.16.0" +version = "2.17.0" description = "Internationalization utilities" optional = false python-versions = ">=3.8" groups = ["docs"] files = [ - {file = "babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b"}, - {file = "babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316"}, + {file = "babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2"}, + {file = "babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d"}, ] [package.extras] -dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] +dev = ["backports.zoneinfo", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata"] [[package]] name = "beautifulsoup4" -version = "4.12.3" +version = "4.13.3" description = "Screen-scraping library" optional = false -python-versions = ">=3.6.0" +python-versions = ">=3.7.0" groups = ["main", "docs"] files = [ - {file = "beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"}, - {file = "beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"}, + {file = "beautifulsoup4-4.13.3-py3-none-any.whl", hash = "sha256:99045d7d3f08f91f0d656bc9b7efbae189426cd913d830294a15eefa0ea4df16"}, + {file = "beautifulsoup4-4.13.3.tar.gz", hash = "sha256:1bd32405dacc920b42b83ba01644747ed77456a65760e285fbc47633ceddaf8b"}, ] [package.dependencies] soupsieve = ">1.2" +typing-extensions = ">=4.0.0" [package.extras] cchardet = ["cchardet"] @@ -152,18 +153,18 @@ lxml = ["lxml"] [[package]] name = "boto3" -version = "1.36.6" +version = "1.36.17" description = "The AWS SDK for Python" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "boto3-1.36.6-py3-none-any.whl", hash = "sha256:6d473f0f340d02b4e9ad5b8e68786a09728101a8b950231b89ebdaf72b6dca21"}, - {file = "boto3-1.36.6.tar.gz", hash = "sha256:b36feae061dc0793cf311468956a0a9e99215ce38bc99a1a4e55a5b105f16297"}, + {file = "boto3-1.36.17-py3-none-any.whl", hash = "sha256:59bcf0c4b04d9cc36f8b418ad17ab3c4a99a21a175d2fad7096aa21cbe84630b"}, + {file = "boto3-1.36.17.tar.gz", hash = "sha256:5ecae20e780a3ce9afb3add532b61c466a8cb8960618e4fa565b3883064c1346"}, ] [package.dependencies] -botocore = ">=1.36.6,<1.37.0" +botocore = ">=1.36.17,<1.37.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.11.0,<0.12.0" @@ -172,14 +173,14 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.36.6" +version = "1.36.17" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "botocore-1.36.6-py3-none-any.whl", hash = "sha256:f77bbbb03fb420e260174650fb5c0cc142ec20a96967734eed2b0ef24334ef34"}, - {file = "botocore-1.36.6.tar.gz", hash = "sha256:4864c53d638da191a34daf3ede3ff1371a3719d952cc0c6bd24ce2836a38dd77"}, + {file = "botocore-1.36.17-py3-none-any.whl", hash = "sha256:069858b2fd693548035d7fd53a774e37e4260fea64e0ac9b8a3aee904f9321df"}, + {file = "botocore-1.36.17.tar.gz", hash = "sha256:cec13e0a7ce78e71aad0b397581b4e81824c7981ef4c261d2e296d200c399b09"}, ] [package.dependencies] @@ -188,7 +189,7 @@ python-dateutil = ">=2.1,<3.0.0" urllib3 = {version = ">=1.25.4,<2.2.0 || >2.2.0,<3", markers = "python_version >= \"3.10\""} [package.extras] -crt = ["awscrt (==0.23.4)"] +crt = ["awscrt (==0.23.8)"] [[package]] name = "brotli" @@ -355,14 +356,14 @@ files = [ [[package]] name = "certifi" -version = "2024.12.14" +version = "2025.1.31" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" groups = ["main", "docs"] files = [ - {file = "certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56"}, - {file = "certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db"}, + {file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"}, + {file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"}, ] [[package]] @@ -655,26 +656,26 @@ typing-inspect = ">=0.4.0,<1" [[package]] name = "dateparser" -version = "1.2.0" +version = "1.2.1" description = "Date parsing library designed to parse dates from HTML pages" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" groups = ["main"] files = [ - {file = "dateparser-1.2.0-py2.py3-none-any.whl", hash = "sha256:0b21ad96534e562920a0083e97fd45fa959882d4162acc358705144520a35830"}, - {file = "dateparser-1.2.0.tar.gz", hash = "sha256:7975b43a4222283e0ae15be7b4999d08c9a70e2d378ac87385b1ccf2cffbbb30"}, + {file = "dateparser-1.2.1-py3-none-any.whl", hash = "sha256:bdcac262a467e6260030040748ad7c10d6bacd4f3b9cdb4cfd2251939174508c"}, + {file = "dateparser-1.2.1.tar.gz", hash = "sha256:7e4919aeb48481dbfc01ac9683c8e20bfe95bb715a38c1e9f6af889f4f30ccc3"}, ] [package.dependencies] -python-dateutil = "*" -pytz = "*" -regex = "<2019.02.19 || >2019.02.19,<2021.8.27 || >2021.8.27" -tzlocal = "*" +python-dateutil = ">=2.7.0" +pytz = ">=2024.2" +regex = ">=2015.06.24,<2019.02.19 || >2019.02.19,<2021.8.27 || >2021.8.27" +tzlocal = ">=0.2" [package.extras] -calendars = ["convertdate", "hijri-converter"] -fasttext = ["fasttext"] -langdetect = ["langdetect"] +calendars = ["convertdate (>=2.2.1)", "hijridate"] +fasttext = ["fasttext (>=0.9.1)", "numpy (>=1.19.3,<2)"] +langdetect = ["langdetect (>=1.0.0)"] [[package]] name = "docutils" @@ -754,14 +755,14 @@ files = [ [[package]] name = "google-api-core" -version = "2.24.0" +version = "2.24.1" description = "Google API client core library" optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "google_api_core-2.24.0-py3-none-any.whl", hash = "sha256:10d82ac0fca69c82a25b3efdeefccf6f28e02ebb97925a8cce8edbfe379929d9"}, - {file = "google_api_core-2.24.0.tar.gz", hash = "sha256:e255640547a597a4da010876d333208ddac417d60add22b6851a0c66a831fcaf"}, + {file = "google_api_core-2.24.1-py3-none-any.whl", hash = "sha256:bc78d608f5a5bf853b80bd70a795f703294de656c096c0968320830a4bc280f1"}, + {file = "google_api_core-2.24.1.tar.gz", hash = "sha256:f8b36f5456ab0dd99a1b693a40a31d1e7757beea380ad1b38faaf8941eae9d8a"}, ] [package.dependencies] @@ -779,14 +780,14 @@ grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] [[package]] name = "google-api-python-client" -version = "2.159.0" +version = "2.160.0" description = "Google API Client Library for Python" optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "google_api_python_client-2.159.0-py2.py3-none-any.whl", hash = "sha256:baef0bb631a60a0bd7c0bf12a5499e3a40cd4388484de7ee55c1950bf820a0cf"}, - {file = "google_api_python_client-2.159.0.tar.gz", hash = "sha256:55197f430f25c907394b44fa078545ffef89d33fd4dca501b7db9f0d8e224bd6"}, + {file = "google_api_python_client-2.160.0-py2.py3-none-any.whl", hash = "sha256:63d61fb3e4cf3fb31a70a87f45567c22f6dfe87bbfa27252317e3e2c42900db4"}, + {file = "google_api_python_client-2.160.0.tar.gz", hash = "sha256:a8ccafaecfa42d15d5b5c3134ced8de08380019717fc9fb1ed510ca58eca3b7e"}, ] [package.dependencies] @@ -1136,14 +1137,14 @@ files = [ [[package]] name = "marshmallow" -version = "3.26.0" +version = "3.26.1" description = "A lightweight library for converting complex datatypes to and from native Python datatypes." optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "marshmallow-3.26.0-py3-none-any.whl", hash = "sha256:1287bca04e6a5f4094822ac153c03da5e214a0a60bcd557b140f3e66991b8ca1"}, - {file = "marshmallow-3.26.0.tar.gz", hash = "sha256:eb36762a1cc76d7abf831e18a3a1b26d3d481bbc74581b8e532a3d3a8115e1cb"}, + {file = "marshmallow-3.26.1-py3-none-any.whl", hash = "sha256:3350409f20a70a7e4e11a27661187b77cdcaeb20abca41c1454fe33636bea09c"}, + {file = "marshmallow-3.26.1.tar.gz", hash = "sha256:e6d8affb6cb61d39d26402096dc0aee12d5a26d490a121f118d2e81dc0719dc6"}, ] [package.dependencies] @@ -1512,14 +1513,14 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "proto-plus" -version = "1.25.0" -description = "Beautiful, Pythonic protocol buffers." +version = "1.26.0" +description = "Beautiful, Pythonic protocol buffers" optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "proto_plus-1.25.0-py3-none-any.whl", hash = "sha256:c91fc4a65074ade8e458e95ef8bac34d4008daa7cce4a12d6707066fca648961"}, - {file = "proto_plus-1.25.0.tar.gz", hash = "sha256:fbb17f57f7bd05a68b7707e745e26528b0b3c34e378db91eef93912c54982d91"}, + {file = "proto_plus-1.26.0-py3-none-any.whl", hash = "sha256:bf2dfaa3da281fc3187d12d224c707cb57214fb2c22ba854eb0c105a3fb2d4d7"}, + {file = "proto_plus-1.26.0.tar.gz", hash = "sha256:6e93d5f5ca267b54300880fff156b6a3386b3fa3f43b1da62e680fc0c586ef22"}, ] [package.dependencies] @@ -1820,14 +1821,14 @@ requests = ">=2.28" [[package]] name = "pytz" -version = "2024.2" +version = "2025.1" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" groups = ["main"] files = [ - {file = "pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725"}, - {file = "pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a"}, + {file = "pytz-2025.1-py2.py3-none-any.whl", hash = "sha256:89dd22dca55b46eac6eda23b2d72721bf1bdfef212645d81513ef5d03038de57"}, + {file = "pytz-2025.1.tar.gz", hash = "sha256:c2db42be2a2518b28e65f9207c4d05e6ff547d1efa4086469ef855e4ab70178e"}, ] [[package]] @@ -2076,14 +2077,14 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"] [[package]] name = "rich-argparse" -version = "1.6.0" +version = "1.7.0" description = "Rich help formatters for argparse and optparse" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "rich_argparse-1.6.0-py3-none-any.whl", hash = "sha256:fbe70a1d821b3f2fa8958cddf0cae131870a6e9faa04ab52b409cb1eda809bd7"}, - {file = "rich_argparse-1.6.0.tar.gz", hash = "sha256:092083c30da186f25bcdff8b1d47fdfb571288510fb051e0488a72cc3128de13"}, + {file = "rich_argparse-1.7.0-py3-none-any.whl", hash = "sha256:b8ec8943588e9731967f4f97b735b03dc127c416f480a083060433a97baf2fd3"}, + {file = "rich_argparse-1.7.0.tar.gz", hash = "sha256:f31d809c465ee43f367d599ccaf88b73bc2c4d75d74ed43f2d538838c53544ba"}, ] [package.dependencies] @@ -2316,20 +2317,20 @@ test = ["cython (>=3.0)", "defusedxml (>=0.7.1)", "pytest (>=8.0)", "setuptools [[package]] name = "sphinx-autoapi" -version = "3.4.0" +version = "3.5.0" description = "Sphinx API documentation generator" optional = false python-versions = ">=3.8" groups = ["docs"] files = [ - {file = "sphinx_autoapi-3.4.0-py3-none-any.whl", hash = "sha256:4027fef2875a22c5f2a57107c71641d82f6166bf55beb407a47aaf3ef14e7b92"}, - {file = "sphinx_autoapi-3.4.0.tar.gz", hash = "sha256:e6d5371f9411bbb9fca358c00a9e57aef3ac94cbfc5df4bab285946462f69e0c"}, + {file = "sphinx_autoapi-3.5.0-py3-none-any.whl", hash = "sha256:8676db32dded669dc6be9100696652640dc1e883e45b74710d74eb547a310114"}, + {file = "sphinx_autoapi-3.5.0.tar.gz", hash = "sha256:10dcdf86e078ae1fb144f653341794459e86f5b23cf3e786a735def71f564089"}, ] [package.dependencies] astroid = [ {version = ">=2.7", markers = "python_version < \"3.12\""}, - {version = ">=3.0.0a1", markers = "python_version >= \"3.12\""}, + {version = ">=3", markers = "python_version >= \"3.12\""}, ] Jinja2 = "*" PyYAML = "*" diff --git a/src/auto_archiver/modules/thumbnail_enricher/__manifest__.py b/src/auto_archiver/modules/thumbnail_enricher/__manifest__.py index e47397f..1bd23b5 100644 --- a/src/auto_archiver/modules/thumbnail_enricher/__manifest__.py +++ b/src/auto_archiver/modules/thumbnail_enricher/__manifest__.py @@ -7,8 +7,12 @@ "bin": ["ffmpeg"] }, "configs": { - "thumbnails_per_minute": {"default": 60, "help": "how many thumbnails to generate per minute of video, can be limited by max_thumbnails"}, - "max_thumbnails": {"default": 16, "help": "limit the number of thumbnails to generate per video, 0 means no limit"}, + "thumbnails_per_minute": {"default": 60, + "type": "int", + "help": "how many thumbnails to generate per minute of video, can be limited by max_thumbnails"}, + "max_thumbnails": {"default": 16, + "type": "int", + "help": "limit the number of thumbnails to generate per video, 0 means no limit"}, }, "description": """ Generates thumbnails for video files to provide visual previews. diff --git a/src/auto_archiver/modules/thumbnail_enricher/thumbnail_enricher.py b/src/auto_archiver/modules/thumbnail_enricher/thumbnail_enricher.py index e0ac937..8178cd8 100644 --- a/src/auto_archiver/modules/thumbnail_enricher/thumbnail_enricher.py +++ b/src/auto_archiver/modules/thumbnail_enricher/thumbnail_enricher.py @@ -42,7 +42,7 @@ class ThumbnailEnricher(Enricher): logger.error(f"error getting duration of video {m.filename}: {e}") return - num_thumbs = int(min(max(1, duration * self.thumbnails_per_minute), self.max_thumbnails)) + num_thumbs = int(min(max(1, (duration / 60) * self.thumbnails_per_minute), self.max_thumbnails)) timestamps = [duration / (num_thumbs + 1) * i for i in range(1, num_thumbs + 1)] thumbnails_media = [] diff --git a/tests/enrichers/test_thumbnail_enricher.py b/tests/enrichers/test_thumbnail_enricher.py new file mode 100644 index 0000000..eb27b99 --- /dev/null +++ b/tests/enrichers/test_thumbnail_enricher.py @@ -0,0 +1,155 @@ +import pytest +from unittest.mock import patch, MagicMock +from auto_archiver.core import Metadata, Media +from auto_archiver.modules.thumbnail_enricher import ThumbnailEnricher + + +@pytest.fixture +def thumbnail_enricher(setup_module) -> ThumbnailEnricher: + configs: dict = { + "thumbnails_per_minute": 60, + "max_thumbnails": 4, + } + return setup_module("thumbnail_enricher", configs) + + +@pytest.fixture +def metadata_with_video(): + m = Metadata() + m.set_url("https://example.com") + m.add_media(Media(filename="video.mp4").set("id", "video1")) + return m + + +@pytest.fixture +def mock_ffmpeg_environment(): + # Mocking all the ffmpeg calls in one place + with ( + patch("ffmpeg.input") as mock_ffmpeg_input, + patch("os.makedirs") as mock_makedirs, + patch.object(Media, "is_video", return_value=True), + patch( + "ffmpeg.probe", + return_value={ + "streams": [ + {"codec_type": "video", "duration": "120"} + ] # Default 2-minute duration, but can override in tests + }, + ) as mock_probe, + ): + mock_output = MagicMock() + mock_ffmpeg_input.return_value.filter.return_value.output.return_value = ( + mock_output + ) + + yield { + "mock_ffmpeg_input": mock_ffmpeg_input, + "mock_makedirs": mock_makedirs, + "mock_output": mock_output, + "mock_probe": mock_probe, + } + + +@pytest.mark.parametrize("thumbnails_per_minute, max_thumbnails, expected_count", [ + (10, 5, 5), # Capped at max_thumbnails + (1, 10, 2), # Less than max_thumbnails + (60, 7, 7), # Matches exactly +]) +def test_enrich_thumbnail_limits( + thumbnail_enricher, metadata_with_video, mock_ffmpeg_environment, + thumbnails_per_minute, max_thumbnails, expected_count +): + thumbnail_enricher.thumbnails_per_minute = thumbnails_per_minute + thumbnail_enricher.max_thumbnails = max_thumbnails + + thumbnail_enricher.enrich(metadata_with_video) + + assert mock_ffmpeg_environment["mock_output"].run.call_count == expected_count + thumbnails = metadata_with_video.media[0].get("thumbnails") + assert len(thumbnails) == expected_count + +def test_enrich_handles_probe_failure(thumbnail_enricher, metadata_with_video): + with ( + patch("ffmpeg.probe", side_effect=Exception("Probe error")), + patch("os.makedirs"), + patch("loguru.logger.error") as mock_logger, + patch.object(Media, "is_video", return_value=True), + ): + thumbnail_enricher.enrich(metadata_with_video) + # Ensure error was logged + mock_logger.assert_called_with( + f"error getting duration of video video.mp4: Probe error" + ) + # Ensure no thumbnails were created + thumbnails = metadata_with_video.media[0].get("thumbnails") + assert thumbnails is None + + +def test_enrich_skips_non_video_files(thumbnail_enricher, metadata_with_video): + with ( + patch.object(Media, "is_video", return_value=False), + patch("ffmpeg.input") as mock_ffmpeg, + ): + thumbnail_enricher.enrich(metadata_with_video) + mock_ffmpeg.assert_not_called() + + +@pytest.mark.parametrize("thumbnails_per_minute,max_thumbnails,expected_count", [ + (60, 5, 5), # caught by max + (60, 20, 10), # caught by t/min + (0, 20, 1), # test min caught (1) + (11, 20, 1), # test min caught (1) + (12, 20, 2), # test caught by t/min +]) +def test_enrich_handles_short_video( + thumbnail_enricher, metadata_with_video, mock_ffmpeg_environment, thumbnails_per_minute, max_thumbnails, expected_count +): + # override mock duration + fake_duration = 10 + with patch( + "ffmpeg.probe", + return_value={ "streams": [{"codec_type": "video", "duration": str(fake_duration)}]}, + ): + thumbnail_enricher.thumbnails_per_minute = thumbnails_per_minute + thumbnail_enricher.max_thumbnails = max_thumbnails + + thumbnail_enricher.enrich(metadata_with_video) + assert mock_ffmpeg_environment["mock_output"].run.call_count == expected_count + thumbnails = metadata_with_video.media[0].get("thumbnails") + assert len(thumbnails) == expected_count + + +def test_uses_existing_duration( + thumbnail_enricher, metadata_with_video, mock_ffmpeg_environment +): + metadata_with_video.media[0].set("duration", 60) + thumbnail_enricher.enrich(metadata_with_video) + mock_ffmpeg_environment["mock_probe"].assert_not_called() + assert mock_ffmpeg_environment["mock_output"].run.call_count == 4 + + +def test_enrich_metadata_structure(thumbnail_enricher, metadata_with_video, mock_ffmpeg_environment): + fake_duration = 120 + with patch("ffmpeg.probe", return_value={ + 'streams': [{'codec_type': 'video', 'duration': str(fake_duration)}] + }): + thumbnail_enricher.thumbnails_per_minute = 2 + thumbnail_enricher.max_thumbnails = 4 + + thumbnail_enricher.enrich(metadata_with_video) + + media_item = metadata_with_video.media[0] + thumbnails = media_item.get("thumbnails") + + # Assert normal metadata + assert media_item.get("id") == "video1" + assert media_item.get("duration") == fake_duration + # Evenly spaced timestamps + expected_timestamps = ["24.000s", "48.000s", "72.000s", "96.000s"] + assert thumbnails is not None + assert len(thumbnails) == 4 + + for index, thumbnail in enumerate(thumbnails): + assert thumbnail.filename is not None + assert thumbnail.properties.get("id") == f"thumbnail_{index}" + assert thumbnail.properties.get("timestamp") == expected_timestamps[index] From 27f9287b657a3d2f6b09bd03ec3ca58ca1609ddd Mon Sep 17 00:00:00 2001 From: Miguel Sozinho Ramalho <19508417+msramalho@users.noreply.github.com> Date: Wed, 12 Feb 2025 17:37:36 +0000 Subject: [PATCH 037/160] markdown fixes --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 20ad1de..368a904 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,9 @@ Auto Archiver is a Python tool to automatically archive content on the web in a secure and verifiable way. It takes URLs from different sources (e.g. a CSV file, Google Sheets, command line etc.) and archives the content of each one. It can archive social media posts, videos, images and webpages. Content can enriched, then saved either locally or remotely (S3 bucket, Google Drive). The status of the archiving process can be appended to a CSV report, or if using Google Sheets – back to the original sheet.
-**[See the Auto Arciver documentation for more information.](https://auto-archiver.readthedocs.io/en/latest/)** + +**[See the Auto Archiver documentation for more information.](https://auto-archiver.readthedocs.io/en/latest/)** +
Read the [article about Auto Archiver on bellingcat.com](https://www.bellingcat.com/resources/2022/09/22/preserve-vital-online-content-with-bellingcats-auto-archiver-tool/). From cbe98c729d9a4928889b5ece924eb62db1f41017 Mon Sep 17 00:00:00 2001 From: erinhmclark Date: Wed, 12 Feb 2025 19:32:40 +0000 Subject: [PATCH 038/160] Enricher tests --- .../screenshot_enricher.py | 8 +- tests/conftest.py | 20 +- tests/enrichers/test_metadata_enricher.py | 76 +++++++ tests/enrichers/test_pdq_hash_enricher.py | 84 +++++++ tests/enrichers/test_screenshot_enricher.py | 205 ++++++++++++++++++ tests/enrichers/test_ssl_enricher.py | 54 +++++ tests/enrichers/test_whisper_enricher.py | 93 ++++++++ 7 files changed, 538 insertions(+), 2 deletions(-) create mode 100644 tests/enrichers/test_metadata_enricher.py create mode 100644 tests/enrichers/test_pdq_hash_enricher.py create mode 100644 tests/enrichers/test_screenshot_enricher.py create mode 100644 tests/enrichers/test_ssl_enricher.py create mode 100644 tests/enrichers/test_whisper_enricher.py diff --git a/src/auto_archiver/modules/screenshot_enricher/screenshot_enricher.py b/src/auto_archiver/modules/screenshot_enricher/screenshot_enricher.py index e1da99d..832d0f8 100644 --- a/src/auto_archiver/modules/screenshot_enricher/screenshot_enricher.py +++ b/src/auto_archiver/modules/screenshot_enricher/screenshot_enricher.py @@ -11,6 +11,10 @@ from auto_archiver.core import Media, Metadata class ScreenshotEnricher(Enricher): + def __init__(self, webdriver_factory=None): + super().__init__() + self.webdriver_factory = webdriver_factory or Webdriver + def enrich(self, to_enrich: Metadata) -> None: url = to_enrich.get_url() @@ -20,7 +24,8 @@ class ScreenshotEnricher(Enricher): logger.debug(f"Enriching screenshot for {url=}") auth = self.auth_for_site(url) - with Webdriver(self.width, self.height, self.timeout, facebook_accept_cookies='facebook.com' in url, + with self.webdriver_factory( + self.width, self.height, self.timeout, facebook_accept_cookies='facebook.com' in url, http_proxy=self.http_proxy, print_options=self.print_options, auth=auth) as driver: try: driver.get(url) @@ -38,3 +43,4 @@ class ScreenshotEnricher(Enricher): logger.info("TimeoutException loading page for screenshot") except Exception as e: logger.error(f"Got error while loading webdriver for screenshot enricher: {e}") + diff --git a/tests/conftest.py b/tests/conftest.py index 8675fbc..d7f484f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,6 +6,8 @@ import pickle from tempfile import TemporaryDirectory from typing import Dict, Tuple import hashlib +from unittest.mock import patch + import pytest from auto_archiver.core.metadata import Metadata from auto_archiver.core.module import get_module, _LAZY_LOADED_MODULES @@ -128,4 +130,20 @@ def unpickle(): test_data_dir = os.path.join(os.path.dirname(__file__), "data", "test_files") with open(os.path.join(test_data_dir, path), "rb") as f: return pickle.load(f) - return _unpickle \ No newline at end of file + return _unpickle + + +@pytest.fixture +def mock_python_dependencies(): + with patch("auto_archiver.core.module") as mock_check_python_dep: + # Mock all Python dependencies as available + mock_check_python_dep.return_value = True + yield mock_check_python_dep + + +@pytest.fixture +def mock_binary_dependencies(): + with patch("shutil.which") as mock_shutil_which: + # Mock all binary dependencies as available + mock_shutil_which.return_value = "/usr/bin/fake_binary" + yield mock_shutil_which diff --git a/tests/enrichers/test_metadata_enricher.py b/tests/enrichers/test_metadata_enricher.py new file mode 100644 index 0000000..314fca7 --- /dev/null +++ b/tests/enrichers/test_metadata_enricher.py @@ -0,0 +1,76 @@ +from unittest.mock import MagicMock, patch, Mock + +import pytest + +from auto_archiver.core import Metadata, Media + + +@pytest.fixture +def mock_media(): + """Creates a mock Media object.""" + mock: Media = MagicMock(spec=Media) + mock.filename = "mock_file.txt" + return mock + + +@pytest.fixture +def enricher(setup_module): + return setup_module("metadata_enricher", {}) + + +@pytest.mark.parametrize( + "output,expected", + [ + ("Key1: Value1\nKey2: Value2", {"Key1": "Value1", "Key2": "Value2"}), + ("InvalidLine", {}), + ("", {}), + ], +) +@patch("subprocess.run") +def test_get_metadata(mock_run, enricher, output, expected): + mock_run.return_value.stdout = output + mock_run.return_value.stderr = "" + mock_run.return_value.returncode = 0 + + result = enricher.get_metadata("test.jpg") + assert result == expected + mock_run.assert_called_once_with( + ["exiftool", "test.jpg"], capture_output=True, text=True + ) + + +@patch("subprocess.run") +def test_get_metadata_exiftool_not_found(mock_run, enricher): + mock_run.side_effect = FileNotFoundError + result = enricher.get_metadata("test.jpg") + assert result == {} + + +def test_enrich_sets_metadata(enricher): + media1 = Mock(filename="img1.jpg") + media2 = Mock(filename="img2.jpg") + metadata = Mock() + metadata.media = [media1, media2] + enricher.get_metadata = lambda f: {"key": "value"} if f == "img1.jpg" else {} + + enricher.enrich(metadata) + + media1.set.assert_called_once_with("metadata", {"key": "value"}) + media2.set.assert_not_called() + assert metadata.media == [media1, media2] + + +def test_enrich_empty_media(enricher): + metadata = Mock() + metadata.media = [] + # Should not raise errors + enricher.enrich(metadata) + + +@patch("loguru.logger.error") +@patch("subprocess.run") +def test_get_metadata_error_handling(mock_run, mock_logger_error, enricher): + mock_run.side_effect = Exception("Test error") + result = enricher.get_metadata("test.jpg") + assert result == {} + mock_logger_error.assert_called_once() diff --git a/tests/enrichers/test_pdq_hash_enricher.py b/tests/enrichers/test_pdq_hash_enricher.py new file mode 100644 index 0000000..e90cd22 --- /dev/null +++ b/tests/enrichers/test_pdq_hash_enricher.py @@ -0,0 +1,84 @@ +from unittest.mock import patch + +import pytest +from PIL import UnidentifiedImageError + +from auto_archiver.core import Metadata, Media +from auto_archiver.modules.pdq_hash_enricher import PdqHashEnricher + + +@pytest.fixture +def enricher(setup_module): + return setup_module("pdq_hash_enricher", {}) + + +@pytest.fixture +def metadata_with_images(): + m = Metadata() + m.set_url("https://example.com") + m.add_media(Media(filename="image1.jpg", key="image1")) + m.add_media(Media(filename="image2.jpg", key="image2")) + return m + + +def test_successful_enrich(metadata_with_images): + with ( + patch("pdqhash.compute", return_value=([1, 0, 1, 0] * 64, 100)), + patch("PIL.Image.open"), + patch.object(Media, "is_image", return_value=True) as mock_is_image, + ): + enricher = PdqHashEnricher() + enricher.enrich(metadata_with_images) + + # Ensure the hash is set for image media + for media in metadata_with_images.media: + assert media.get("pdq_hash") is not None + + +def test_enrich_skip_non_image(metadata_with_images): + with ( + patch.object(Media, "is_image", return_value=False), + patch("pdqhash.compute") as mock_pdq, + ): + enricher = PdqHashEnricher() + enricher.enrich(metadata_with_images) + mock_pdq.assert_not_called() + + +def test_enrich_handles_corrupted_image(metadata_with_images): + with ( + patch("PIL.Image.open", side_effect=UnidentifiedImageError("Corrupted image")), + patch("pdqhash.compute") as mock_pdq, + patch("loguru.logger.error") as mock_logger, + ): + enricher = PdqHashEnricher() + enricher.enrich(metadata_with_images) + + assert mock_logger.call_count == len(metadata_with_images.media) + mock_pdq.assert_not_called() + + +@pytest.mark.parametrize( + "media_id, should_have_hash", + [ + ("screenshot", False), + ("warc-file-123", False), + ("regular-image", True), + ] +) +def test_enrich_excludes_by_filetype(media_id, should_have_hash): + metadata = Metadata() + metadata.set_url("https://example.com") + metadata.add_media(Media(filename="image.jpg").set("id", media_id)) + + with ( + patch("pdqhash.compute", return_value=([1, 0, 1, 0] * 64, 100)), + patch("PIL.Image.open"), + patch.object(Media, "is_image", return_value=True), + ): + enricher = PdqHashEnricher() + enricher.enrich(metadata) + + media_item = metadata.media[0] + assert (media_item.get("pdq_hash") is not None) == should_have_hash + diff --git a/tests/enrichers/test_screenshot_enricher.py b/tests/enrichers/test_screenshot_enricher.py new file mode 100644 index 0000000..3998deb --- /dev/null +++ b/tests/enrichers/test_screenshot_enricher.py @@ -0,0 +1,205 @@ +import base64 +from unittest.mock import patch, MagicMock + +import pytest +from selenium.common.exceptions import TimeoutException + +from auto_archiver.core import Metadata, Media +from auto_archiver.modules.screenshot_enricher import ScreenshotEnricher + + +@pytest.fixture +def mock_selenium_env(): + # Patches Selenium calls and driver checks in one place. + with ( + patch("shutil.which") as mock_which, + patch("auto_archiver.utils.webdriver.CookieSettingDriver") as mock_driver_class, + patch( + "selenium.webdriver.common.selenium_manager.SeleniumManager.binary_paths" + ) as mock_binary_paths, + patch("pathlib.Path.is_file", return_value=True), + patch("subprocess.Popen") as mock_popen, + patch( + "selenium.webdriver.common.service.Service.is_connectable", + return_value=True, + ), + patch("selenium.webdriver.FirefoxOptions") as mock_firefox_options, + ): + # Mock driver existence + def mock_which_side_effect(dep): + return "/mock/geckodriver" if dep == "geckodriver" else None + + mock_which.side_effect = mock_which_side_effect + # Mock binary paths + mock_binary_paths.return_value = { + "driver_path": "/mock/driver", + "browser_path": "/mock/browser", + } + # Popen + mock_proc = MagicMock() + mock_proc.poll.return_value = None + mock_popen.return_value = mock_proc + # CookieSettingDriver -> returns a mock driver + mock_driver = MagicMock() + mock_driver_class.return_value = mock_driver + # FirefoxOptions + mock_options_instance = MagicMock() + mock_firefox_options.return_value = mock_options_instance + yield mock_driver, mock_driver_class, mock_options_instance + + +@pytest.fixture +def common_patches(tmp_path): + with ( + patch("auto_archiver.utils.url.is_auth_wall", return_value=False), + patch("os.path.join", return_value=str(tmp_path / "test.png")), + patch("time.sleep"), + ): + yield + + +@pytest.fixture +def screenshot_enricher(setup_module, mock_binary_dependencies) -> ScreenshotEnricher: + configs: dict = { + "width": 1280, + "height": 720, + "timeout": 60, + "sleep_before_screenshot": 4, + "http_proxy": "", + "save_to_pdf": "False", + "print_options": {}, + } + return setup_module("screenshot_enricher", configs) + + +@pytest.fixture +def metadata_with_video(): + m = Metadata() + m.set_url("https://example.com") + m.add_media(Media(filename="video.mp4").set("id", "video1")) + return m + + +def test_enrich_adds_screenshot( + screenshot_enricher, + metadata_with_video, + mock_selenium_env, + common_patches, + tmp_path, +): + mock_driver, mock_driver_class, mock_options_instance = mock_selenium_env + screenshot_enricher.enrich(metadata_with_video) + mock_driver_class.assert_called_once_with( + cookies=None, + cookiejar=None, + facebook_accept_cookies=False, + options=mock_options_instance, + ) + # Verify the actual calls on the returned mock_driver + mock_driver.get.assert_called_once_with("https://example.com") + mock_driver.save_screenshot.assert_called_once_with(str(tmp_path / "test.png")) + # Check that the media was added (2 = original video + screenshot) + assert len(metadata_with_video.media) == 2 + assert metadata_with_video.media[1].properties.get("id") == "screenshot" + + +@pytest.mark.parametrize( + "url,is_auth", + [ + ("https://example.com", False), + ("https://private.com", True), + ], +) +def test_enrich_auth_wall( + screenshot_enricher, + metadata_with_video, + mock_selenium_env, + common_patches, + url, + is_auth, +): + # Testing with and without is_auth_wall + mock_driver, mock_driver_class, _ = mock_selenium_env + with patch("auto_archiver.utils.url.is_auth_wall", return_value=is_auth): + metadata_with_video.set_url(url) + screenshot_enricher.enrich(metadata_with_video) + + if is_auth: + mock_driver.get.assert_not_called() + assert len(metadata_with_video.media) == 1 + assert metadata_with_video.media[0].properties.get("id") == "video1" + else: + mock_driver.get.assert_called_once_with(url) + assert len(metadata_with_video.media) == 2 + assert metadata_with_video.media[1].properties.get("id") == "screenshot" + + +def test_handle_timeout_exception( + screenshot_enricher, metadata_with_video, mock_selenium_env +): + mock_driver, mock_driver_class, mock_options_instance = mock_selenium_env + + mock_driver.get.side_effect = TimeoutException + with patch("loguru.logger.info") as mock_log: + screenshot_enricher.enrich(metadata_with_video) + mock_log.assert_called_once_with("TimeoutException loading page for screenshot") + assert len(metadata_with_video.media) == 1 + + +def test_handle_general_exception( + screenshot_enricher, metadata_with_video, mock_selenium_env +): + """Test proper handling of unexpected general exceptions""" + mock_driver, mock_driver_class, mock_options_instance = mock_selenium_env + # Simulate a generic exception when save_screenshot is called + mock_driver.get.return_value = None + mock_driver.save_screenshot.side_effect = Exception("Unexpected Error") + + with patch("loguru.logger.error") as mock_log: + screenshot_enricher.enrich(metadata_with_video) + # Verify that the exception was logged with the log + mock_log.assert_called_once_with( + "Got error while loading webdriver for screenshot enricher: Unexpected Error" + ) + # And no new media was added due to the error + assert len(metadata_with_video.media) == 1 + + +def test_pdf_creation(screenshot_enricher, metadata_with_video, mock_selenium_env): + """Test PDF creation when save_to_pdf is enabled""" + mock_driver, mock_driver_class, mock_options_instance = mock_selenium_env + + # Override the save_to_pdf option + screenshot_enricher.save_to_pdf = True + # Mock the print_page method to return base64-encoded content + mock_driver.print_page.return_value = base64.b64encode(b"fake_pdf_content").decode( + "utf-8" + ) + with ( + patch("os.path.join", side_effect=lambda *args: f"{args[-1]}"), + patch( + "auto_archiver.modules.screenshot_enricher.screenshot_enricher.random_str", + return_value="fixed123", + ), + patch("builtins.open", new_callable=MagicMock()) as mock_open, + patch("loguru.logger.error") as mock_log, + ): + screenshot_enricher.enrich(metadata_with_video) + + # Verify screenshot and PDF creation + mock_driver.save_screenshot.assert_called_once() + mock_driver.print_page.assert_called_once_with(mock_driver.print_options) + + # Check that PDF file was opened and written + mock_open.assert_any_call("pdf_fixed123.pdf", "wb") + # Ensure both screenshot and PDF were added as media + assert len(metadata_with_video.media) == 3 # Original video + screenshot + PDF + assert metadata_with_video.media[1].properties.get("id") == "screenshot" + assert metadata_with_video.media[2].properties.get("id") == "pdf" + + +@pytest.fixture(autouse=True) +def cleanup_files(tmp_path): + yield + for file in tmp_path.iterdir(): + file.unlink() diff --git a/tests/enrichers/test_ssl_enricher.py b/tests/enrichers/test_ssl_enricher.py new file mode 100644 index 0000000..c4d2dc5 --- /dev/null +++ b/tests/enrichers/test_ssl_enricher.py @@ -0,0 +1,54 @@ +import ssl +from unittest.mock import patch, mock_open + +import pytest + +from auto_archiver.core import Metadata, Media + + +@pytest.fixture +def enricher(setup_module): + configs: dict = { + "skip_when_nothing_archived": "True", + } + return setup_module("ssl_enricher", configs) + + +@pytest.fixture +def metadata(): + m = Metadata() + m.set_url("https://example.com") + m.add_media(Media("tests/data/testfile_1.txt")) + m.add_media(Media("tests/data/testfile_2.txt")) + return m + + +def test_http_raises(metadata, enricher): + metadata.set_url("http://example.com") + with pytest.raises(AssertionError) as exc_info: + enricher.enrich(metadata) + assert "Invalid URL scheme" in str(exc_info.value) + + +def test_empty_metadata(metadata, enricher): + metadata.media = [] + assert enricher.enrich(metadata) is None + + +def test_ssl_enrich(metadata, enricher): + with patch("ssl.get_server_certificate", return_value="TEST_CERT"), \ + patch("builtins.open", mock_open()) as mock_file: + enricher.enrich(metadata) + + ssl.get_server_certificate.assert_called_once_with(("example.com", 443)) + mock_file.assert_called_once_with(f"{enricher.tmp_dir}/example-com.pem", "w") + mock_file().write.assert_called_once_with("TEST_CERT") + # Ensure the certificate is added to metadata + assert any(media.filename.endswith("example-com.pem") for media in metadata.media) + + +def test_ssl_error_handling(enricher, metadata): + with patch("ssl.get_server_certificate", side_effect=ssl.SSLError("SSL error")): + with pytest.raises(ssl.SSLError, match="SSL error"): + enricher.enrich(metadata) + diff --git a/tests/enrichers/test_whisper_enricher.py b/tests/enrichers/test_whisper_enricher.py new file mode 100644 index 0000000..8a73ed7 --- /dev/null +++ b/tests/enrichers/test_whisper_enricher.py @@ -0,0 +1,93 @@ +import shutil +import sys +import pytest +from unittest.mock import MagicMock, patch +from auto_archiver.core import Metadata, Media +from auto_archiver.modules.s3_storage import S3Storage + +from auto_archiver.modules.whisper_enricher import WhisperEnricher + + +@pytest.fixture +def enricher(): + """Fixture with mocked S3 and API dependencies""" + config = { + "api_endpoint": "http://testapi", + "api_key": "whisper-key", + "include_srt": False, + "timeout": 5, + "action": "translate", + "steps": {"storages": ["s3_storage"]} + } + mock_s3 = MagicMock(spec=S3Storage) + mock_s3.get_cdn_url.return_value = "http://s3.example.com/media.mp3" + instance = WhisperEnricher() + instance.name = "whisper_enricher" + instance.display_name = "Whisper Enricher" + instance.config_setup({instance.name: config}) + # bypassing the setup method and mocking S3 setup + instance.stores = config['steps']['storages'] + instance.s3 = mock_s3 + yield instance, mock_s3 + + +@pytest.fixture +def metadata(): + metadata = Metadata() + metadata.set_url("http://test.url") + metadata.set_title("test title") + return metadata + + +@pytest.fixture +def mock_requests(): + with patch("auto_archiver.modules.whisper_enricher.whisper_enricher.requests") as mock_requests: + mock_response = MagicMock() + mock_response.status_code = 201 + mock_response.json.return_value = {"id": "job123"} + mock_requests.post.return_value = mock_response + yield mock_requests + + +def test_successful_job_submission(enricher, metadata, mock_requests): + """Test successful media processing with S3 configured""" + whisper, mock_s3 = enricher + # Configure mock S3 URL to match test expectation + mock_s3.get_cdn_url.return_value = "http://cdn.example.com/test.mp4" + + # Create test media with matching CDN URL + m = Media("test.mp4") + m.mimetype = "video/mp4" + m.add_url(mock_s3.get_cdn_url.return_value) + metadata.media = [m] + + # Mock the complete API interaction chain + mock_status_response = MagicMock() + mock_status_response.status_code = 200 + mock_status_response.json.return_value = { + "status": "success", + "meta": {} + } + mock_artifacts_response = MagicMock() + mock_artifacts_response.status_code = 200 + mock_artifacts_response.json.return_value = [{ + "data": [{"start": 0, "end": 5, "text": "test transcript"}] + }] + # Set up mock response sequence + mock_requests.get.side_effect = [ + mock_status_response, # First call: status check + mock_artifacts_response # Second call: artifacts check + ] + # Run enrichment (without opening file) + whisper.enrich(metadata) + # Check API interactions + mock_requests.post.assert_called_once_with( + "http://testapi/jobs", + json={"url": "http://cdn.example.com/test.mp4", "type": "translate"}, + headers={"Authorization": "Bearer whisper-key"} + ) + # Verify job status checks + assert mock_requests.get.call_count == 2 + assert "artifact_0_text" in metadata.media[0].get("whisper_model") + assert "test transcript" in metadata.metadata.get("content") + From 319c1e8f92531db0d89f72fa72b3d06c7172c06c Mon Sep 17 00:00:00 2001 From: erinhmclark Date: Fri, 14 Feb 2025 09:48:37 +0000 Subject: [PATCH 039/160] Add more tests. --- poetry.lock | 30 +++++------ .../whisper_enricher/whisper_enricher.py | 3 +- tests/conftest.py | 8 --- .../metadata_enricher_ytshort_expected.pickle | Bin 0 -> 12524 bytes .../metadata_enricher_ytshort_input.pickle | Bin 0 -> 10840 bytes tests/enrichers/test_metadata_enricher.py | 15 +++++- tests/enrichers/test_ssl_enricher.py | 2 + tests/enrichers/test_thumbnail_enricher.py | 6 +-- tests/enrichers/test_whisper_enricher.py | 48 ++++++++++++++++-- tests/test_metadata.py | 23 ++++++++- 10 files changed, 102 insertions(+), 33 deletions(-) create mode 100644 tests/data/metadata/metadata_enricher_ytshort_expected.pickle create mode 100644 tests/data/metadata/metadata_enricher_ytshort_input.pickle diff --git a/poetry.lock b/poetry.lock index decadca..d61b908 100644 --- a/poetry.lock +++ b/poetry.lock @@ -172,18 +172,18 @@ lxml = ["lxml"] [[package]] name = "boto3" -version = "1.36.17" +version = "1.36.19" description = "The AWS SDK for Python" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "boto3-1.36.17-py3-none-any.whl", hash = "sha256:59bcf0c4b04d9cc36f8b418ad17ab3c4a99a21a175d2fad7096aa21cbe84630b"}, - {file = "boto3-1.36.17.tar.gz", hash = "sha256:5ecae20e780a3ce9afb3add532b61c466a8cb8960618e4fa565b3883064c1346"}, + {file = "boto3-1.36.19-py3-none-any.whl", hash = "sha256:7784590369a9d545bb07b2de56b6ce4d5a5e232883a957f704c3f842caeba155"}, + {file = "boto3-1.36.19.tar.gz", hash = "sha256:8c2c2a4ccdfe35dd2611ee1b7473dd2383948415c777e42dc4e7f1ebe371fe8c"}, ] [package.dependencies] -botocore = ">=1.36.17,<1.37.0" +botocore = ">=1.36.19,<1.37.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.11.0,<0.12.0" @@ -192,14 +192,14 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.36.17" +version = "1.36.19" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "botocore-1.36.17-py3-none-any.whl", hash = "sha256:069858b2fd693548035d7fd53a774e37e4260fea64e0ac9b8a3aee904f9321df"}, - {file = "botocore-1.36.17.tar.gz", hash = "sha256:cec13e0a7ce78e71aad0b397581b4e81824c7981ef4c261d2e296d200c399b09"}, + {file = "botocore-1.36.19-py3-none-any.whl", hash = "sha256:98882c106fec4c08678ea028199f7f5119550fab95d682b30846f7aae04b7bec"}, + {file = "botocore-1.36.19.tar.gz", hash = "sha256:cdf6729f601f82b1acdb9004b1f88b57cfb470f576394cdb3bbf5150f7fafb5b"}, ] [package.dependencies] @@ -860,14 +860,14 @@ tool = ["click (>=6.0.0)"] [[package]] name = "googleapis-common-protos" -version = "1.66.0" +version = "1.67.0" description = "Common protobufs used in Google APIs" optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "googleapis_common_protos-1.66.0-py2.py3-none-any.whl", hash = "sha256:d7abcd75fabb2e0ec9f74466401f6c119a0b498e27370e9be4c94cb7e382b8ed"}, - {file = "googleapis_common_protos-1.66.0.tar.gz", hash = "sha256:c3e7b33d15fdca5374cc0a7346dd92ffa847425cc4ea941d970f13680052ec8c"}, + {file = "googleapis_common_protos-1.67.0-py2.py3-none-any.whl", hash = "sha256:579de760800d13616f51cf8be00c876f00a9f146d3e6510e19d1f4111758b741"}, + {file = "googleapis_common_protos-1.67.0.tar.gz", hash = "sha256:21398025365f138be356d5923e9168737d94d46a72aefee4a6110a1f23463c86"}, ] [package.dependencies] @@ -1235,14 +1235,14 @@ files = [ [[package]] name = "myst-parser" -version = "4.0.0" +version = "4.0.1" description = "An extended [CommonMark](https://spec.commonmark.org/) compliant parser," optional = false python-versions = ">=3.10" groups = ["docs"] files = [ - {file = "myst_parser-4.0.0-py3-none-any.whl", hash = "sha256:b9317997552424448c6096c2558872fdb6f81d3ecb3a40ce84a7518798f3f28d"}, - {file = "myst_parser-4.0.0.tar.gz", hash = "sha256:851c9dfb44e36e56d15d05e72f02b80da21a9e0d07cba96baf5e2d476bb91531"}, + {file = "myst_parser-4.0.1-py3-none-any.whl", hash = "sha256:9134e88959ec3b5780aedf8a99680ea242869d012e8821db3126d427edc9c95d"}, + {file = "myst_parser-4.0.1.tar.gz", hash = "sha256:5cfea715e4f3574138aecbf7d54132296bfd72bb614d31168f48c477a830a7c4"}, ] [package.dependencies] @@ -1254,10 +1254,10 @@ pyyaml = "*" sphinx = ">=7,<9" [package.extras] -code-style = ["pre-commit (>=3.0,<4.0)"] +code-style = ["pre-commit (>=4.0,<5.0)"] linkify = ["linkify-it-py (>=2.0,<3.0)"] rtd = ["ipython", "sphinx (>=7)", "sphinx-autodoc2 (>=0.5.0,<0.6.0)", "sphinx-book-theme (>=1.1,<2.0)", "sphinx-copybutton", "sphinx-design", "sphinx-pyscript", "sphinx-tippy (>=0.4.3)", "sphinx-togglebutton", "sphinxext-opengraph (>=0.9.0,<0.10.0)", "sphinxext-rediraffe (>=0.2.7,<0.3.0)"] -testing = ["beautifulsoup4", "coverage[toml]", "defusedxml", "pytest (>=8,<9)", "pytest-cov", "pytest-param-files (>=0.6.0,<0.7.0)", "pytest-regressions", "sphinx-pytest"] +testing = ["beautifulsoup4", "coverage[toml]", "defusedxml", "pygments (<2.19)", "pytest (>=8,<9)", "pytest-cov", "pytest-param-files (>=0.6.0,<0.7.0)", "pytest-regressions", "sphinx-pytest"] testing-docutils = ["pygments", "pytest (>=8,<9)", "pytest-param-files (>=0.6.0,<0.7.0)"] [[package]] diff --git a/src/auto_archiver/modules/whisper_enricher/whisper_enricher.py b/src/auto_archiver/modules/whisper_enricher/whisper_enricher.py index 89579f9..917ab85 100644 --- a/src/auto_archiver/modules/whisper_enricher/whisper_enricher.py +++ b/src/auto_archiver/modules/whisper_enricher/whisper_enricher.py @@ -29,8 +29,7 @@ class WhisperEnricher(Enricher): job_results = {} for i, m in enumerate(to_enrich.media): if m.is_video() or m.is_audio(): - # TODO: this used to pass all storage items to store now - # Now only passing S3, the rest will get added later in the usual order (?) + # Only storing S3, the rest will get added later in the usual order (?) m.store(url=url, metadata=to_enrich, storages=[self.s3]) try: job_id = self.submit_job(m) diff --git a/tests/conftest.py b/tests/conftest.py index d7f484f..f7ed4b7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -133,14 +133,6 @@ def unpickle(): return _unpickle -@pytest.fixture -def mock_python_dependencies(): - with patch("auto_archiver.core.module") as mock_check_python_dep: - # Mock all Python dependencies as available - mock_check_python_dep.return_value = True - yield mock_check_python_dep - - @pytest.fixture def mock_binary_dependencies(): with patch("shutil.which") as mock_shutil_which: diff --git a/tests/data/metadata/metadata_enricher_ytshort_expected.pickle b/tests/data/metadata/metadata_enricher_ytshort_expected.pickle new file mode 100644 index 0000000000000000000000000000000000000000..23ce5f6101dbd8b63b5aa4062a97d753d748ebc8 GIT binary patch literal 12524 zcmbVSTW=iKk(Mmcq9`emlXViqi|oNV3p=sZocj!k&4r>ap=FM>vSe9{F^Ha-Gt+JM zOi%j4Aq6au07(`L^qYGhgZ!8LfXzdGLEe&ok+169dMG;?t|eNWnmVVdPTlLAUtay+ zKi4ktf5qE=p2bIgG77>InRuf(k={&Z{@Bm_ve-N{zkXT%@K@!3mG2d=rFCX^GVj~N)2weQ@^T5x<-hoKuG>`l& zjOSt$=h0Y1aeOSI@EAx;AEM6|WpER`{^iEoH(TGr8J5L2NT56mBQJZ2zqIkoJR5;e z92qlrBVRBAnwZh?~h#Fpr~n z8p`wo;m^k+h?gP_q>%HIFa_0AWcW0JeiKPFSI)>2`+*!C3))|yMUhzea~TO7AW%V6 z?rwdZMk3hTcI`hOMcvvXznxBhnub4a>^wX;Ieqp*cBid7Cl4oLYwI1QIq?qUdS|F) z9zk92SnkhoZll1TgYXd>LGSBGYOL*N`wyZ!4_`idA@`p*!{=Yj|KnbM`f_iN3^GsH z0Ln+77h5NxTpqy^=2?08f0v6JOF3NlQ>m!D|8-QBel`kzdh+8HU2cJAIgJw>b@_Q& z+`2!{WCC5yXA-;n#Vr^q{i?}5dac+ZyE7l(Dz0hf%VJAw9y^xBcZ}qdIEvz>Obox9 zzb-bF)ybp`dWAMYAdsPb`)BSbd{d{)rz({xFQf3@Ukjkq@JA zgd>go`83C|mBnjvUKZb+gpo|cm-5J8ATXSkANJq<hQJ6M-sdj|$kSF|dhn}iTgGC6lRy&hmhU4#?jOZ|>0F_Sugz?~1&ste%_ z`YY@;JQiv%G^88eP91_H_83T0q4=o-u$bfx4%F$=D)+fZM>Fyh18FI-mv%|ZYY<8n zg!5w~QphpSPdUO1pVo^d;^w+I=Y(r{uQ1y3I()K0g^w<2I-H-t*%4t4!)>og2N7E% zX?r`Q{}UO6BlwHK>v(MfQ?OQ%oz8{BuicUYwOMu{#6cfn+!5-w!O+q_bC4dLdWK|a zKwYm^12g?T}mKGlR6_)kt%pRnU&w2PJ}^jP1G|uf+mk{XqR<8&OS?o)K_Go?FDbh z_7i4s_jS<*ee0O@p4TF7P3*^K4v0}~x#)Ra3X$j>jLGeIb@F(V1nc-rL3k1;Q)!pM zmC19Lh_``T$RXV&7A#e%CZ3_vJtL3y(IsR=4GB{1sP^D=f~xK!c!NGVj3l#m*Qn{Z zWH@Tnl~R{sRspfRRRe2i&yJ{$G8zPs#kOh&fGx!OKL8w~=MrszVnpPDAY$4U)rJD9 zL;zSHZP34Ky}bd~R!KvjG61Zep&X%Lb3oR!;|(dDlx8iV(ej$)Scpid#musn*JEQg zA+4c3H=Hfd0Q&O`ebXEX2Bt{VNDSyu?Li+dj7L5f+9Pf&@|9u*3~*{uq292mee;#D z-RLOett8n7lB~)x=tYT^Dpz#sa68~Ts3oJ0buBDZFXM4YHHdFdud7l7wc3W$o;r5q z@;Fb>#XC6r`V59|B2HsRRX|QfJ02Gi%$cJmXn$%))sBMqAT?YBZ_tksV@xU0tf}ba zJQWbO1m%IKa+{9%NIn-e__ zf+~5Uyy7IP>w4OPHljrrLL2l~qG%JYOOz`5LAE4gLFR~z+ce>{=Za;3*rs^0WZ3Si zBBFXK$g@-@4<+K13Aa!X@2)hWW>YNO`dCOU*wN8tmTi~S?3q#!qdW9J_FPduvmRa1 z4^(a;k-p7tCI`C~4M?0PW2-h4)VcvnQEEUoWdAhQ)RbIQ5O6ya2LK?@t%(Qkv8?Da z-P+WSV%8b8;TegXltx}*o0sN}^1W&7PM}ruYAcou03S)3g_B4Mu<2KvPH!l93oh|5 zf>tSXT6I;}!2LAKfN*xM6w?6lF(VQhB%S#yg}M7ixBd>mr4#(k9AB%U)s|2=CeP=B z8rz#Pm3j|qFlmv9*F~fFW>2|=ct*ala>`BR95!A6iOcIzY+E^iI9|*!qBbI+_$RBX zW}y~B4+nURXJ&}$(V6Netwv}|tUyDc6VJsFEAblSxGi?>&}u7RGB$|M%+OF~F2exG zp{*)kM}_s&KdG3XF2!}a*JZPh@IaekObuDo0@FkTcDb!PIu&+=+;6V zLLC6QYY?c06ZodTk&%+#p>FqX$j2nf5KEl%5YWzq-iGvI`2qnKoB z0{f%H#Ex2AbPD8KYv2v~OVq4d1)#!mRWdn1ESDP58$4a;SNiETI(sUXZJ7Z8rx}z- zwlo5a6R>qc0Q5K!%^Lik>oa{Dz)d!6jW?h`hY8_2NT`?1wSzICHoRDk$*ldg^#ISb zZ1~LO!#H-$HekA2Q5I>WM`KQfklPC#IGF>d6h~Z1b0Bz2Yu41-TnG`X)&*Q+2|6-Vy0JFu0kQ>aSNKe4x}@tqw~PmbLmMVA)T4|7 zHRI${)1KEc%-4|~^_k+4hOzJizC0YFIIt_&^Vh&Z1z$- zR7Zdl918v60D((Mgp2j@P`P57%F1CfS{Dw$);P8`8<1M6*wpcv0emKvV{55~&dQAG zJ+&HC{Hi+`Ud3BiYD55acBh(%_@ap})28VG356h=6N6EfEY zhC&8};6ce+m%LyI4G95ock57EWD;XF1Yr~ousSLM0H3CPozbAECxEW$fuS8VMc-5z ziV!1I^3p}|IHq6&{cpfX5hU{l4^ zcQFdr9fpql460LXw26>G4cf~d1(xyLZyHls>*IDy(PnCR(tt9N)1DeCOH{4v;!)$M zN4`v#9Y__0_>78y_z3d(IMMSyhH{PW?j{(<7C%X1bvwZzo*$d3Iv~jJly^*7#1J+D zF5tPV@(nS48$lZinwQ%;e?2tRB=TKdJTe{DL{(PA29ixTf56ez{Y<3_2Jm!m8tVke zAYFtSSH~)z9=X9Lu(W6E#+VtgJ>%>Q+DWN*G^6tiq0uDInGWIu`H(5=nKWA=Ub(2G z%^=o;RjQs%OygK36QCem&{LZ~Ga*>YK%QC}lD=t_*m*&UU1U(}6SEWHqLRc(~BE6~>vZ!;itcQkMzgVWGO2=WE zpyjd-K%6CQIR*eHkBOAVVF2QAUDY1~86{Uc1L4it(0GVMjiIKkvJOfo+EJ;9R>A5p zY`OrR9Z-OjeuUuaK|t8@kxyMv!aW6>iIYuVa8i+D;scOWRMPJ?usix2sy-^i4FFN2 zQ1B#OKo+Y16fOG1`*G;fGmN#d27Pq&$(gy>104h>BJKhyAZ$cr9=b(hG>)zmR*d=S z5N66htYg6fy!>$}d`i_-2x>$RWsl*61yR&cW*}_g*&Fsyzpsxs=%Z;fgfVj^Dwuvc z2%DLHjR1-C(8UElKv)3PotMEidU^TX`={aLDK$@@%7otY30$NO@gA($Kyt0u@T&0v zX4az552bEb9<1r7QP=N;MF*H{N%5SY?12h$BsNySpR31v zfZ`qt(5H!yN21#Bg45!<0z5moR~ENN^=?g0>asQRyQ7W|`M#mP{>VNWydvjQydvC( zSR+A;Uf(_I3bh}pz>Ub{c1(}cgAJG5I*{i*o-a%|o+FG-7Uk&V}PaqHG%s!VuGotE;S z>vV#r>cF-R@jhq~o)YuL6(q$n&x`9vGpIIWgIqDNYey;EQ;y63$%ny*>WOx6Sp3V1 zvu@A)Qw%P~a^mMv=6$i4mchsU;PK~W@PuACzE^CF=v~(TD{Q>+>gL7TaiiH4$7{un zbDrEbp8WUX#NNh<`6094#LzXX8C?zt|t{C#~JaZcV(`5RD(gyQ>beSqI0b%;1WDY~X=R dqX7>Ie%B9v4~@;r@oql!{sx!S6FmR` literal 0 HcmV?d00001 diff --git a/tests/data/metadata/metadata_enricher_ytshort_input.pickle b/tests/data/metadata/metadata_enricher_ytshort_input.pickle new file mode 100644 index 0000000000000000000000000000000000000000..5f1a4eb4317bf69f2ccf59306fb645985a70c648 GIT binary patch literal 10840 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 m`1q?bxKmu6(w8^NG ThumbnailEnricher: - configs: dict = { +def thumbnail_enricher(setup_module, mock_binary_dependencies) -> ThumbnailEnricher: + config: dict = { "thumbnails_per_minute": 60, "max_thumbnails": 4, } - return setup_module("thumbnail_enricher", configs) + return setup_module("thumbnail_enricher", config) @pytest.fixture diff --git a/tests/enrichers/test_whisper_enricher.py b/tests/enrichers/test_whisper_enricher.py index 8a73ed7..873198f 100644 --- a/tests/enrichers/test_whisper_enricher.py +++ b/tests/enrichers/test_whisper_enricher.py @@ -8,6 +8,9 @@ from auto_archiver.modules.s3_storage import S3Storage from auto_archiver.modules.whisper_enricher import WhisperEnricher +TEST_S3_URL = "http://cdn.example.com/test.mp4" + + @pytest.fixture def enricher(): """Fixture with mocked S3 and API dependencies""" @@ -20,7 +23,7 @@ def enricher(): "steps": {"storages": ["s3_storage"]} } mock_s3 = MagicMock(spec=S3Storage) - mock_s3.get_cdn_url.return_value = "http://s3.example.com/media.mp3" + mock_s3.get_cdn_url.return_value = TEST_S3_URL instance = WhisperEnricher() instance.name = "whisper_enricher" instance.display_name = "Whisper Enricher" @@ -53,7 +56,7 @@ def test_successful_job_submission(enricher, metadata, mock_requests): """Test successful media processing with S3 configured""" whisper, mock_s3 = enricher # Configure mock S3 URL to match test expectation - mock_s3.get_cdn_url.return_value = "http://cdn.example.com/test.mp4" + mock_s3.get_cdn_url.return_value = TEST_S3_URL # Create test media with matching CDN URL m = Media("test.mp4") @@ -78,6 +81,7 @@ def test_successful_job_submission(enricher, metadata, mock_requests): mock_status_response, # First call: status check mock_artifacts_response # Second call: artifacts check ] + # Run enrichment (without opening file) whisper.enrich(metadata) # Check API interactions @@ -89,5 +93,43 @@ def test_successful_job_submission(enricher, metadata, mock_requests): # Verify job status checks assert mock_requests.get.call_count == 2 assert "artifact_0_text" in metadata.media[0].get("whisper_model") - assert "test transcript" in metadata.metadata.get("content") + assert metadata.media[0].get("whisper_model") == {'artifact_0_text': 'test transcript', 'job_artifacts_check': 'http://testapi/jobs/job123/artifacts', 'job_id': 'job123', 'job_status_check': 'http://testapi/jobs/job123'} + + + +def test_submit_job(enricher): + """Test job submission method""" + whisper, _ = enricher + m = Media("test.mp4") + m.add_url(TEST_S3_URL) + with patch("auto_archiver.modules.whisper_enricher.whisper_enricher.requests") as mock_requests: + mock_response = MagicMock() + mock_response.status_code = 201 + mock_response.json.return_value = {"id": "job123"} + mock_requests.post.return_value = mock_response + job_id = whisper.submit_job(m) + assert job_id == "job123" + +def test_submit_raises_status(enricher): + whisper, _ = enricher + m = Media("test.mp4") + m.add_url(TEST_S3_URL) + with patch("auto_archiver.modules.whisper_enricher.whisper_enricher.requests") as mock_requests: + mock_response = MagicMock() + mock_response.status_code = 400 + mock_response.json.return_value = {"id": "job123"} + mock_requests.post.return_value = mock_response + with pytest.raises(AssertionError) as exc_info: + whisper.submit_job(m) + assert str(exc_info.value) == "calling the whisper api http://testapi returned a non-success code: 400" + +# @pytest.mark.parametrize("test_url, status", ["http://cdn.example.com/test.mp4",]) +def test_submit_job_fails(enricher): + """Test assertion fails with non-S3 URL""" + whisper, mock_s3 = enricher + m = Media("test.mp4") + m.add_url("http://cdn.wrongurl.com/test.mp4") + with pytest.raises(AssertionError): + whisper.submit_job(m) + diff --git a/tests/test_metadata.py b/tests/test_metadata.py index b07e107..a753936 100644 --- a/tests/test_metadata.py +++ b/tests/test_metadata.py @@ -162,4 +162,25 @@ def test_get_context(): def test_choose_most_complete(): - pass \ No newline at end of file + m_more = Metadata() + m_more.set_title("Title 1") + m_more.set_content("Content 1") + m_more.set_url("https://example.com") + + m_less = Metadata() + m_less.set_title("Title 2") + m_less.set_content("Content 2") + m_less.set_url("https://example.com") + m_less.set_context("key", "value") + + res = Metadata.choose_most_complete([m_more, m_less]) + assert res.metadata.get("title") == "Title 1" + +def test_choose_most_complete_from_pickles(unpickle): + # test most complete from pickles before and after an enricher has run + # Only compares length of media, not the actual media + m_before_enriching = unpickle("/Users/erinclark/PycharmProjects/auto-archiver/tests/data/metadata/metadata_enricher_ytshort_input.pickle") + m_after_enriching = unpickle("/Users/erinclark/PycharmProjects/auto-archiver/tests/data/metadata/metadata_enricher_ytshort_expected.pickle") + # Iterates `for r in results[1:]:` + res = Metadata.choose_most_complete([Metadata(), m_after_enriching, m_before_enriching]) + assert res.media == m_after_enriching.media From b0756a6a3405ef6b860edc070d91c0ce70f4b7b1 Mon Sep 17 00:00:00 2001 From: erinhmclark Date: Fri, 14 Feb 2025 09:57:44 +0000 Subject: [PATCH 040/160] Remove accidental full path. --- tests/test_metadata.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_metadata.py b/tests/test_metadata.py index a753936..c6f3593 100644 --- a/tests/test_metadata.py +++ b/tests/test_metadata.py @@ -179,8 +179,8 @@ def test_choose_most_complete(): def test_choose_most_complete_from_pickles(unpickle): # test most complete from pickles before and after an enricher has run # Only compares length of media, not the actual media - m_before_enriching = unpickle("/Users/erinclark/PycharmProjects/auto-archiver/tests/data/metadata/metadata_enricher_ytshort_input.pickle") - m_after_enriching = unpickle("/Users/erinclark/PycharmProjects/auto-archiver/tests/data/metadata/metadata_enricher_ytshort_expected.pickle") + m_before_enriching = unpickle("tests/data/metadata/metadata_enricher_ytshort_input.pickle") + m_after_enriching = unpickle("tests/data/metadata/metadata_enricher_ytshort_expected.pickle") # Iterates `for r in results[1:]:` res = Metadata.choose_most_complete([Metadata(), m_after_enriching, m_before_enriching]) assert res.media == m_after_enriching.media From 71b41dd9018ff09126943602c69a10bc19fe4a62 Mon Sep 17 00:00:00 2001 From: erinhmclark Date: Fri, 14 Feb 2025 10:05:32 +0000 Subject: [PATCH 041/160] Remove accidental path, yet again. --- tests/conftest.py | 5 ++--- .../metadata_enricher_ytshort_expected.pickle | Bin .../metadata_enricher_ytshort_input.pickle | Bin tests/enrichers/test_metadata_enricher.py | 4 ++-- tests/test_metadata.py | 4 ++-- 5 files changed, 6 insertions(+), 7 deletions(-) rename tests/data/{metadata => }/metadata_enricher_ytshort_expected.pickle (100%) rename tests/data/{metadata => }/metadata_enricher_ytshort_input.pickle (100%) diff --git a/tests/conftest.py b/tests/conftest.py index f7ed4b7..19e1f6b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -124,11 +124,10 @@ def pytest_runtest_setup(item): def unpickle(): """ Returns a helper function that unpickles a file - ** gets the file from the test_files directory: tests/data/test_files ** + ** gets the file from the test_files directory: tests/data/ ** """ def _unpickle(path): - test_data_dir = os.path.join(os.path.dirname(__file__), "data", "test_files") - with open(os.path.join(test_data_dir, path), "rb") as f: + with open(os.path.join("tests/data", path), "rb") as f: return pickle.load(f) return _unpickle diff --git a/tests/data/metadata/metadata_enricher_ytshort_expected.pickle b/tests/data/metadata_enricher_ytshort_expected.pickle similarity index 100% rename from tests/data/metadata/metadata_enricher_ytshort_expected.pickle rename to tests/data/metadata_enricher_ytshort_expected.pickle diff --git a/tests/data/metadata/metadata_enricher_ytshort_input.pickle b/tests/data/metadata_enricher_ytshort_input.pickle similarity index 100% rename from tests/data/metadata/metadata_enricher_ytshort_input.pickle rename to tests/data/metadata_enricher_ytshort_input.pickle diff --git a/tests/enrichers/test_metadata_enricher.py b/tests/enrichers/test_metadata_enricher.py index 9dc410b..c6190ed 100644 --- a/tests/enrichers/test_metadata_enricher.py +++ b/tests/enrichers/test_metadata_enricher.py @@ -79,8 +79,8 @@ def test_get_metadata_error_handling(mock_run, mock_logger_error, enricher): @pytest.mark.skip(reason="Requires ExifTool to be installed. TODO mock") def test_metadata_pickle(enricher, unpickle): # Uses a pickle of a YouTube short - metadata = unpickle("tests/data/metadata/metadata_enricher_ytshort_input.pickle") - expected = unpickle("tests/data/metadata/metadata_enricher_ytshort_expected.pickle") + metadata = unpickle("metadata_enricher_ytshort_input.pickle") + expected = unpickle("metadata_enricher_ytshort_expected.pickle") enricher.enrich(metadata) expected_media = expected.media actual_media = metadata.media diff --git a/tests/test_metadata.py b/tests/test_metadata.py index c6f3593..e1f7797 100644 --- a/tests/test_metadata.py +++ b/tests/test_metadata.py @@ -179,8 +179,8 @@ def test_choose_most_complete(): def test_choose_most_complete_from_pickles(unpickle): # test most complete from pickles before and after an enricher has run # Only compares length of media, not the actual media - m_before_enriching = unpickle("tests/data/metadata/metadata_enricher_ytshort_input.pickle") - m_after_enriching = unpickle("tests/data/metadata/metadata_enricher_ytshort_expected.pickle") + m_before_enriching = unpickle("metadata_enricher_ytshort_input.pickle") + m_after_enriching = unpickle("metadata_enricher_ytshort_expected.pickle") # Iterates `for r in results[1:]:` res = Metadata.choose_most_complete([Metadata(), m_after_enriching, m_before_enriching]) assert res.media == m_after_enriching.media From 5614af3f63be377dd0a5b65674f3b6811bbd4caa Mon Sep 17 00:00:00 2001 From: Miguel Sozinho Ramalho <19508417+msramalho@users.noreply.github.com> Date: Fri, 14 Feb 2025 10:51:56 +0000 Subject: [PATCH 042/160] removes fixed oscrypto dependency, it blocked pypi publishing (#195) * disables tests on ubuntu-latest * drops fixed oscrypto version for git commit * version bump --- .github/workflows/tests-core.yaml | 3 ++- poetry.lock | 14 +++++--------- pyproject.toml | 3 +-- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/.github/workflows/tests-core.yaml b/.github/workflows/tests-core.yaml index aab3713..917cfbb 100644 --- a/.github/workflows/tests-core.yaml +++ b/.github/workflows/tests-core.yaml @@ -16,7 +16,8 @@ jobs: fail-fast: false matrix: python-version: ["3.10", "3.11", "3.12"] - os: [ubuntu-22.04, ubuntu-latest] + os: [ubuntu-22.04] + #TODO: re-enable ubuntu-latest, this is disabled as oscrypto cannot be pinned to github commit and pushed to pypi defaults: run: working-directory: ./ diff --git a/poetry.lock b/poetry.lock index cca7024..c0cd7c2 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1367,18 +1367,14 @@ description = "TLS (SSL) sockets, key generation, encryption, decryption, signin optional = false python-versions = "*" groups = ["main"] -files = [] -develop = false +files = [ + {file = "oscrypto-1.3.0-py2.py3-none-any.whl", hash = "sha256:2b2f1d2d42ec152ca90ccb5682f3e051fb55986e1b170ebde472b133713e7085"}, + {file = "oscrypto-1.3.0.tar.gz", hash = "sha256:6f5fef59cb5b3708321db7cca56aed8ad7e662853351e7991fcf60ec606d47a4"}, +] [package.dependencies] asn1crypto = ">=1.5.1" -[package.source] -type = "git" -url = "https://github.com/wbond/oscrypto.git" -reference = "d5f3437ed24257895ae1edd9e503cfb352e635a8" -resolved_reference = "d5f3437ed24257895ae1edd9e503cfb352e635a8" - [[package]] name = "outcome" version = "1.3.0.post0" @@ -3168,4 +3164,4 @@ test = ["pytest (>=8.1,<9.0)", "pytest-rerunfailures (>=14.0,<15.0)"] [metadata] lock-version = "2.1" python-versions = ">=3.10,<3.13" -content-hash = "c2503c982b9362c3757f39432cdaa8375b45e2d4a0497fa80c2b82a65d1eedf7" +content-hash = "b3a6142d6495bc4c8741e9411d29352af219851e4b84b263f991e1bb6db1614e" diff --git a/pyproject.toml b/pyproject.toml index c217ce2..296ee01 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api" [project] name = "auto-archiver" -version = "0.13.0" +version = "0.13.1" description = "Automatically archive links to videos, images, and social media content from Google Sheets (and more)." requires-python = ">=3.10,<3.13" @@ -22,7 +22,6 @@ classifiers = [ ] dependencies = [ - "oscrypto @ git+https://github.com/wbond/oscrypto.git@d5f3437ed24257895ae1edd9e503cfb352e635a8", "gspread (>=0.0.0)", "beautifulsoup4 (>=0.0.0)", "bs4 (>=0.0.0)", From 9297697ef5166bc14456bbfbec40efbca3650316 Mon Sep 17 00:00:00 2001 From: Miguel Sozinho Ramalho <19508417+msramalho@users.noreply.github.com> Date: Sat, 15 Feb 2025 12:41:26 +0000 Subject: [PATCH 043/160] makes orchestrator.run return the results to allow for code integration (#196) --- pyproject.toml | 2 +- src/auto_archiver/core/orchestrator.py | 68 +++++++++++++------------- 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 296ee01..cd76b59 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api" [project] name = "auto-archiver" -version = "0.13.1" +version = "0.13.2" description = "Automatically archive links to videos, images, and social media content from Google Sheets (and more)." requires-python = ">=3.10,<3.13" diff --git a/src/auto_archiver/core/orchestrator.py b/src/auto_archiver/core/orchestrator.py index bb5f9e3..8985e0c 100644 --- a/src/auto_archiver/core/orchestrator.py +++ b/src/auto_archiver/core/orchestrator.py @@ -30,6 +30,7 @@ from loguru import logger DEFAULT_CONFIG_FILE = "orchestration.yaml" + class JsonParseAction(argparse.Action): def __call__(self, parser, namespace, values, option_string=None): try: @@ -60,6 +61,8 @@ class AuthenticationJsonParseAction(JsonParseAction): if not isinstance(site, str) or not isinstance(auth, dict): raise argparse.ArgumentTypeError("Authentication must be a dictionary of site names and their authentication methods") setattr(namespace, self.dest, auth_dict) + + class UniqueAppendAction(argparse.Action): def __call__(self, parser, namespace, values, option_string=None): if not hasattr(namespace, self.dest): @@ -68,6 +71,7 @@ class UniqueAppendAction(argparse.Action): if value not in getattr(namespace, self.dest): getattr(namespace, self.dest).append(value) + class ArchivingOrchestrator: feeders: List[Type[Feeder]] @@ -76,17 +80,17 @@ class ArchivingOrchestrator: databases: List[Type[Database]] storages: List[Type[Storage]] formatters: List[Type[Formatter]] - + def setup_basic_parser(self): parser = argparse.ArgumentParser( - prog="auto-archiver", - add_help=False, - description=""" + prog="auto-archiver", + add_help=False, + description=""" Auto Archiver is a CLI tool to archive media/metadata from online URLs; it can read URLs from many sources (Google Sheets, Command Line, ...); and write results to many destinations too (CSV, Google Sheets, MongoDB, ...)! """, - epilog="Check the code at https://github.com/bellingcat/auto-archiver", - formatter_class=RichHelpFormatter, + epilog="Check the code at https://github.com/bellingcat/auto-archiver", + formatter_class=RichHelpFormatter, ) parser.add_argument('--help', '-h', action='store_true', dest='help', help='show a full help message and exit') parser.add_argument('--version', action='version', version=__version__) @@ -130,7 +134,7 @@ class ArchivingOrchestrator: # for simple mode, we use the cli_feeder and any modules that don't require setup yaml_config['steps']['feeders'] = ['cli_feeder'] - + # add them to the config for module in simple_modules: for module_type in module.type: @@ -155,23 +159,22 @@ class ArchivingOrchestrator: if unknown: logger.warning(f"Ignoring unknown/unused arguments: {unknown}\nPerhaps you don't have this module enabled?") - + if (self.config != yaml_config and basic_config.store) or not os.path.isfile(basic_config.config_file): logger.info(f"Storing configuration file to {basic_config.config_file}") store_yaml(self.config, basic_config.config_file) - + return self.config - + def add_additional_args(self, parser: argparse.ArgumentParser = None): if not parser: parser = self.parser - # allow passing URLs directly on the command line parser.add_argument('urls', nargs='*', default=[], help='URL(s) to archive, either a single URL or a list of urls, should not come from config.yaml') parser.add_argument('--feeders', dest='steps.feeders', nargs='+', default=['cli_feeder'], help='the feeders to use', action=UniqueAppendAction) - parser.add_argument('--enrichers', dest='steps.enrichers', nargs='+', help='the enrichers to use', action=UniqueAppendAction) + parser.add_argument('--enrichers', dest='steps.enrichers', nargs='+', help='the enrichers to use', action=UniqueAppendAction) parser.add_argument('--extractors', dest='steps.extractors', nargs='+', help='the extractors to use', action=UniqueAppendAction) parser.add_argument('--databases', dest='steps.databases', nargs='+', help='the databases to use', action=UniqueAppendAction) parser.add_argument('--storages', dest='steps.storages', nargs='+', help='the storages to use', action=UniqueAppendAction) @@ -180,7 +183,7 @@ class ArchivingOrchestrator: parser.add_argument('--authentication', dest='authentication', help='A dictionary of sites and their authentication methods \ (token, username etc.) that extractors can use to log into \ a website. If passing this on the command line, use a JSON string. \ - You may also pass a path to a valid JSON/YAML file which will be parsed.',\ + You may also pass a path to a valid JSON/YAML file which will be parsed.', default={}, action=AuthenticationJsonParseAction) # logging arguments @@ -188,7 +191,6 @@ class ArchivingOrchestrator: parser.add_argument('--logging.file', action='store', dest='logging.file', help='the logging file to write to', default=None) parser.add_argument('--logging.rotation', action='store', dest='logging.rotation', help='the logging rotation to use', default=None) - def add_module_args(self, modules: list[LazyBaseModule] = None, parser: argparse.ArgumentParser = None) -> None: if not modules: @@ -225,16 +227,16 @@ class ArchivingOrchestrator: def show_help(self, basic_config: dict): # for the help message, we want to load *all* possible modules and show the help - # add configs as arg parser arguments - + # add configs as arg parser arguments + self.add_additional_args(self.basic_parser) self.add_module_args(parser=self.basic_parser) self.basic_parser.print_help() self.basic_parser.exit() - + def setup_logging(self): # setup loguru logging - logger.remove(0) # remove the default logger + logger.remove(0) # remove the default logger logging_config = self.config['logging'] logger.add(sys.stderr, level=logging_config['level']) if log_file := logging_config['file']: @@ -246,7 +248,7 @@ class ArchivingOrchestrator: orchestrator's attributes (self.feeders, self.extractors etc.). If no modules of a certain type are loaded, the program will exit with an error message. """ - + invalid_modules = [] for module_type in BaseModule.MODULE_TYPES: @@ -273,6 +275,7 @@ class ArchivingOrchestrator: logger.error("No URLs provided. Please provide at least one URL via the command line, or set up an alternative feeder. Use --help for more information.") exit() # cli_feeder is a pseudo module, it just takes the command line args + def feed(self) -> Generator[Metadata]: for url in urls: logger.debug(f"Processing URL: '{url}'") @@ -284,7 +287,6 @@ class ArchivingOrchestrator: '__iter__': feed })() - pseudo_module.__iter__ = feed step_items.append(pseudo_module) @@ -308,7 +310,7 @@ class ArchivingOrchestrator: check_steps_ok() setattr(self, f"{module_type}s", step_items) - + def load_config(self, config_file: str) -> dict: if not os.path.exists(config_file) and config_file != DEFAULT_CONFIG_FILE: logger.error(f"The configuration file {config_file} was not found. Make sure the file exists and try again, or run without the --config file to use the default settings.") @@ -316,8 +318,8 @@ class ArchivingOrchestrator: return read_yaml(config_file) - def run(self, args: list) -> None: - + def run(self, args: list) -> Generator[Metadata]: + self.setup_basic_parser() # parse the known arguments for now (basically, we want the config file) @@ -340,10 +342,10 @@ class ArchivingOrchestrator: for module_type in BaseModule.MODULE_TYPES: logger.info(f"{module_type.upper()}S: " + ", ".join(m.display_name for m in getattr(self, f"{module_type}s"))) - for _ in self.feed(): - pass + for result in self.feed(): + yield result - def cleanup(self)->None: + def cleanup(self) -> None: logger.info("Cleaning up") for e in self.extractors: e.cleanup() @@ -393,7 +395,6 @@ class ArchivingOrchestrator: m.tmp_dir = None tmp_dir.cleanup() - def archive(self, result: Metadata) -> Union[Metadata, None]: """ Runs the archiving process for a single URL @@ -440,13 +441,13 @@ class ArchivingOrchestrator: try: result.merge(a.download(result)) if result.is_success(): break - except Exception as e: + except Exception as e: logger.error(f"ERROR archiver {a.name}: {e}: {traceback.format_exc()}") # 4 - call enrichers to work with archived content for e in self.enrichers: try: e.enrich(result) - except Exception as exc: + except Exception as exc: logger.error(f"ERROR enricher {e.name}: {exc}: {traceback.format_exc()}") # 5 - store all downloaded/generated media @@ -474,13 +475,13 @@ class ArchivingOrchestrator: Blocks localhost, private, reserved, and link-local IPs and all non-http/https schemes. """ assert url.startswith("http://") or url.startswith("https://"), f"Invalid URL scheme" - + parsed = urlparse(url) assert parsed.scheme in ["http", "https"], f"Invalid URL scheme" assert parsed.hostname, f"Invalid URL hostname" assert parsed.hostname != "localhost", f"Invalid URL" - try: # special rules for IP addresses + try: # special rules for IP addresses ip = ip_address(parsed.hostname) except ValueError: pass else: @@ -489,9 +490,8 @@ class ArchivingOrchestrator: assert not ip.is_link_local, f"Invalid IP used" assert not ip.is_private, f"Invalid IP used" - # Helper Properties - + @property def all_modules(self) -> List[Type[BaseModule]]: - return self.feeders + self.extractors + self.enrichers + self.databases + self.storages + self.formatters \ No newline at end of file + return self.feeders + self.extractors + self.enrichers + self.databases + self.storages + self.formatters From 6d43bc7d4daef9a1baf52b0399085250a85bef0c Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Sat, 15 Feb 2025 18:36:44 +0100 Subject: [PATCH 044/160] Fix generator programmatic setup (#197) * Fix returning a generator of a generator * Move download test test to pytest.mark.download --- src/auto_archiver/core/orchestrator.py | 16 ++++++++++------ tests/extractors/test_twitter_api_extractor.py | 7 +++++-- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/auto_archiver/core/orchestrator.py b/src/auto_archiver/core/orchestrator.py index 8985e0c..9dd3e06 100644 --- a/src/auto_archiver/core/orchestrator.py +++ b/src/auto_archiver/core/orchestrator.py @@ -317,9 +317,11 @@ class ArchivingOrchestrator: exit() return read_yaml(config_file) - - def run(self, args: list) -> Generator[Metadata]: - + + def setup(self, args: list): + """ + Main entry point for the orchestrator, sets up the basic parser, loads the config file, and sets up the complete parser + """ self.setup_basic_parser() # parse the known arguments for now (basically, we want the config file) @@ -342,8 +344,10 @@ class ArchivingOrchestrator: for module_type in BaseModule.MODULE_TYPES: logger.info(f"{module_type.upper()}S: " + ", ".join(m.display_name for m in getattr(self, f"{module_type}s"))) - for result in self.feed(): - yield result + def run(self, args: list) -> Generator[Metadata]: + + self.setup(args) + return self.feed() def cleanup(self) -> None: logger.info("Cleaning up") @@ -351,7 +355,7 @@ class ArchivingOrchestrator: e.cleanup() def feed(self) -> Generator[Metadata]: - + url_count = 0 for feeder in self.feeders: for item in feeder: diff --git a/tests/extractors/test_twitter_api_extractor.py b/tests/extractors/test_twitter_api_extractor.py index d9a8eb0..004376c 100644 --- a/tests/extractors/test_twitter_api_extractor.py +++ b/tests/extractors/test_twitter_api_extractor.py @@ -23,7 +23,6 @@ class TestTwitterApiExtractor(TestExtractorBase): } @pytest.mark.parametrize("url, expected", [ - ("https://t.co/yl3oOJatFp", "https://www.bellingcat.com/category/resources/"), # t.co URL ("https://x.com/bellingcat/status/1874097816571961839", "https://x.com/bellingcat/status/1874097816571961839"), # x.com urls unchanged ("https://twitter.com/bellingcat/status/1874097816571961839", "https://twitter.com/bellingcat/status/1874097816571961839"), # twitter urls unchanged ("https://twitter.com/bellingcat/status/1874097816571961839?s=20&t=3d0g4ZQis7dCbSDg-mE7-w", "https://twitter.com/bellingcat/status/1874097816571961839?s=20&t=3d0g4ZQis7dCbSDg-mE7-w"), # don't strip params from twitter urls (changed Jan 2025) @@ -32,7 +31,11 @@ class TestTwitterApiExtractor(TestExtractorBase): ]) def test_sanitize_url(self, url, expected): assert expected == self.extractor.sanitize_url(url) - + + @pytest.mark.download + def test_sanitize_url_download(self): + assert "https://t.co/yl3oOJatFp" == self.extractor.sanitize_url("https://www.bellingcat.com/category/resources/") + @pytest.mark.parametrize("url, exptected_username, exptected_tweetid", [ ("https://twitter.com/bellingcat/status/1874097816571961839", "bellingcat", "1874097816571961839"), ("https://x.com/bellingcat/status/1874097816571961839", "bellingcat", "1874097816571961839"), From ce5a200d1f42572061cd2cedf3db0d847c8c7e34 Mon Sep 17 00:00:00 2001 From: erinhmclark Date: Tue, 18 Feb 2025 12:59:10 +0000 Subject: [PATCH 045/160] Added tests, updated instagram_tbot_extractor.py raise failure. --- poetry.lock | 193 +++++++++--------- .../instagram_tbot_extractor.py | 9 +- src/auto_archiver/utils/misc.py | 2 +- tests/conftest.py | 7 + tests/databases/test_api_db.py | 69 +++++++ .../test_instagram_tbot_extractor.py | 131 +++++++----- tests/feeders/test_gworksheet.py | 1 + tests/utils/test_misc.py | 146 +++++++++++++ 8 files changed, 401 insertions(+), 157 deletions(-) create mode 100644 tests/databases/test_api_db.py create mode 100644 tests/utils/test_misc.py diff --git a/poetry.lock b/poetry.lock index c6bb954..c8524b4 100644 --- a/poetry.lock +++ b/poetry.lock @@ -172,18 +172,18 @@ lxml = ["lxml"] [[package]] name = "boto3" -version = "1.36.19" +version = "1.36.22" description = "The AWS SDK for Python" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "boto3-1.36.19-py3-none-any.whl", hash = "sha256:7784590369a9d545bb07b2de56b6ce4d5a5e232883a957f704c3f842caeba155"}, - {file = "boto3-1.36.19.tar.gz", hash = "sha256:8c2c2a4ccdfe35dd2611ee1b7473dd2383948415c777e42dc4e7f1ebe371fe8c"}, + {file = "boto3-1.36.22-py3-none-any.whl", hash = "sha256:39957eabdce009353d72d131046489fbbfa15891865d5f069f1e8bfa414e6b81"}, + {file = "boto3-1.36.22.tar.gz", hash = "sha256:768c8a4d4a6227fe2258105efa086f1424cba5ca915a5eb2305b2cd979306ad1"}, ] [package.dependencies] -botocore = ">=1.36.19,<1.37.0" +botocore = ">=1.36.22,<1.37.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.11.0,<0.12.0" @@ -192,14 +192,14 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.36.19" +version = "1.36.22" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "botocore-1.36.19-py3-none-any.whl", hash = "sha256:98882c106fec4c08678ea028199f7f5119550fab95d682b30846f7aae04b7bec"}, - {file = "botocore-1.36.19.tar.gz", hash = "sha256:cdf6729f601f82b1acdb9004b1f88b57cfb470f576394cdb3bbf5150f7fafb5b"}, + {file = "botocore-1.36.22-py3-none-any.whl", hash = "sha256:75d6b34acb0686ee4d54ff6eb285e78ccfe318407428769d1e3e13351714d890"}, + {file = "botocore-1.36.22.tar.gz", hash = "sha256:59520247d5a479731724f97c995d5a1c2aae3b303b324f39d99efcfad1d3019e"}, ] [package.dependencies] @@ -781,14 +781,14 @@ grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] [[package]] name = "google-api-python-client" -version = "2.160.0" +version = "2.161.0" description = "Google API Client Library for Python" optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "google_api_python_client-2.160.0-py2.py3-none-any.whl", hash = "sha256:63d61fb3e4cf3fb31a70a87f45567c22f6dfe87bbfa27252317e3e2c42900db4"}, - {file = "google_api_python_client-2.160.0.tar.gz", hash = "sha256:a8ccafaecfa42d15d5b5c3134ced8de08380019717fc9fb1ed510ca58eca3b7e"}, + {file = "google_api_python_client-2.161.0-py2.py3-none-any.whl", hash = "sha256:9476a5a4f200bae368140453df40f9cda36be53fa7d0e9a9aac4cdb859a26448"}, + {file = "google_api_python_client-2.161.0.tar.gz", hash = "sha256:324c0cce73e9ea0a0d2afd5937e01b7c2d6a4d7e2579cdb6c384f9699d6c9f37"}, ] [package.dependencies] @@ -2363,14 +2363,14 @@ test = ["cython (>=3.0)", "defusedxml (>=0.7.1)", "pytest (>=8.0)", "setuptools [[package]] name = "sphinx-autoapi" -version = "3.5.0" +version = "3.6.0" description = "Sphinx API documentation generator" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["docs"] files = [ - {file = "sphinx_autoapi-3.5.0-py3-none-any.whl", hash = "sha256:8676db32dded669dc6be9100696652640dc1e883e45b74710d74eb547a310114"}, - {file = "sphinx_autoapi-3.5.0.tar.gz", hash = "sha256:10dcdf86e078ae1fb144f653341794459e86f5b23cf3e786a735def71f564089"}, + {file = "sphinx_autoapi-3.6.0-py3-none-any.whl", hash = "sha256:f3b66714493cab140b0e896d33ce7137654a16ac1edb6563edcbd47bf975f711"}, + {file = "sphinx_autoapi-3.6.0.tar.gz", hash = "sha256:c685f274e41d0842ae7e199460c322c4bd7fec816ccc2da8d806094b4f64af06"}, ] [package.dependencies] @@ -2380,7 +2380,7 @@ astroid = [ ] Jinja2 = "*" PyYAML = "*" -sphinx = ">=6.1.0" +sphinx = ">=7.4.0" [[package]] name = "sphinx-autobuild" @@ -2680,14 +2680,14 @@ telegram = ["requests"] [[package]] name = "trio" -version = "0.28.0" +version = "0.29.0" description = "A friendly Python library for async concurrency and I/O" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "trio-0.28.0-py3-none-any.whl", hash = "sha256:56d58977acc1635735a96581ec70513cc781b8b6decd299c487d3be2a721cd94"}, - {file = "trio-0.28.0.tar.gz", hash = "sha256:4e547896fe9e8a5658e54e4c7c5fa1db748cbbbaa7c965e7d40505b928c73c05"}, + {file = "trio-0.29.0-py3-none-any.whl", hash = "sha256:d8c463f1a9cc776ff63e331aba44c125f423a5a13c684307e828d930e625ba66"}, + {file = "trio-0.29.0.tar.gz", hash = "sha256:ea0d3967159fc130acb6939a0be0e558e364fee26b5deeecc893a6b08c361bdf"}, ] [package.dependencies] @@ -2701,18 +2701,19 @@ sortedcontainers = "*" [[package]] name = "trio-websocket" -version = "0.11.1" +version = "0.12.1" description = "WebSocket library for Trio" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" groups = ["main"] files = [ - {file = "trio-websocket-0.11.1.tar.gz", hash = "sha256:18c11793647703c158b1f6e62de638acada927344d534e3c7628eedcb746839f"}, - {file = "trio_websocket-0.11.1-py3-none-any.whl", hash = "sha256:520d046b0d030cf970b8b2b2e00c4c2245b3807853ecd44214acd33d74581638"}, + {file = "trio_websocket-0.12.1-py3-none-any.whl", hash = "sha256:608ec746bb287e5d5a66baf483e41194193c5cf05ffaad6240e7d1fcd80d1e6f"}, + {file = "trio_websocket-0.12.1.tar.gz", hash = "sha256:d55ccd4d3eae27c494f3fdae14823317839bdcb8214d1173eacc4d42c69fc91b"}, ] [package.dependencies] exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} +outcome = ">=1.2.0" trio = ">=0.11" wsproto = ">=0.14" @@ -2779,14 +2780,14 @@ files = [ [[package]] name = "tzlocal" -version = "5.2" +version = "5.3" description = "tzinfo object for the local timezone" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "tzlocal-5.2-py3-none-any.whl", hash = "sha256:49816ef2fe65ea8ac19d19aa7a1ae0551c834303d5014c6d5a62e4cbda8047b8"}, - {file = "tzlocal-5.2.tar.gz", hash = "sha256:8d399205578f1a9342816409cc1e46a93ebd5755e39ea2d85334bea911bf0e6e"}, + {file = "tzlocal-5.3-py3-none-any.whl", hash = "sha256:3814135a1bb29763c6e4f08fd6e41dbb435c7a60bfbb03270211bcc537187d8c"}, + {file = "tzlocal-5.3.tar.gz", hash = "sha256:2fafbfc07e9d8b49ade18f898d6bcd37ae88ce3ad6486842a2e4f03af68323d2"}, ] [package.dependencies] @@ -3032,81 +3033,81 @@ test = ["websockets"] [[package]] name = "websockets" -version = "14.2" +version = "15.0" description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" optional = false python-versions = ">=3.9" groups = ["main", "docs"] files = [ - {file = "websockets-14.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e8179f95323b9ab1c11723e5d91a89403903f7b001828161b480a7810b334885"}, - {file = "websockets-14.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0d8c3e2cdb38f31d8bd7d9d28908005f6fa9def3324edb9bf336d7e4266fd397"}, - {file = "websockets-14.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:714a9b682deb4339d39ffa674f7b674230227d981a37d5d174a4a83e3978a610"}, - {file = "websockets-14.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2e53c72052f2596fb792a7acd9704cbc549bf70fcde8a99e899311455974ca3"}, - {file = "websockets-14.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e3fbd68850c837e57373d95c8fe352203a512b6e49eaae4c2f4088ef8cf21980"}, - {file = "websockets-14.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b27ece32f63150c268593d5fdb82819584831a83a3f5809b7521df0685cd5d8"}, - {file = "websockets-14.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4daa0faea5424d8713142b33825fff03c736f781690d90652d2c8b053345b0e7"}, - {file = "websockets-14.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:bc63cee8596a6ec84d9753fd0fcfa0452ee12f317afe4beae6b157f0070c6c7f"}, - {file = "websockets-14.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7a570862c325af2111343cc9b0257b7119b904823c675b22d4ac547163088d0d"}, - {file = "websockets-14.2-cp310-cp310-win32.whl", hash = "sha256:75862126b3d2d505e895893e3deac0a9339ce750bd27b4ba515f008b5acf832d"}, - {file = "websockets-14.2-cp310-cp310-win_amd64.whl", hash = "sha256:cc45afb9c9b2dc0852d5c8b5321759cf825f82a31bfaf506b65bf4668c96f8b2"}, - {file = "websockets-14.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3bdc8c692c866ce5fefcaf07d2b55c91d6922ac397e031ef9b774e5b9ea42166"}, - {file = "websockets-14.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c93215fac5dadc63e51bcc6dceca72e72267c11def401d6668622b47675b097f"}, - {file = "websockets-14.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1c9b6535c0e2cf8a6bf938064fb754aaceb1e6a4a51a80d884cd5db569886910"}, - {file = "websockets-14.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a52a6d7cf6938e04e9dceb949d35fbdf58ac14deea26e685ab6368e73744e4c"}, - {file = "websockets-14.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9f05702e93203a6ff5226e21d9b40c037761b2cfb637187c9802c10f58e40473"}, - {file = "websockets-14.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22441c81a6748a53bfcb98951d58d1af0661ab47a536af08920d129b4d1c3473"}, - {file = "websockets-14.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd9b868d78b194790e6236d9cbc46d68aba4b75b22497eb4ab64fa640c3af56"}, - {file = "websockets-14.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1a5a20d5843886d34ff8c57424cc65a1deda4375729cbca4cb6b3353f3ce4142"}, - {file = "websockets-14.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:34277a29f5303d54ec6468fb525d99c99938607bc96b8d72d675dee2b9f5bf1d"}, - {file = "websockets-14.2-cp311-cp311-win32.whl", hash = "sha256:02687db35dbc7d25fd541a602b5f8e451a238ffa033030b172ff86a93cb5dc2a"}, - {file = "websockets-14.2-cp311-cp311-win_amd64.whl", hash = "sha256:862e9967b46c07d4dcd2532e9e8e3c2825e004ffbf91a5ef9dde519ee2effb0b"}, - {file = "websockets-14.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1f20522e624d7ffbdbe259c6b6a65d73c895045f76a93719aa10cd93b3de100c"}, - {file = "websockets-14.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:647b573f7d3ada919fd60e64d533409a79dcf1ea21daeb4542d1d996519ca967"}, - {file = "websockets-14.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6af99a38e49f66be5a64b1e890208ad026cda49355661549c507152113049990"}, - {file = "websockets-14.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:091ab63dfc8cea748cc22c1db2814eadb77ccbf82829bac6b2fbe3401d548eda"}, - {file = "websockets-14.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b374e8953ad477d17e4851cdc66d83fdc2db88d9e73abf755c94510ebddceb95"}, - {file = "websockets-14.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a39d7eceeea35db85b85e1169011bb4321c32e673920ae9c1b6e0978590012a3"}, - {file = "websockets-14.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0a6f3efd47ffd0d12080594f434faf1cd2549b31e54870b8470b28cc1d3817d9"}, - {file = "websockets-14.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:065ce275e7c4ffb42cb738dd6b20726ac26ac9ad0a2a48e33ca632351a737267"}, - {file = "websockets-14.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e9d0e53530ba7b8b5e389c02282f9d2aa47581514bd6049d3a7cffe1385cf5fe"}, - {file = "websockets-14.2-cp312-cp312-win32.whl", hash = "sha256:20e6dd0984d7ca3037afcb4494e48c74ffb51e8013cac71cf607fffe11df7205"}, - {file = "websockets-14.2-cp312-cp312-win_amd64.whl", hash = "sha256:44bba1a956c2c9d268bdcdf234d5e5ff4c9b6dc3e300545cbe99af59dda9dcce"}, - {file = "websockets-14.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6f1372e511c7409a542291bce92d6c83320e02c9cf392223272287ce55bc224e"}, - {file = "websockets-14.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4da98b72009836179bb596a92297b1a61bb5a830c0e483a7d0766d45070a08ad"}, - {file = "websockets-14.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8a86a269759026d2bde227652b87be79f8a734e582debf64c9d302faa1e9f03"}, - {file = "websockets-14.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:86cf1aaeca909bf6815ea714d5c5736c8d6dd3a13770e885aafe062ecbd04f1f"}, - {file = "websockets-14.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9b0f6c3ba3b1240f602ebb3971d45b02cc12bd1845466dd783496b3b05783a5"}, - {file = "websockets-14.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:669c3e101c246aa85bc8534e495952e2ca208bd87994650b90a23d745902db9a"}, - {file = "websockets-14.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:eabdb28b972f3729348e632ab08f2a7b616c7e53d5414c12108c29972e655b20"}, - {file = "websockets-14.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2066dc4cbcc19f32c12a5a0e8cc1b7ac734e5b64ac0a325ff8353451c4b15ef2"}, - {file = "websockets-14.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ab95d357cd471df61873dadf66dd05dd4709cae001dd6342edafc8dc6382f307"}, - {file = "websockets-14.2-cp313-cp313-win32.whl", hash = "sha256:a9e72fb63e5f3feacdcf5b4ff53199ec8c18d66e325c34ee4c551ca748623bbc"}, - {file = "websockets-14.2-cp313-cp313-win_amd64.whl", hash = "sha256:b439ea828c4ba99bb3176dc8d9b933392a2413c0f6b149fdcba48393f573377f"}, - {file = "websockets-14.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7cd5706caec1686c5d233bc76243ff64b1c0dc445339bd538f30547e787c11fe"}, - {file = "websockets-14.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ec607328ce95a2f12b595f7ae4c5d71bf502212bddcea528290b35c286932b12"}, - {file = "websockets-14.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:da85651270c6bfb630136423037dd4975199e5d4114cae6d3066641adcc9d1c7"}, - {file = "websockets-14.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3ecadc7ce90accf39903815697917643f5b7cfb73c96702318a096c00aa71f5"}, - {file = "websockets-14.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1979bee04af6a78608024bad6dfcc0cc930ce819f9e10342a29a05b5320355d0"}, - {file = "websockets-14.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dddacad58e2614a24938a50b85969d56f88e620e3f897b7d80ac0d8a5800258"}, - {file = "websockets-14.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:89a71173caaf75fa71a09a5f614f450ba3ec84ad9fca47cb2422a860676716f0"}, - {file = "websockets-14.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:6af6a4b26eea4fc06c6818a6b962a952441e0e39548b44773502761ded8cc1d4"}, - {file = "websockets-14.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:80c8efa38957f20bba0117b48737993643204645e9ec45512579132508477cfc"}, - {file = "websockets-14.2-cp39-cp39-win32.whl", hash = "sha256:2e20c5f517e2163d76e2729104abc42639c41cf91f7b1839295be43302713661"}, - {file = "websockets-14.2-cp39-cp39-win_amd64.whl", hash = "sha256:b4c8cef610e8d7c70dea92e62b6814a8cd24fbd01d7103cc89308d2bfe1659ef"}, - {file = "websockets-14.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:d7d9cafbccba46e768be8a8ad4635fa3eae1ffac4c6e7cb4eb276ba41297ed29"}, - {file = "websockets-14.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:c76193c1c044bd1e9b3316dcc34b174bbf9664598791e6fb606d8d29000e070c"}, - {file = "websockets-14.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd475a974d5352390baf865309fe37dec6831aafc3014ffac1eea99e84e83fc2"}, - {file = "websockets-14.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2c6c0097a41968b2e2b54ed3424739aab0b762ca92af2379f152c1aef0187e1c"}, - {file = "websockets-14.2-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d7ff794c8b36bc402f2e07c0b2ceb4a2424147ed4785ff03e2a7af03711d60a"}, - {file = "websockets-14.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:dec254fcabc7bd488dab64846f588fc5b6fe0d78f641180030f8ea27b76d72c3"}, - {file = "websockets-14.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:bbe03eb853e17fd5b15448328b4ec7fb2407d45fb0245036d06a3af251f8e48f"}, - {file = "websockets-14.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a3c4aa3428b904d5404a0ed85f3644d37e2cb25996b7f096d77caeb0e96a3b42"}, - {file = "websockets-14.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:577a4cebf1ceaf0b65ffc42c54856214165fb8ceeba3935852fc33f6b0c55e7f"}, - {file = "websockets-14.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ad1c1d02357b7665e700eca43a31d52814ad9ad9b89b58118bdabc365454b574"}, - {file = "websockets-14.2-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f390024a47d904613577df83ba700bd189eedc09c57af0a904e5c39624621270"}, - {file = "websockets-14.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3c1426c021c38cf92b453cdf371228d3430acd775edee6bac5a4d577efc72365"}, - {file = "websockets-14.2-py3-none-any.whl", hash = "sha256:7a6ceec4ea84469f15cf15807a747e9efe57e369c384fa86e022b3bea679b79b"}, - {file = "websockets-14.2.tar.gz", hash = "sha256:5059ed9c54945efb321f097084b4c7e52c246f2c869815876a69d1efc4ad6eb5"}, + {file = "websockets-15.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5e6ee18a53dd5743e6155b8ff7e8e477c25b29b440f87f65be8165275c87fef0"}, + {file = "websockets-15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ee06405ea2e67366a661ed313e14cf2a86e84142a3462852eb96348f7219cee3"}, + {file = "websockets-15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8711682a629bbcaf492f5e0af72d378e976ea1d127a2d47584fa1c2c080b436b"}, + {file = "websockets-15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94c4a9b01eede952442c088d415861b0cf2053cbd696b863f6d5022d4e4e2453"}, + {file = "websockets-15.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:45535fead66e873f411c1d3cf0d3e175e66f4dd83c4f59d707d5b3e4c56541c4"}, + {file = "websockets-15.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e389efe46ccb25a1f93d08c7a74e8123a2517f7b7458f043bd7529d1a63ffeb"}, + {file = "websockets-15.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:67a04754d121ea5ca39ddedc3f77071651fb5b0bc6b973c71c515415b44ed9c5"}, + {file = "websockets-15.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:bd66b4865c8b853b8cca7379afb692fc7f52cf898786537dfb5e5e2d64f0a47f"}, + {file = "websockets-15.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a4cc73a6ae0a6751b76e69cece9d0311f054da9b22df6a12f2c53111735657c8"}, + {file = "websockets-15.0-cp310-cp310-win32.whl", hash = "sha256:89da58e4005e153b03fe8b8794330e3f6a9774ee9e1c3bd5bc52eb098c3b0c4f"}, + {file = "websockets-15.0-cp310-cp310-win_amd64.whl", hash = "sha256:4ff380aabd7a74a42a760ee76c68826a8f417ceb6ea415bd574a035a111fd133"}, + {file = "websockets-15.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:dd24c4d256558429aeeb8d6c24ebad4e982ac52c50bc3670ae8646c181263965"}, + {file = "websockets-15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f83eca8cbfd168e424dfa3b3b5c955d6c281e8fc09feb9d870886ff8d03683c7"}, + {file = "websockets-15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4095a1f2093002c2208becf6f9a178b336b7572512ee0a1179731acb7788e8ad"}, + {file = "websockets-15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb915101dfbf318486364ce85662bb7b020840f68138014972c08331458d41f3"}, + {file = "websockets-15.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:45d464622314973d78f364689d5dbb9144e559f93dca11b11af3f2480b5034e1"}, + {file = "websockets-15.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ace960769d60037ca9625b4c578a6f28a14301bd2a1ff13bb00e824ac9f73e55"}, + {file = "websockets-15.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c7cd4b1015d2f60dfe539ee6c95bc968d5d5fad92ab01bb5501a77393da4f596"}, + {file = "websockets-15.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4f7290295794b5dec470867c7baa4a14182b9732603fd0caf2a5bf1dc3ccabf3"}, + {file = "websockets-15.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3abd670ca7ce230d5a624fd3d55e055215d8d9b723adee0a348352f5d8d12ff4"}, + {file = "websockets-15.0-cp311-cp311-win32.whl", hash = "sha256:110a847085246ab8d4d119632145224d6b49e406c64f1bbeed45c6f05097b680"}, + {file = "websockets-15.0-cp311-cp311-win_amd64.whl", hash = "sha256:8d7bbbe2cd6ed80aceef2a14e9f1c1b61683194c216472ed5ff33b700e784e37"}, + {file = "websockets-15.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:cccc18077acd34c8072578394ec79563664b1c205f7a86a62e94fafc7b59001f"}, + {file = "websockets-15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d4c22992e24f12de340ca5f824121a5b3e1a37ad4360b4e1aaf15e9d1c42582d"}, + {file = "websockets-15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1206432cc6c644f6fc03374b264c5ff805d980311563202ed7fef91a38906276"}, + {file = "websockets-15.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d3cc75ef3e17490042c47e0523aee1bcc4eacd2482796107fd59dd1100a44bc"}, + {file = "websockets-15.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b89504227a5311610e4be16071465885a0a3d6b0e82e305ef46d9b064ce5fb72"}, + {file = "websockets-15.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56e3efe356416bc67a8e093607315951d76910f03d2b3ad49c4ade9207bf710d"}, + {file = "websockets-15.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0f2205cdb444a42a7919690238fb5979a05439b9dbb73dd47c863d39640d85ab"}, + {file = "websockets-15.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:aea01f40995fa0945c020228ab919b8dfc93fc8a9f2d3d705ab5b793f32d9e99"}, + {file = "websockets-15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a9f8e33747b1332db11cf7fcf4a9512bef9748cb5eb4d3f7fbc8c30d75dc6ffc"}, + {file = "websockets-15.0-cp312-cp312-win32.whl", hash = "sha256:32e02a2d83f4954aa8c17e03fe8ec6962432c39aca4be7e8ee346b05a3476904"}, + {file = "websockets-15.0-cp312-cp312-win_amd64.whl", hash = "sha256:ffc02b159b65c05f2ed9ec176b715b66918a674bd4daed48a9a7a590dd4be1aa"}, + {file = "websockets-15.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d2244d8ab24374bed366f9ff206e2619345f9cd7fe79aad5225f53faac28b6b1"}, + {file = "websockets-15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3a302241fbe825a3e4fe07666a2ab513edfdc6d43ce24b79691b45115273b5e7"}, + {file = "websockets-15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:10552fed076757a70ba2c18edcbc601c7637b30cdfe8c24b65171e824c7d6081"}, + {file = "websockets-15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c53f97032b87a406044a1c33d1e9290cc38b117a8062e8a8b285175d7e2f99c9"}, + {file = "websockets-15.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1caf951110ca757b8ad9c4974f5cac7b8413004d2f29707e4d03a65d54cedf2b"}, + {file = "websockets-15.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8bf1ab71f9f23b0a1d52ec1682a3907e0c208c12fef9c3e99d2b80166b17905f"}, + {file = "websockets-15.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bfcd3acc1a81f106abac6afd42327d2cf1e77ec905ae11dc1d9142a006a496b6"}, + {file = "websockets-15.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c8c5c8e1bac05ef3c23722e591ef4f688f528235e2480f157a9cfe0a19081375"}, + {file = "websockets-15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:86bfb52a9cfbcc09aba2b71388b0a20ea5c52b6517c0b2e316222435a8cdab72"}, + {file = "websockets-15.0-cp313-cp313-win32.whl", hash = "sha256:26ba70fed190708551c19a360f9d7eca8e8c0f615d19a574292b7229e0ae324c"}, + {file = "websockets-15.0-cp313-cp313-win_amd64.whl", hash = "sha256:ae721bcc8e69846af00b7a77a220614d9b2ec57d25017a6bbde3a99473e41ce8"}, + {file = "websockets-15.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c348abc5924caa02a62896300e32ea80a81521f91d6db2e853e6b1994017c9f6"}, + {file = "websockets-15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5294fcb410ed0a45d5d1cdedc4e51a60aab5b2b3193999028ea94afc2f554b05"}, + {file = "websockets-15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c24ba103ecf45861e2e1f933d40b2d93f5d52d8228870c3e7bf1299cd1cb8ff1"}, + {file = "websockets-15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc8821a03bcfb36e4e4705316f6b66af28450357af8a575dc8f4b09bf02a3dee"}, + {file = "websockets-15.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc5ae23ada6515f31604f700009e2df90b091b67d463a8401c1d8a37f76c1d7"}, + {file = "websockets-15.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ac67b542505186b3bbdaffbc303292e1ee9c8729e5d5df243c1f20f4bb9057e"}, + {file = "websockets-15.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c86dc2068f1c5ca2065aca34f257bbf4f78caf566eb230f692ad347da191f0a1"}, + {file = "websockets-15.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:30cff3ef329682b6182c01c568f551481774c476722020b8f7d0daacbed07a17"}, + {file = "websockets-15.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:98dcf978d4c6048965d1762abd534c9d53bae981a035bfe486690ba11f49bbbb"}, + {file = "websockets-15.0-cp39-cp39-win32.whl", hash = "sha256:37d66646f929ae7c22c79bc73ec4074d6db45e6384500ee3e0d476daf55482a9"}, + {file = "websockets-15.0-cp39-cp39-win_amd64.whl", hash = "sha256:24d5333a9b2343330f0f4eb88546e2c32a7f5c280f8dd7d3cc079beb0901781b"}, + {file = "websockets-15.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b499caef4bca9cbd0bd23cd3386f5113ee7378094a3cb613a2fa543260fe9506"}, + {file = "websockets-15.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:17f2854c6bd9ee008c4b270f7010fe2da6c16eac5724a175e75010aacd905b31"}, + {file = "websockets-15.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89f72524033abbfde880ad338fd3c2c16e31ae232323ebdfbc745cbb1b3dcc03"}, + {file = "websockets-15.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1657a9eecb29d7838e3b415458cc494e6d1b194f7ac73a34aa55c6fb6c72d1f3"}, + {file = "websockets-15.0-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e413352a921f5ad5d66f9e2869b977e88d5103fc528b6deb8423028a2befd842"}, + {file = "websockets-15.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:8561c48b0090993e3b2a54db480cab1d23eb2c5735067213bb90f402806339f5"}, + {file = "websockets-15.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:190bc6ef8690cd88232a038d1b15714c258f79653abad62f7048249b09438af3"}, + {file = "websockets-15.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:327adab7671f3726b0ba69be9e865bba23b37a605b585e65895c428f6e47e766"}, + {file = "websockets-15.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bd8ef197c87afe0a9009f7a28b5dc613bfc585d329f80b7af404e766aa9e8c7"}, + {file = "websockets-15.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:789c43bf4a10cd067c24c321238e800b8b2716c863ddb2294d2fed886fa5a689"}, + {file = "websockets-15.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7394c0b7d460569c9285fa089a429f58465db930012566c03046f9e3ab0ed181"}, + {file = "websockets-15.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2ea4f210422b912ebe58ef0ad33088bc8e5c5ff9655a8822500690abc3b1232d"}, + {file = "websockets-15.0-py3-none-any.whl", hash = "sha256:51ffd53c53c4442415b613497a34ba0aa7b99ac07f1e4a62db5dcd640ae6c3c3"}, + {file = "websockets-15.0.tar.gz", hash = "sha256:ca36151289a15b39d8d683fd8b7abbe26fc50be311066c5f8dcf3cb8cee107ab"}, ] [[package]] diff --git a/src/auto_archiver/modules/instagram_tbot_extractor/instagram_tbot_extractor.py b/src/auto_archiver/modules/instagram_tbot_extractor/instagram_tbot_extractor.py index d4b7a8e..4404d07 100644 --- a/src/auto_archiver/modules/instagram_tbot_extractor/instagram_tbot_extractor.py +++ b/src/auto_archiver/modules/instagram_tbot_extractor/instagram_tbot_extractor.py @@ -77,13 +77,14 @@ class InstagramTbotExtractor(Extractor): chat, since_id = self._send_url_to_bot(url) message = self._process_messages(chat, since_id, tmp_dir, result) + # This may be outdated and replaced by the below message, but keeping until confirmed if "You must enter a URL to a post" in message: logger.debug(f"invalid link {url=} for {self.name}: {message}") return False - # # TODO: It currently returns this as a success - is that intentional? - # if "Media not found or unavailable" in message: - # logger.debug(f"invalid link {url=} for {self.name}: {message}") - # return False + + if "Media not found or unavailable" in message: + logger.debug(f"No media found for link {url=} for {self.name}: {message}") + return False if message: result.set_content(message).set_title(message[:128]) diff --git a/src/auto_archiver/utils/misc.py b/src/auto_archiver/utils/misc.py index cd03b49..108deae 100644 --- a/src/auto_archiver/utils/misc.py +++ b/src/auto_archiver/utils/misc.py @@ -46,7 +46,7 @@ def dump_payload(p): def update_nested_dict(dictionary, update_dict): - # takes 2 dicts and overwrites the first with the second only on the changed balues + # takes 2 dicts and overwrites the first with the second only on the changed values for key, value in update_dict.items(): if key in dictionary and isinstance(value, dict) and isinstance(dictionary[key], dict): update_nested_dict(dictionary[key], value) diff --git a/tests/conftest.py b/tests/conftest.py index 19e1f6b..8deebff 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,6 +3,7 @@ pytest conftest file, for shared fixtures and configuration """ import os import pickle +from datetime import datetime, timezone from tempfile import TemporaryDirectory from typing import Dict, Tuple import hashlib @@ -138,3 +139,9 @@ def mock_binary_dependencies(): # Mock all binary dependencies as available mock_shutil_which.return_value = "/usr/bin/fake_binary" yield mock_shutil_which + + +@pytest.fixture +def sample_datetime(): + return datetime(2023, 1, 1, 12, 0, tzinfo=timezone.utc) + diff --git a/tests/databases/test_api_db.py b/tests/databases/test_api_db.py new file mode 100644 index 0000000..d07cb1a --- /dev/null +++ b/tests/databases/test_api_db.py @@ -0,0 +1,69 @@ +from unittest.mock import patch + +import pytest + +from auto_archiver.core import Metadata +from auto_archiver.modules.api_db import AAApiDb + + +@pytest.fixture +def api_db(setup_module): + configs: dict = { + "api_endpoint": "https://api.example.com", + "api_token": "test-token", + "public": False, + "author_id": "Someone", + "group_id": "123", + "use_api_cache": True, + "store_results": True, + "tags": "[]", + } + return setup_module(AAApiDb, configs) + + +@pytest.fixture +def metadata(): + metadata = Metadata() + metadata.set("_processed_at", "2021-01-01T00:00:00") + metadata.set_url("https://example.com") + return metadata + + +def test_fetch_no_cache(api_db, metadata): + # Test fetch + api_db.use_api_cache = False + assert api_db.fetch(metadata) is None + + +def test_fetch_fail_status(api_db, metadata): + # Test response fail in fetch method + with patch("auto_archiver.modules.api_db.api_db.requests.get") as mock_get: + mock_get.return_value.status_code = 400 + mock_get.return_value.json.return_value = {} + with patch("loguru.logger.error") as mock_error: + assert api_db.fetch(metadata) is False + mock_error.assert_called_once_with("AA API FAIL (400): {}") + + +def test_fetch(api_db, metadata): + # Test successful fetch method + with patch("auto_archiver.modules.api_db.api_db.requests.get") as mock_get,\ + patch("auto_archiver.core.metadata.datetime.datetime") as mock_datetime: + mock_datetime.now.return_value = "2021-01-01T00:00:00" + mock_get.return_value.status_code = 200 + mock_get.return_value.json.return_value = [{"result": {}}, {"result": + {'media': [], 'metadata': {'_processed_at': '2021-01-01T00:00:00', 'url': 'https://example.com'}, + 'status': 'no archiver'}}] + assert api_db.fetch(metadata) == metadata + + +def test_done_success(api_db, metadata): + with patch("auto_archiver.modules.api_db.api_db.requests.post") as mock_post: + mock_post.return_value.status_code = 201 + api_db.done(metadata) + mock_post.assert_called_once() + mock_post.assert_called_once_with("https://api.example.com/interop/submit-archive", + json={'author_id': 'Someone', 'url': 'https://example.com', + 'public': False, 'group_id': '123', 'tags': ['[', ']'], 'result': '{"status": "no archiver", "metadata": {"_processed_at": "2021-01-01T00:00:00", "url": "https://example.com"}, "media": []}'}, + headers={'Authorization': 'Bearer test-token'}) + diff --git a/tests/extractors/test_instagram_tbot_extractor.py b/tests/extractors/test_instagram_tbot_extractor.py index d7a1e53..9df9983 100644 --- a/tests/extractors/test_instagram_tbot_extractor.py +++ b/tests/extractors/test_instagram_tbot_extractor.py @@ -1,11 +1,9 @@ import os -from typing import Type from unittest.mock import patch, MagicMock import pytest from auto_archiver.core import Metadata -from auto_archiver.core.extractor import Extractor from auto_archiver.modules.instagram_tbot_extractor import InstagramTbotExtractor from tests.extractors.test_extractor_base import TestExtractorBase @@ -13,82 +11,103 @@ TESTFILES = os.path.join(os.path.dirname(__file__), "testfiles") @pytest.fixture -def session_file(tmpdir): - """Fixture to create a test session file.""" - session_file = os.path.join(tmpdir, "test_session.session") - with open(session_file, "w") as f: - f.write("mock_session_data") - return session_file.replace(".session", "") - - -@pytest.fixture(autouse=True) def patch_extractor_methods(request, setup_module): with patch.object(InstagramTbotExtractor, '_prepare_session_file', return_value=None), \ patch.object(InstagramTbotExtractor, '_initialize_telegram_client', return_value=None): - if hasattr(request, 'cls') and hasattr(request.cls, 'config'): - request.cls.extractor = setup_module("instagram_tbot_extractor", request.cls.config) - yield +@pytest.fixture(autouse=True) +def mock_sleep(): + """Globally mock time.sleep to avoid delays.""" + with patch("time.sleep") as mock_sleep: + yield mock_sleep + + @pytest.fixture def metadata_sample(): m = Metadata() m.set_title("Test Title") - m.set_timestamp("2021-01-01T00:00:00Z") + m.set_timestamp("2021-01-01T00:00:00") m.set_url("https://www.instagram.com/p/1234567890") return m -class TestInstagramTbotExtractor: +@pytest.fixture +def mock_telegram_client(): + """Fixture to mock TelegramClient interactions.""" + with patch("auto_archiver.modules.instagram_tbot_extractor.client") as mock_client: + instance = MagicMock() + mock_client.return_value = instance + yield instance + +@pytest.fixture +def extractor(setup_module, patch_extractor_methods): extractor_module = "instagram_tbot_extractor" - extractor: InstagramTbotExtractor config = { "api_id": 12345, "api_hash": "test_api_hash", "session_file": "test_session", + "timeout": 4 + } + extractor = setup_module(extractor_module, config) + extractor.client = MagicMock() + extractor.session_file = "test_session" + return extractor + + +def test_non_instagram_url(extractor, metadata_sample): + metadata_sample.set_url("https://www.youtube.com") + assert extractor.download(metadata_sample) is False + +def test_download_success(extractor, metadata_sample): + with patch.object(extractor, "_send_url_to_bot", return_value=(MagicMock(), 101)), \ + patch.object(extractor, "_process_messages", return_value="Sample Instagram post caption"): + result = extractor.download(metadata_sample) + assert result.is_success() + assert result.status == "insta-via-bot: success" + assert result.metadata.get("title") == "Sample Instagram post caption" + + +def test_download_invalid(extractor, metadata_sample): + with patch.object(extractor, "_send_url_to_bot", return_value=(MagicMock(), 101)), \ + patch.object(extractor, "_process_messages", return_value="You must enter a URL to a post"): + assert extractor.download(metadata_sample) is False + + + +@pytest.mark.skip(reason="Requires authentication.") +class TestInstagramTbotExtractorReal(TestExtractorBase): + # To run these tests set the TELEGRAM_API_ID and TELEGRAM_API_HASH environment variables, and ensure the session file exists. + # Note these are true at this point in time, but changes to source media could be reason for failure. + extractor_module = "instagram_tbot_extractor" + extractor: InstagramTbotExtractor + config = { + "api_id": os.environ.get("TELEGRAM_API_ID"), + "api_hash": os.environ.get("TELEGRAM_API_HASH"), + "session_file": "secrets/anon-insta", } - @pytest.fixture - def mock_telegram_client(self): - """Fixture to mock TelegramClient interactions.""" - with patch("auto_archiver.modules.instagram_tbot_extractor._initialize_telegram_client") as mock_client: - instance = MagicMock() - mock_client.return_value = instance - yield instance - - def test_extractor_is_initialized(self): - assert self.extractor is not None - - - @patch("time.sleep") - @pytest.mark.parametrize("url, expected_status, bot_responses", [ - ("https://www.instagram.com/p/C4QgLbrIKXG", "insta-via-bot: success", [MagicMock(id=101, media=None, message="Are you new to Bellingcat? - The way we share our investigations is different. 💭\nWe want you to read our story but also learn ou")]), - ("https://www.instagram.com/reel/DEVLK8qoIbg/", "insta-via-bot: success", [MagicMock(id=101, media=None, message="Our volunteer community is at the centre of many incredible Bellingcat investigations and tools. Stephanie Ladel is one such vol")]), - # todo tbot not working for stories :( - ("https://www.instagram.com/stories/bellingcatofficial/3556336382743057476/", False, [MagicMock(id=101, media=None, message="Media not found or unavailable")]), - ("https://www.youtube.com/watch?v=ymCMy8OffHM", False, []), - ("https://www.instagram.com/p/INVALID", False, [MagicMock(id=101, media=None, message="You must enter a URL to a post")]), + @pytest.mark.parametrize("url, expected_status, message, len_media", [ + ("https://www.instagram.com/p/C4QgLbrIKXG", "insta-via-bot: success", "Are you new to Bellingcat? - The way we share our investigations is different. 💭\nWe want you to read our story but also learn ou", 6), + ("https://www.instagram.com/reel/DEVLK8qoIbg/", "insta-via-bot: success", "Our volunteer community is at the centre of many incredible Bellingcat investigations and tools. Stephanie Ladel is one such vol", 3), + # instagram tbot not working (potentially intermittently?) for stories - replace with a live story to retest + # ("https://www.instagram.com/stories/bellingcatofficial/3556336382743057476/", False, "Media not found or unavailable"), + # Seems to be working intermittently for highlights + # ("https://www.instagram.com/stories/highlights/17868810693068139/", "insta-via-bot: success", None, 50), + # Marking invalid url as success + ("https://www.instagram.com/p/INVALID", "insta-via-bot: success", "Media not found or unavailable", 0), + ("https://www.youtube.com/watch?v=ymCMy8OffHM", False, None, 0), ]) - def test_download(self, mock_sleep, url, expected_status, bot_responses, metadata_sample): + def test_download(self, url, expected_status, message, len_media, metadata_sample): """Test the `download()` method with various Instagram URLs.""" metadata_sample.set_url(url) - self.extractor.client = MagicMock() + result = self.extractor.download(metadata_sample) - pass - # TODO fully mock or use as authenticated test - # if expected_status: - # assert result.is_success() - # assert result.status == expected_status - # assert result.metadata.get("title") in [msg.message[:128] for msg in bot_responses if msg.message] - # else: - # assert result is False - - - - - # Test story -# Test expired story -# Test requires login/ access (?) -# Test post -# Test multiple images? \ No newline at end of file + if expected_status: + assert result.is_success() + assert result.status == expected_status + assert result.metadata.get("title") == message + assert len(result.media) == len_media + else: + assert result is False diff --git a/tests/feeders/test_gworksheet.py b/tests/feeders/test_gworksheet.py index e6f5cc6..016cfb2 100644 --- a/tests/feeders/test_gworksheet.py +++ b/tests/feeders/test_gworksheet.py @@ -1,3 +1,4 @@ +# Note this isn't a feeder, but contained as utility of the gsheet feeder module import pytest from unittest.mock import MagicMock diff --git a/tests/utils/test_misc.py b/tests/utils/test_misc.py new file mode 100644 index 0000000..e45c1c1 --- /dev/null +++ b/tests/utils/test_misc.py @@ -0,0 +1,146 @@ +import hashlib +import json +from datetime import datetime, timezone +from unittest.mock import Mock, patch + +import pytest + +from auto_archiver.utils.misc import ( + mkdir_if_not_exists, + expand_url, + getattr_or, + DateTimeEncoder, + dump_payload, + get_datetime_from_str, + update_nested_dict, + calculate_file_hash, + random_str, + get_timestamp +) + + +@pytest.fixture +def sample_file(tmp_path): + file_path = tmp_path / "test.txt" + file_path.write_text("test content") + return file_path + + +class TestDirectoryUtils: + def test_mkdir_creates_new_directory(self, tmp_path): + new_dir = tmp_path / "new_folder" + mkdir_if_not_exists(new_dir) + assert new_dir.exists() + assert new_dir.is_dir() + + def test_mkdir_exists_quietly(self, tmp_path): + existing_dir = tmp_path / "existing" + existing_dir.mkdir() + mkdir_if_not_exists(existing_dir) + assert existing_dir.exists() + +class TestURLExpansion: + @pytest.mark.parametrize("input_url,expected", [ + ("https://example.com", "https://example.com"), + ("https://t.co/test", "https://expanded.url") + ]) + def test_expand_url(self, input_url, expected): + mock_response = Mock() + mock_response.url = "https://expanded.url" + with patch('requests.get', return_value=mock_response): + + result = expand_url(input_url) + assert result == expected + + def test_expand_url_handles_errors(self, caplog): + with patch('requests.get', side_effect=Exception("Connection error")): + url = "https://t.co/error" + result = expand_url(url) + assert result == url + assert f"Failed to expand url {url}" in caplog.text + +class TestAttributeHandling: + class Sample: + exists = "value" + none = None + + @pytest.mark.parametrize("obj,attr,default,expected", [ + (Sample(), "exists", "default", "value"), + (Sample(), "none", "default", "default"), + (Sample(), "missing", "default", "default"), + (None, "anything", "fallback", "fallback"), + ]) + def test_getattr_or(self, obj, attr, default, expected): + # Test gets attribute or returns a default value + assert getattr_or(obj, attr, default) == expected + +class TestDateTimeHandling: + def test_datetime_encoder(self, sample_datetime): + result = json.dumps({"dt": sample_datetime}, cls=DateTimeEncoder) + loaded = json.loads(result) + assert loaded["dt"] == str(sample_datetime) + + def test_dump_payload(self, sample_datetime): + payload = {"timestamp": sample_datetime} + result = dump_payload(payload) + assert str(sample_datetime) in result + + @pytest.mark.parametrize("dt_str,fmt,expected", [ + ("2023-01-01 12:00:00+00:00", None, datetime(2023, 1, 1, 12, 0, tzinfo=timezone.utc)), + ("20230101 120000", "%Y%m%d %H%M%S", datetime(2023, 1, 1, 12, 0)), + ("invalid", None, None), + ]) + def test_datetime_from_string(self, dt_str, fmt, expected): + result = get_datetime_from_str(dt_str, fmt) + if expected is None: + assert result is None + else: + assert result == expected.replace(tzinfo=result.tzinfo) + +class TestDictUtils: + @pytest.mark.parametrize("original,update,expected", [ + ({"a": 1}, {"b": 2}, {"a": 1, "b": 2}), + ({"nested": {"a": 1}}, {"nested": {"b": 2}}, {"nested": {"a": 1, "b": 2}}), + ({"a": {"b": {"c": 1}}}, {"a": {"b": {"c": 2}}}, {"a": {"b": {"c": 2}}}), + ]) + def test_update_nested_dict(self, original, update, expected): + update_nested_dict(original, update) + assert original == expected + +class TestHashingUtils: + def test_file_hashing(self, sample_file): + expected = hashlib.sha256(b"test content").hexdigest() + assert calculate_file_hash(str(sample_file)) == expected + + def test_large_file_hashing(self, tmp_path): + file_path = tmp_path / "large.bin" + content = b"0" * 16_000_000 * 2 # 32MB + file_path.write_bytes(content) + + expected = hashlib.sha256(content).hexdigest() + assert calculate_file_hash(str(file_path)) == expected + +class TestMiscUtils: + def test_random_str_length(self): + for length in [8, 16, 32]: + assert len(random_str(length)) == length + + def test_random_str_raises_too_long(self): + with pytest.raises(AssertionError) as exc_info: + random_str(64) + assert "length must be less than 32 as UUID4 is used" == str(exc_info.value) + + def test_random_str_uniqueness(self): + assert random_str() != random_str() + + @pytest.mark.parametrize("ts_input,utc,iso,expected_type", [ + (datetime.now(), True, True, str), + ("2023-01-01T12:00:00+00:00", False, False, datetime), + (1672574400, True, True, str), + ]) + def test_timestamp_parsing(self, ts_input, utc, iso, expected_type): + result = get_timestamp(ts_input, utc=utc, iso=iso) + assert isinstance(result, expected_type) + + def test_invalid_timestamp_returns_none(self): + assert get_timestamp("invalid-date") is None \ No newline at end of file From 3c543a3a6a1d49fdc01337593756006c6515d9aa Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Tue, 18 Feb 2025 19:10:09 +0000 Subject: [PATCH 046/160] Various fixes for issues with new architecture (#208) * Add formatters to the TOC - fixes #204 * Add 'steps' settings to the example YAML in the docs. Fixes #206 * Improved docs on authentication architecture * Fix setting modules on the command line - they now override any module settings in the orchestration as opposed to appending * Fix tests for gsheet-feeder: add a test service_account.json (note: not real keys in there) * Rename the command line entrypoint to _command_line_run Also: make it clear that code implementation should not call this Make sure the command line entry returns (we don't want a generator) * Fix unit tests to use now code-entry points * Version bump * Move iterating of generator up to __main__ * Breakpoint * two minor fixes * Fix unit tests + add new '__main__' entry point implementation test * Skip youtube tests if running on CI. Should still run them locally * Fix full implementation run on GH actions * Fix skipif test for GH Actions CI * Add skipifs for truth - it blocks GH: --------- Co-authored-by: msramalho <19508417+msramalho@users.noreply.github.com> --- docs/scripts/scripts.py | 35 ++++- docs/source/conf.py | 3 + docs/source/core_modules.md | 1 + docs/source/how_to.md | 7 + docs/source/how_to/authentication.md | 57 +++++++ pyproject.toml | 2 +- src/auto_archiver/__main__.py | 2 +- src/auto_archiver/core/base_module.py | 29 ++-- src/auto_archiver/core/config.py | 11 +- src/auto_archiver/core/orchestrator.py | 141 ++++++++++++------ .../modules/gsheet_feeder/__manifest__.py | 2 +- .../templates/html_template.html | 2 +- .../screenshot_enricher/__manifest__.py | 1 - tests/data/test_service_account.json | 14 ++ tests/extractors/test_generic_extractor.py | 9 +- .../extractors/test_twitter_api_extractor.py | 2 +- tests/test_implementation.py | 12 ++ tests/test_orchestrator.py | 68 +++++++-- 18 files changed, 314 insertions(+), 84 deletions(-) create mode 100644 docs/source/how_to/authentication.md create mode 100644 tests/data/test_service_account.json diff --git a/docs/scripts/scripts.py b/docs/scripts/scripts.py index f73315b..9712439 100644 --- a/docs/scripts/scripts.py +++ b/docs/scripts/scripts.py @@ -19,6 +19,19 @@ type_color = { TABLE_HEADER = ("Option", "Description", "Default", "Type") +EXAMPLE_YAML = """ +# steps configuration +steps: +... +{steps_str} +... + +# module configuration +... + +{config_string} +""" + def generate_module_docs(): yaml = YAML() SAVE_FOLDER.mkdir(exist_ok=True) @@ -45,11 +58,14 @@ def generate_module_docs(): ``` {description} """ + steps_str = "\n".join(f" {t}s:\n - {module.name}" for t in manifest['type']) + if not manifest['configs']: - readme_str += "\n*This module has no configuration options.*\n" + config_string = f"# No configuration options for {module.name}.*\n" else: - config_yaml = {} + config_table = header_row + config_yaml = {} for key, value in manifest['configs'].items(): type = value.get('type', 'string') if type == 'auto_archiver.utils.json_loader': @@ -65,11 +81,14 @@ def generate_module_docs(): configs_cheatsheet += f"| `{module.name}.{key}` | {help} | {default} | {type} |\n" readme_str += "\n## Configuration Options\n" readme_str += "\n### YAML\n" - yaml_string = io.BytesIO() - yaml.dump({module.name: config_yaml}, yaml_string) - - readme_str += f"```{{code}} yaml\n{yaml_string.getvalue().decode('utf-8')}\n```\n" + config_string = io.BytesIO() + yaml.dump({module.name: config_yaml}, config_string) + config_string = config_string.getvalue().decode('utf-8') + yaml_string = EXAMPLE_YAML.format(steps_str=steps_str, config_string=config_string) + readme_str += f"```{{code}} yaml\n{yaml_string}\n```\n" + + if manifest['configs']: readme_str += "\n### Command Line:\n" readme_str += config_table @@ -103,3 +122,7 @@ def generate_index(modules_by_type): with open(SAVE_FOLDER / "module_list.md", "w") as f: print("writing", SAVE_FOLDER / "module_list.md") f.write(readme_str) + + +if __name__ == "__main__": + generate_module_docs() \ No newline at end of file diff --git a/docs/source/conf.py b/docs/source/conf.py index e7093c4..5b1ad9b 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -77,3 +77,6 @@ html_theme = 'sphinx_book_theme' html_static_path = ["../_static"] html_css_files = ["custom.css"] +copybutton_prompt_text = r">>> |\.\.\." +copybutton_prompt_is_regexp = True +copybutton_only_copy_prompt_lines = False \ No newline at end of file diff --git a/docs/source/core_modules.md b/docs/source/core_modules.md index 4ee3bfc..3a8e5ec 100644 --- a/docs/source/core_modules.md +++ b/docs/source/core_modules.md @@ -24,4 +24,5 @@ modules/extractor modules/enricher modules/storage modules/database +modules/formatter ``` \ No newline at end of file diff --git a/docs/source/how_to.md b/docs/source/how_to.md index bf3b9fc..25e1e1d 100644 --- a/docs/source/how_to.md +++ b/docs/source/how_to.md @@ -45,3 +45,10 @@ The "archive location" link contains the path of the archived file, in local sto ![The archive result for a link in the demo sheet.](../demo-archive.png) --- + +```{toctree} +:maxdepth: 1 +:glob: + +how_to/* +``` \ No newline at end of file diff --git a/docs/source/how_to/authentication.md b/docs/source/how_to/authentication.md new file mode 100644 index 0000000..5f3bc48 --- /dev/null +++ b/docs/source/how_to/authentication.md @@ -0,0 +1,57 @@ +# Authentication + +The Authentication framework for auto-archiver allows you to add login details for various websites in a flexible way, directly from the configuration file. + +There are two main use cases for authentication: +* Some websites require some kind of authentication in order to view the content. Examples include Facebook, Telegram etc. +* Some websites use anti-bot systems to block bot-like tools from accessig the website. Adding real login information to auto-archiver can sometimes bypass this. + +## The Authentication Config + +You can save your authentication information directly inside your orchestration config file, or as a separate file (for security/multi-deploy purposes). Whether storing your settings inside the orchestration file, or as a separate file, the configuration format is the same. + +```{code} yaml +authentication: + # optional file to load authentication information from, for security or multi-system deploy purposes + load_from_file: path/to/authentication/file.txt + # optional setting to load cookies from the named browser on the system. + cookies_from_browser: firefox + # optional setting to load cookies from a cookies.txt/cookies.jar file. See note below on extracting these + cookies_file: path/to/cookies.jar + + twitter.com,x.com: + username: myusername + password: 123 + + facebook.com: + cookie: single_cookie + + othersite.com: + api_key: 123 + api_secret: 1234 + +# All available options: + # - username: str - the username to use for login + # - password: str - the password to use for login + # - api_key: str - the API key to use for login + # - api_secret: str - the API secret to use for login + # - cookie: str - a cookie string to use for login (specific to this site) +``` + +### Recommendations for authentication + +1. **Store authentication information separately:** +The authentication part of your configuration contains sensitive information. You should make efforts not to share this with others. For extra security, use the `load_from_file` option to keep your authentication settings out of your configuration file, ideally in a different folder. + +2. **Don't use your own personal credentials** +Depending on the website you are extracting information from, there may be rules (Terms of Service) that prohibit you from scraping or extracting information using a bot. If you use your own personal account, there's a possibility it might get blocked/disabled. It's recommended to set up a separate, 'throwaway' account. In that way, if it gets blocked you can easily create another one to continue your archiving. + + +### How to create a cookies.jar or pass cookies directly to auto-archiver + +auto-archiver uses yt-dlp's powerful cookies features under the hood. For instructions on how to extract a cookies.jar (or cookies.txt) file directly from your browser, see the FAQ in the [yt-dlp documentation](https://github.com/yt-dlp/yt-dlp/wiki/FAQ#how-do-i-pass-cookies-to-yt-dlp) + +```{note} For developers: + +For information on how to access and use authentication settings from within your module, see the `{generic_extractor}` for an example, or view the [`auth_for_site()` function in BaseModule](../autoapi/core/base_module/index.rst) +``` \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index cd76b59..9823833 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api" [project] name = "auto-archiver" -version = "0.13.2" +version = "0.13.3" description = "Automatically archive links to videos, images, and social media content from Google Sheets (and more)." requires-python = ">=3.10,<3.13" diff --git a/src/auto_archiver/__main__.py b/src/auto_archiver/__main__.py index 0023a59..f901d21 100644 --- a/src/auto_archiver/__main__.py +++ b/src/auto_archiver/__main__.py @@ -3,7 +3,7 @@ from auto_archiver.core.orchestrator import ArchivingOrchestrator import sys def main(): - ArchivingOrchestrator().run(sys.argv[1:]) + for _ in ArchivingOrchestrator()._command_line_run(sys.argv[1:]): pass if __name__ == "__main__": main() diff --git a/src/auto_archiver/core/base_module.py b/src/auto_archiver/core/base_module.py index ece4719..dfdd5ad 100644 --- a/src/auto_archiver/core/base_module.py +++ b/src/auto_archiver/core/base_module.py @@ -63,12 +63,6 @@ class BaseModule(ABC): def config_setup(self, config: dict): authentication = config.get('authentication', {}) - # extract out concatenated sites - for key, val in copy(authentication).items(): - if "," in key: - for site in key.split(","): - authentication[site] = val - del authentication[key] # this is important. Each instance is given its own deepcopied config, so modules cannot # change values to affect other modules @@ -89,16 +83,21 @@ class BaseModule(ABC): Returns the authentication information for a given site. This is used to authenticate with a site before extracting data. The site should be the domain of the site, e.g. 'twitter.com' - extract_cookies: bool - whether or not to extract cookies from the given browser and return the - cookie jar (disabling can speed up) processing if you don't actually need the cookies jar + :param site: the domain of the site to get authentication information for + :param extract_cookies: whether or not to extract cookies from the given browser/file and return the cookie jar (disabling can speed up processing if you don't actually need the cookies jar). - Currently, the dict can have keys of the following types: - - username: str - the username to use for login - - password: str - the password to use for login - - api_key: str - the API key to use for login - - api_secret: str - the API secret to use for login - - cookie: str - a cookie string to use for login (specific to this site) - - cookies_jar: YoutubeDLCookieJar | http.cookiejar.MozillaCookieJar - a cookie jar compatible with requests (e.g. `requests.get(cookies=cookie_jar)`) + :returns: authdict dict of login information for the given site + + **Global options:**\n + * cookies_from_browser: str - the name of the browser to extract cookies from (e.g. 'chrome', 'firefox' - uses ytdlp under the hood to extract\n + * cookies_file: str - the path to a cookies file to use for login\n + + **Currently, the sites dict can have keys of the following types:**\n + * username: str - the username to use for login\n + * password: str - the password to use for login\n + * api_key: str - the API key to use for login\n + * api_secret: str - the API secret to use for login\n + * cookie: str - a cookie string to use for login (specific to this site)\n """ # TODO: think about if/how we can deal with sites that have multiple domains (main one is x.com/twitter.com) # for now the user must enter them both, like "x.com,twitter.com" in their config. Maybe we just hard-code? diff --git a/src/auto_archiver/core/config.py b/src/auto_archiver/core/config.py index c2d38ee..322ef6e 100644 --- a/src/auto_archiver/core/config.py +++ b/src/auto_archiver/core/config.py @@ -129,6 +129,11 @@ def merge_dicts(dotdict: dict, yaml_dict: CommentedMap) -> CommentedMap: yaml_subdict[key] = value continue + if key == 'steps': + for module_type, modules in value.items(): + # overwrite the 'steps' from the config file with the ones from the CLI + yaml_subdict[key][module_type] = modules + if is_dict_type(value): update_dict(value, yaml_subdict[key]) elif is_list_type(value): @@ -137,7 +142,6 @@ def merge_dicts(dotdict: dict, yaml_dict: CommentedMap) -> CommentedMap: yaml_subdict[key] = value update_dict(from_dot_notation(dotdict), yaml_dict) - return yaml_dict def read_yaml(yaml_filename: str) -> CommentedMap: @@ -159,6 +163,11 @@ def read_yaml(yaml_filename: str) -> CommentedMap: def store_yaml(config: CommentedMap, yaml_filename: str) -> None: config_to_save = deepcopy(config) + auth_dict = config_to_save.get("authentication", {}) + if auth_dict and auth_dict.get('load_from_file'): + # remove all other values from the config, don't want to store it in the config file + auth_dict = {"load_from_file": auth_dict["load_from_file"]} + config_to_save.pop('urls', None) with open(yaml_filename, "w", encoding="utf-8") as outf: _yaml.dump(config_to_save, outf) \ No newline at end of file diff --git a/src/auto_archiver/core/orchestrator.py b/src/auto_archiver/core/orchestrator.py index 9dd3e06..208512a 100644 --- a/src/auto_archiver/core/orchestrator.py +++ b/src/auto_archiver/core/orchestrator.py @@ -8,6 +8,7 @@ from __future__ import annotations from typing import Generator, Union, List, Type from urllib.parse import urlparse from ipaddress import ip_address +from copy import copy import argparse import os import sys @@ -43,30 +44,50 @@ class AuthenticationJsonParseAction(JsonParseAction): def __call__(self, parser, namespace, values, option_string=None): super().__call__(parser, namespace, values, option_string) auth_dict = getattr(namespace, self.dest) - if isinstance(auth_dict, str): - # if it's a string + + def load_from_file(path): try: - with open(auth_dict, 'r') as f: + with open(path, 'r') as f: try: auth_dict = json.load(f) except json.JSONDecodeError: + f.seek(0) # maybe it's yaml, try that auth_dict = _yaml.load(f) + if auth_dict.get('authentication'): + auth_dict = auth_dict['authentication'] + auth_dict['load_from_file'] = path + return auth_dict except: - pass + return None + if isinstance(auth_dict, dict) and auth_dict.get('from_file'): + auth_dict = load_from_file(auth_dict['from_file']) + elif isinstance(auth_dict, str): + # if it's a string + auth_dict = load_from_file(auth_dict) + if not isinstance(auth_dict, dict): raise argparse.ArgumentTypeError("Authentication must be a dictionary of site names and their authentication methods") - for site, auth in auth_dict.items(): - if not isinstance(site, str) or not isinstance(auth, dict): - raise argparse.ArgumentTypeError("Authentication must be a dictionary of site names and their authentication methods") + global_options = ['cookies_from_browser', 'cookies_file', 'load_from_file'] + for key, auth in auth_dict.items(): + if key in global_options: + continue + if not isinstance(key, str) or not isinstance(auth, dict): + raise argparse.ArgumentTypeError(f"Authentication must be a dictionary of site names and their authentication methods. Valid global configs are {global_options}") + + # extract out concatenated sites + for key, val in copy(auth_dict).items(): + if "," in key: + for site in key.split(","): + auth_dict[site] = val + del auth_dict[key] + setattr(namespace, self.dest, auth_dict) class UniqueAppendAction(argparse.Action): def __call__(self, parser, namespace, values, option_string=None): - if not hasattr(namespace, self.dest): - setattr(namespace, self.dest, []) for value in values: if value not in getattr(namespace, self.dest): getattr(namespace, self.dest).append(value) @@ -104,36 +125,50 @@ class ArchivingOrchestrator: return parser def setup_complete_parser(self, basic_config: dict, yaml_config: dict, unused_args: list[str]) -> None: + + + # modules parser to get the overridden 'steps' values + modules_parser = argparse.ArgumentParser( + add_help=False, + ) + self.add_modules_args(modules_parser) + cli_modules, unused_args = modules_parser.parse_known_args(unused_args) + for module_type in BaseModule.MODULE_TYPES: + yaml_config['steps'][f"{module_type}s"] = getattr(cli_modules, f"{module_type}s", []) or yaml_config['steps'].get(f"{module_type}s", []) + parser = DefaultValidatingParser( add_help=False, ) self.add_additional_args(parser) + # merge command line module args (--feeders, --enrichers etc.) and add them to the config + # check what mode we're in # if we have a config file, use that to decide which modules to load # if simple, we'll load just the modules that has requires_setup = False # if full, we'll load all modules # TODO: BUG** - basic_config won't have steps in it, since these args aren't added to 'basic_parser' # but should we add them? Or should we just add them to the 'complete' parser? + if yaml_config != EMPTY_CONFIG: # only load the modules enabled in config # TODO: if some steps are empty (e.g. 'feeders' is empty), should we default to the 'simple' ones? Or only if they are ALL empty? enabled_modules = [] # first loads the modules from the config file, then from the command line - for config in [yaml_config['steps'], basic_config.__dict__]: - for module_type in BaseModule.MODULE_TYPES: - enabled_modules.extend(config.get(f"{module_type}s", [])) + for module_type in BaseModule.MODULE_TYPES: + enabled_modules.extend(yaml_config['steps'].get(f"{module_type}s", [])) # clear out duplicates, but keep the order enabled_modules = list(dict.fromkeys(enabled_modules)) avail_modules = available_modules(with_manifest=True, limit_to_modules=enabled_modules, suppress_warnings=True) - self.add_module_args(avail_modules, parser) + self.add_individual_module_args(avail_modules, parser) elif basic_config.mode == 'simple': simple_modules = [module for module in available_modules(with_manifest=True) if not module.requires_setup] - self.add_module_args(simple_modules, parser) + self.add_individual_module_args(simple_modules, parser) # for simple mode, we use the cli_feeder and any modules that don't require setup - yaml_config['steps']['feeders'] = ['cli_feeder'] + if not yaml_config['steps']['feeders']: + yaml_config['steps']['feeders'] = ['cli_feeder'] # add them to the config for module in simple_modules: @@ -141,30 +176,38 @@ class ArchivingOrchestrator: yaml_config['steps'].setdefault(f"{module_type}s", []).append(module.name) else: # load all modules, they're not using the 'simple' mode - self.add_module_args(available_modules(with_manifest=True), parser) - + self.add_individual_module_args(available_modules(with_manifest=True), parser) + parser.set_defaults(**to_dot_notation(yaml_config)) # reload the parser with the new arguments, now that we have them parsed, unknown = parser.parse_known_args(unused_args) - # merge the new config with the old one - self.config = merge_dicts(vars(parsed), yaml_config) + config = merge_dicts(vars(parsed), yaml_config) + # clean out args from the base_parser that we don't want in the config for key in vars(basic_config): - self.config.pop(key, None) + config.pop(key, None) # setup the logging - self.setup_logging() + self.setup_logging(config) if unknown: logger.warning(f"Ignoring unknown/unused arguments: {unknown}\nPerhaps you don't have this module enabled?") - if (self.config != yaml_config and basic_config.store) or not os.path.isfile(basic_config.config_file): + if (config != yaml_config and basic_config.store) or not os.path.isfile(basic_config.config_file): logger.info(f"Storing configuration file to {basic_config.config_file}") - store_yaml(self.config, basic_config.config_file) + store_yaml(config, basic_config.config_file) - return self.config + return config + + def add_modules_args(self, parser: argparse.ArgumentParser = None): + if not parser: + parser = self.parser + + # Module loading from the command line + for module_type in BaseModule.MODULE_TYPES: + parser.add_argument(f'--{module_type}s', dest=f'{module_type}s', nargs='+', help=f'the {module_type}s to use', default=[], action=UniqueAppendAction) def add_additional_args(self, parser: argparse.ArgumentParser = None): if not parser: @@ -173,30 +216,24 @@ class ArchivingOrchestrator: # allow passing URLs directly on the command line parser.add_argument('urls', nargs='*', default=[], help='URL(s) to archive, either a single URL or a list of urls, should not come from config.yaml') - parser.add_argument('--feeders', dest='steps.feeders', nargs='+', default=['cli_feeder'], help='the feeders to use', action=UniqueAppendAction) - parser.add_argument('--enrichers', dest='steps.enrichers', nargs='+', help='the enrichers to use', action=UniqueAppendAction) - parser.add_argument('--extractors', dest='steps.extractors', nargs='+', help='the extractors to use', action=UniqueAppendAction) - parser.add_argument('--databases', dest='steps.databases', nargs='+', help='the databases to use', action=UniqueAppendAction) - parser.add_argument('--storages', dest='steps.storages', nargs='+', help='the storages to use', action=UniqueAppendAction) - parser.add_argument('--formatters', dest='steps.formatters', nargs='+', help='the formatter to use', action=UniqueAppendAction) - parser.add_argument('--authentication', dest='authentication', help='A dictionary of sites and their authentication methods \ (token, username etc.) that extractors can use to log into \ a website. If passing this on the command line, use a JSON string. \ You may also pass a path to a valid JSON/YAML file which will be parsed.', default={}, + nargs="?", action=AuthenticationJsonParseAction) + # logging arguments parser.add_argument('--logging.level', action='store', dest='logging.level', choices=['INFO', 'DEBUG', 'ERROR', 'WARNING'], help='the logging level to use', default='INFO', type=str.upper) parser.add_argument('--logging.file', action='store', dest='logging.file', help='the logging file to write to', default=None) parser.add_argument('--logging.rotation', action='store', dest='logging.rotation', help='the logging rotation to use', default=None) - def add_module_args(self, modules: list[LazyBaseModule] = None, parser: argparse.ArgumentParser = None) -> None: + def add_individual_module_args(self, modules: list[LazyBaseModule] = None, parser: argparse.ArgumentParser = None) -> None: if not modules: modules = available_modules(with_manifest=True) - - module: LazyBaseModule + for module in modules: if not module.configs: @@ -226,18 +263,19 @@ class ArchivingOrchestrator: arg.should_store = should_store def show_help(self, basic_config: dict): - # for the help message, we want to load *all* possible modules and show the help + # for the help message, we want to load manifests from *all* possible modules and show their help/settings # add configs as arg parser arguments + self.add_modules_args(self.basic_parser) self.add_additional_args(self.basic_parser) - self.add_module_args(parser=self.basic_parser) + self.add_individual_module_args(parser=self.basic_parser) self.basic_parser.print_help() self.basic_parser.exit() - def setup_logging(self): + def setup_logging(self, config): # setup loguru logging logger.remove(0) # remove the default logger - logging_config = self.config['logging'] + logging_config = config['logging'] logger.add(sys.stderr, level=logging_config['level']) if log_file := logging_config['file']: logger.add(log_file) if not logging_config['rotation'] else logger.add(log_file, rotation=logging_config['rotation']) @@ -318,9 +356,9 @@ class ArchivingOrchestrator: return read_yaml(config_file) - def setup(self, args: list): + def setup_config(self, args: list) -> dict: """ - Main entry point for the orchestrator, sets up the basic parser, loads the config file, and sets up the complete parser + Sets up the configuration file, merging the default config with the user's config """ self.setup_basic_parser() @@ -333,9 +371,16 @@ class ArchivingOrchestrator: # if help flag was called, then show the help if basic_config.help: self.show_help(basic_config) - + # merge command line --feeder etc. args with what's in the yaml config yaml_config = self.load_config(basic_config.config_file) - self.setup_complete_parser(basic_config, yaml_config, unused_args) + + return self.setup_complete_parser(basic_config, yaml_config, unused_args) + + def setup(self, args: list): + """ + Main entry point for the orchestrator, sets up the basic parser, loads the config file, and sets up the complete parser + """ + self.config = self.setup_config(args) logger.info(f"======== Welcome to the AUTO ARCHIVER ({__version__}) ==========") self.install_modules(self.config['steps']) @@ -344,8 +389,18 @@ class ArchivingOrchestrator: for module_type in BaseModule.MODULE_TYPES: logger.info(f"{module_type.upper()}S: " + ", ".join(m.display_name for m in getattr(self, f"{module_type}s"))) - def run(self, args: list) -> Generator[Metadata]: + def _command_line_run(self, args: list) -> Generator[Metadata]: + """ + This is the main entry point for the orchestrator, when run from the command line. + :param args: list of arguments to pass to the orchestrator - these are the command line args + + You should not call this method from code implementations. + + This method sets up the configuration, loads the modules, and runs the feed. + If you wish to make code invocations yourself, you should use the 'setup' and 'feed' methods separately. + To test configurations, without loading any modules you can also first call 'setup_configs' + """ self.setup(args) return self.feed() diff --git a/src/auto_archiver/modules/gsheet_feeder/__manifest__.py b/src/auto_archiver/modules/gsheet_feeder/__manifest__.py index 7b74072..77026ea 100644 --- a/src/auto_archiver/modules/gsheet_feeder/__manifest__.py +++ b/src/auto_archiver/modules/gsheet_feeder/__manifest__.py @@ -10,7 +10,7 @@ "sheet": {"default": None, "help": "name of the sheet to archive"}, "sheet_id": { "default": None, - "help": "(alternative to sheet name) the id of the sheet to archive", + "help": "the id of the sheet to archive (alternative to 'sheet' config)", }, "header": {"default": 1, "help": "index of the header row (starts at 1)", "type": "int"}, "service_account": { diff --git a/src/auto_archiver/modules/html_formatter/templates/html_template.html b/src/auto_archiver/modules/html_formatter/templates/html_template.html index 8bdf5ef..62d6b0b 100644 --- a/src/auto_archiver/modules/html_formatter/templates/html_template.html +++ b/src/auto_archiver/modules/html_formatter/templates/html_template.html @@ -200,7 +200,7 @@ el.innerHTML = decodeCertificate(certificate); let cyberChefUrl = - `https://gchq.github.io/CyberChef/#recipe=Parse_X.509_certificate('PEM')&input=${btoa(certificate)}`; + `https://gchq.github.io/CyberChef/#recipe=Parse_X.509_certificate('PEM')&input=${btoa(certificate).replace(/=+$/, '')}`; // create a new anchor with this url and append after the code let a = document.createElement("a"); a.href = cyberChefUrl; diff --git a/src/auto_archiver/modules/screenshot_enricher/__manifest__.py b/src/auto_archiver/modules/screenshot_enricher/__manifest__.py index 831959e..9829844 100644 --- a/src/auto_archiver/modules/screenshot_enricher/__manifest__.py +++ b/src/auto_archiver/modules/screenshot_enricher/__manifest__.py @@ -4,7 +4,6 @@ "requires_setup": True, "dependencies": { "python": ["loguru", "selenium"], - "bin": ["geckodriver"] }, "configs": { "width": {"default": 1280, "help": "width of the screenshots"}, diff --git a/tests/data/test_service_account.json b/tests/data/test_service_account.json new file mode 100644 index 0000000..5aae894 --- /dev/null +++ b/tests/data/test_service_account.json @@ -0,0 +1,14 @@ +{ + "type": "service_account", + "project_id": "some-project-id", + "private_key_id": "some-private-key-id", + "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDPlcaFJgt7HzoC\n4z0b18PzI2R5c892mLnNwRO8DOKid5INt6z5RAWKDPdnIyHjRBx74qNZl6768pia\nztQNgnud7mKcmvOvGrpUbFx2BdAw8xTyAlRVMalOBhUS9RKvjP5WgSwR5EKwfvzy\nrGioC6ml/segz5EchSaIzgASwB17ir0w6IrymBxUeNelfzCGJpCRhqG5nG+eEjct\nUYU0QIyihRD1Lq0f3Z3D0xfTLLZ630iFBj/Wr0BCJHkl6hdVuGhnyn4S98sMX1Bd\ntaJF/lWi4jdt7SoXD3+FWv66kHPpFfINMpReuB9u0ogfYkORgiRBOMhYBkGGQjUG\nOnBTxEc3AgMBAAECgf9bKiK8DdSz0ALzQbRLhgj2B9485jHI49wjgINOyceZ23uS\nQYXaO+DFLcgLqBkVSGanuHMpU0+qCpeM0v9yXSTIW8RguWMnFd8ID/yLRktxfQa1\n1FAQh+NlF4/gnuUoM8N/FYSy6R5grfaxwU8Qfg66IQXUB52OezSVu5lxNO4G5Rwv\nJ2e/+XYBUv/H26BnQSmjFCzbJkdbtrOeThpaLwLexKcollvoHKGyus0jpWg4C9Ez\n9EJaE+on4nd+cM1Vd+dWaHXoZ9Db9IvxPBqFJE8fynap7RDBeZK678OuCvQntrp4\nrTsE9hW8073Jhl/LbhfbDC0lhFR0JUHygVGE01ECgYEA+g+ddpGGY90yhhM76bTr\nkU6WwislMmfS0WDdLPemNgzLwCtkC2vsQgzg/egxqkVF5dJ9upiFhVgpYxY7ap9U\nSGFemb6T1ASl/1yeNhd0yc4PZFsJ29k+kNgSIlJYm9KDCIMqS1wPoXvFQhbMitOf\n/gLCPugxl67c+qg6nfuODTkCgYEA1IPngESOJnV8oa2WReWrO6+u6xb/OhqdmBzI\n5yq1z3f5gb98XESZR/rCH2vAOmHIJPn3XdZHsznOuxhZwGr1oztiRIurLmBlxQoL\n7tq0jDOUVSD2yeyQwKt5LaBH94P598FiauGxXM4raREWKtcNBGoOX1u1+kEBsoL4\ntf10Z+8CgYEA3QFkB+ECR8y91KW3NAzEjj5JG/8J9wyv1IGpuQ5/hhG1Gni/CSEv\nRAkh6QaIrpZe+ooYuQwIJhwPKBYEGW4MDZSRCYzYFnCtTY5L/j6o55sJG4cipX3R\nwC5XiKIC0mUxjhpvDP+miPBdHNYNnT0AkH1btEF/YzIW+Coq9GnZ2HECgYAOOpax\ne+WYpZ0mphy9qVcBtA2eJ/gGx+ltWeAJuk5aCcpm6Y9GDkHFFAETYX+JaSqhbysk\n2UgLs/8nf8XioEa6GyvFMyTPAh1OSBHseDBGgt2XpZFgi7pVbCW87FJlPCzsbcJN\nLbdWY2d8rWwyihuRBBjaQaW5j8ixTxuf88xreQKBgQCST4Fr8C5CkpakTA+KOost\nLOlziUBm0534mTg7dTcOE1H1+gxtqpXlXcJylpGz1lUXRlHCIutN5iPJcN5cxFES\nsP7wBd7BhficsMKDiWPm9XbP2zXVZu0ldUxA1mONMsS1P4p7i3Dh4uzrRDmSkTUL\njUpppYDumg3oM7wSJ6sTQA==\n-----END PRIVATE KEY-----", + "client_email": "some-email", + "client_id": "some-client-email", + "auth_uri": "https://example.com/o/oauth2/auth", + "token_uri": "https://oauth2.example.com/token", + "auth_provider_x509_cert_url": "https://www.example.com/oauth2/v1/certs", + "client_x509_cert_url": "https://www.example.com/robot/v1/metadata/x509/some-email", + "universe_domain": "example.com" + } + \ No newline at end of file diff --git a/tests/extractors/test_generic_extractor.py b/tests/extractors/test_generic_extractor.py index c70a51f..54f4d9c 100644 --- a/tests/extractors/test_generic_extractor.py +++ b/tests/extractors/test_generic_extractor.py @@ -9,6 +9,7 @@ import pytest from auto_archiver.modules.generic_extractor.generic_extractor import GenericExtractor from .test_extractor_base import TestExtractorBase +CI=os.getenv("GITHUB_ACTIONS", '') == 'true' class TestGenericExtractor(TestExtractorBase): """Tests Generic Extractor """ @@ -77,10 +78,11 @@ class TestGenericExtractor(TestExtractorBase): result = self.extractor.download(item) assert not result - + @pytest.mark.skipif(CI, reason="Currently no way to authenticate when on CI. Youtube (yt-dlp) doesn't support logging in with username/password.") @pytest.mark.download def test_youtube_download(self, make_item): # url https://www.youtube.com/watch?v=5qap5aO4i9A + item = make_item("https://www.youtube.com/watch?v=J---aiyznGQ") result = self.extractor.download(item) assert result.get_url() == "https://www.youtube.com/watch?v=J---aiyznGQ" @@ -114,6 +116,7 @@ class TestGenericExtractor(TestExtractorBase): result = self.extractor.download(item) assert result is not False + @pytest.mark.skipif(CI, reason="Truth social blocks GH actions.") @pytest.mark.download def test_truthsocial_download_video(self, make_item): item = make_item("https://truthsocial.com/@DaynaTrueman/posts/110602446619561579") @@ -121,18 +124,21 @@ class TestGenericExtractor(TestExtractorBase): assert len(result.media) == 1 assert result is not False + @pytest.mark.skipif(CI, reason="Truth social blocks GH actions.") @pytest.mark.download def test_truthsocial_download_no_media(self, make_item): item = make_item("https://truthsocial.com/@bbcnewa/posts/109598702184774628") result = self.extractor.download(item) assert result is not False + @pytest.mark.skipif(CI, reason="Truth social blocks GH actions.") @pytest.mark.download def test_truthsocial_download_poll(self, make_item): item = make_item("https://truthsocial.com/@CNN_US/posts/113724326568555098") result = self.extractor.download(item) assert result is not False + @pytest.mark.skipif(CI, reason="Truth social blocks GH actions.") @pytest.mark.download def test_truthsocial_download_single_image(self, make_item): item = make_item("https://truthsocial.com/@mariabartiromo/posts/113861116433335006") @@ -140,6 +146,7 @@ class TestGenericExtractor(TestExtractorBase): assert len(result.media) == 1 assert result is not False + @pytest.mark.skipif(CI, reason="Truth social blocks GH actions.") @pytest.mark.download def test_truthsocial_download_multiple_images(self, make_item): item = make_item("https://truthsocial.com/@trrth/posts/113861302149349135") diff --git a/tests/extractors/test_twitter_api_extractor.py b/tests/extractors/test_twitter_api_extractor.py index 004376c..26394ac 100644 --- a/tests/extractors/test_twitter_api_extractor.py +++ b/tests/extractors/test_twitter_api_extractor.py @@ -34,7 +34,7 @@ class TestTwitterApiExtractor(TestExtractorBase): @pytest.mark.download def test_sanitize_url_download(self): - assert "https://t.co/yl3oOJatFp" == self.extractor.sanitize_url("https://www.bellingcat.com/category/resources/") + assert "https://www.bellingcat.com/category/resources/" == self.extractor.sanitize_url("https://t.co/yl3oOJatFp") @pytest.mark.parametrize("url, exptected_username, exptected_tweetid", [ ("https://twitter.com/bellingcat/status/1874097816571961839", "bellingcat", "1874097816571961839"), diff --git a/tests/test_implementation.py b/tests/test_implementation.py index 7e33651..85fc448 100644 --- a/tests/test_implementation.py +++ b/tests/test_implementation.py @@ -60,3 +60,15 @@ def test_run_auto_archiver_empty_file(caplog, autoarchiver, orchestration_file): # should treat an empty file as if there is no file at all assert " No URLs provided. Please provide at least one URL via the com" in caplog.text + +def test_call_autoarchiver_main(caplog, monkeypatch, tmp_path): + from auto_archiver.__main__ import main + + # monkey patch to change the current working directory, so that we don't use the user's real config file + monkeypatch.chdir(tmp_path) + with monkeypatch.context() as m: + m.setattr(sys, "argv", ["auto-archiver"]) + with pytest.raises(SystemExit): + main() + + assert "No URLs provided. Please provide at least one" in caplog.text \ No newline at end of file diff --git a/tests/test_orchestrator.py b/tests/test_orchestrator.py index 5ba57d0..f93f8b8 100644 --- a/tests/test_orchestrator.py +++ b/tests/test_orchestrator.py @@ -75,18 +75,36 @@ def test_help(orchestrator, basic_parser, capsys): orchestrator.show_help(args) assert exit_error.value.code == 0 - assert "Usage: auto-archiver [--help] [--version] [--config CONFIG_FILE]" in capsys.readouterr().out + + logs = capsys.readouterr().out + assert "Usage: auto-archiver [--help] [--version] [--config CONFIG_FILE]" in logs + + # basic config options + assert "--version" in logs + + # setting modules options + assert "--feeders" in logs + assert "--extractors" in logs + + # authentication options + assert "--authentication" in logs + + # logging options + assert "--logging.level" in logs + + # individual module configs + assert "--gsheet_feeder.sheet_id" in logs def test_add_custom_modules_path(orchestrator, test_args): - orchestrator.run(test_args) + orchestrator.setup_config(test_args) import auto_archiver assert "tests/data/test_modules/" in auto_archiver.modules.__path__ def test_add_custom_modules_path_invalid(orchestrator, caplog, test_args): - orchestrator.run(test_args + # we still need to load the real path to get the example_module + orchestrator.setup_config(test_args + # we still need to load the real path to get the example_module ["--module_paths", "tests/data/invalid_test_modules/"]) assert caplog.records[0].message == "Path 'tests/data/invalid_test_modules/' does not exist. Skipping..." @@ -97,7 +115,7 @@ def test_check_required_values(orchestrator, caplog, test_args): test_args = test_args[:-2] with pytest.raises(SystemExit) as exit_error: - orchestrator.run(test_args) + config = orchestrator.setup_config(test_args) assert caplog.records[1].message == "the following arguments are required: --example_module.required_field" @@ -111,24 +129,50 @@ def test_get_required_values_from_config(orchestrator, test_args, tmp_path): store_yaml(test_yaml, tmp_file) # run the orchestrator - orchestrator.run(["--config", tmp_file, "--module_paths", TEST_MODULES]) - assert orchestrator.config is not None + config = orchestrator.setup_config(["--config", tmp_file, "--module_paths", TEST_MODULES]) + assert config is not None def test_load_authentication_string(orchestrator, test_args): - orchestrator.run(test_args + ["--authentication", '{"facebook.com": {"username": "my_username", "password": "my_password"}}']) - assert orchestrator.config['authentication'] == {"facebook.com": {"username": "my_username", "password": "my_password"}} + config = orchestrator.setup_config(test_args + ["--authentication", '{"facebook.com": {"username": "my_username", "password": "my_password"}}']) + assert config['authentication'] == {"facebook.com": {"username": "my_username", "password": "my_password"}} def test_load_authentication_string_concat_site(orchestrator, test_args): - orchestrator.run(test_args + ["--authentication", '{"x.com,twitter.com": {"api_key": "my_key"}}']) - assert orchestrator.config['authentication'] == {"x.com": {"api_key": "my_key"}, + config = orchestrator.setup_config(test_args + ["--authentication", '{"x.com,twitter.com": {"api_key": "my_key"}}']) + assert config['authentication'] == {"x.com": {"api_key": "my_key"}, "twitter.com": {"api_key": "my_key"}} def test_load_invalid_authentication_string(orchestrator, test_args): with pytest.raises(ArgumentTypeError): - orchestrator.run(test_args + ["--authentication", "{\''invalid_json"]) + orchestrator.setup_config(test_args + ["--authentication", "{\''invalid_json"]) def test_load_authentication_invalid_dict(orchestrator, test_args): with pytest.raises(ArgumentTypeError): - orchestrator.run(test_args + ["--authentication", "[true, false]"]) \ No newline at end of file + orchestrator.setup_config(test_args + ["--authentication", "[true, false]"]) + +def test_load_modules_from_commandline(orchestrator, test_args): + args = test_args + ["--feeders", "example_module", "--extractors", "example_module", "--databases", "example_module", "--enrichers", "example_module", "--formatters", "example_module"] + + orchestrator.setup(args) + + assert len(orchestrator.feeders) == 1 + assert len(orchestrator.extractors) == 1 + assert len(orchestrator.databases) == 1 + assert len(orchestrator.enrichers) == 1 + assert len(orchestrator.formatters) == 1 + + assert orchestrator.feeders[0].name == "example_module" + assert orchestrator.extractors[0].name == "example_module" + assert orchestrator.databases[0].name == "example_module" + assert orchestrator.enrichers[0].name == "example_module" + assert orchestrator.formatters[0].name == "example_module" + +def test_load_settings_for_module_from_commandline(orchestrator, test_args): + args = test_args + ["--feeders", "gsheet_feeder", "--gsheet_feeder.sheet_id", "123", "--gsheet_feeder.service_account", "tests/data/test_service_account.json"] + + orchestrator.setup(args) + + assert len(orchestrator.feeders) == 1 + assert orchestrator.feeders[0].name == "gsheet_feeder" + assert orchestrator.config['gsheet_feeder']['sheet_id'] == "123" \ No newline at end of file From 7b88df72cb4d945321855039e133634f00e4fc3d Mon Sep 17 00:00:00 2001 From: erinhmclark Date: Tue, 18 Feb 2025 19:46:57 +0000 Subject: [PATCH 047/160] Update test_metadata_enricher.py --- tests/data/metadata_enricher_exif.pickle | Bin 0 -> 1175 bytes .../metadata_enricher_ytshort_expected.pickle | Bin 12524 -> 12233 bytes .../metadata_enricher_ytshort_input.pickle | Bin 10840 -> 10840 bytes tests/enrichers/test_metadata_enricher.py | 10 +++++----- 4 files changed, 5 insertions(+), 5 deletions(-) create mode 100644 tests/data/metadata_enricher_exif.pickle diff --git a/tests/data/metadata_enricher_exif.pickle b/tests/data/metadata_enricher_exif.pickle new file mode 100644 index 0000000000000000000000000000000000000000..5607a9b92cb87837806cf436f42a98db5f651c72 GIT binary patch literal 1175 zcma)6O>fgc5RF8H(4P1Q%&`?D**L9|?v1z!O;MtV251im+402LVtcLKwVEO!!QD5O z-wW$FLBS!Ek)`#=n>TOX?)Yo{M_#Y3BPS~JQz_=Cl*ymEB2FXJ7)&>>zMJE3=8HMX z>z)ji`C#v!;EFG_5Ru8BHCsAP>Bp{GEyKH3?5!lK02f{s>E>-38qA4AoylY_0yM8{ z`D3L6k!f@N^HHrfutYOU1n*EPE)tkzv5ykA!Jyq~wka9%2w~#IxcyLB8SGUi=Un1k z3wd9`ZXX%^p}nke#Xlde(dojceu*24fG_ym(}n1BPh)GwH|iKT)H!9;VeKBY&)A8> zPU&+>8Qt{pa$a~{$tqPnm^(_Wjo>c5B*Y5CgSk_bF;|7nYU{yvWzdj!4cREJvccJX zdf14|>EP84Y_QUpthaB=SF8-|{*&=&Jb-_a@z@Kob(>;HE{CJx@1wBGwYj&)p-J21 z4Jr|3YwO=8T<-xHOhkg@8g_ttph94q-24Y-R0xFkJkZO(u&jBU9wjC00(rRHmt+$e zbR9|t#USt`gw1?a85#6Fg%MBe_PVndaFHz*v6n!ZMfzMTup0?iUNNWDY+Iy9ToDNg z#UNW35{s_{gO2Un^})3?nO{;Bl6Hc@jqAJeggK1Dkps^0roY1jV(qjxtcoVb7SRS0`N?t2ZHzga zpEC0?au~#$7guDYmv8>fBCR^vS5FNtGPzpMj>|&9(9+7p$ja1Ua<`U>fT@*%k(D8m z;C?LwWHpBRYejn)4X5<*L@ETO7U$=bmSpDVP01*o{89fAZx3gDZf0(3No7Im6u-%* zbmb6seb8-2aLBTFolq$@Tl2&->Usp6^KOs7D5yZbh!JB``>@IK$~`+mNk&->&{{WnZLOYHyP zUM2SjhsUNa4uAgs;cYN5^fiWu9-~GZ>L{2@TnQe;U&K-ezv3gY)Wf=4M%HfQ;7?*= zYBbl9B^6fI?(W?x7w8QuxH)rS;<$(4^rG29VBOq91SfeBsDU#xl@k@7R|FaGf+Cd^ zNe*!SYCrfX&t-~$pH+&oB8b6ZUg~XC=T(BoqM@}XZ3L=a57`~lu?bFu^psIuQmY^= z5}esJo5;D&3R!x08Ehh#wa0jZM*^(9;ulY(wrt$ufY~wCcy)%Lw8dBu4(IFuH7a$y9{@?OMF2t@Ek|*(3Wd@|0?D- z%^p<)7F}o|um(Lkb3l;jttE@T6WB7Fo)OY;WjZR$z}v{w4KGAzg%GW|PAFRq*Xv7J@NW9@$uAOon|^+JxIc5_G|m!@eE#%U3eJRGqG5kOMx&U$ oN0ZfYT7I;-u@ii}_VbeQdc}D2(r+M~s%wtzp{_@s62DRS3xGgQB>(^b diff --git a/tests/data/metadata_enricher_ytshort_input.pickle b/tests/data/metadata_enricher_ytshort_input.pickle index 5f1a4eb4317bf69f2ccf59306fb645985a70c648..2495c4696c8e0b08c6a29ec038797165741e00c9 100644 GIT binary patch delta 72 zcmcZ+awBBIEOsGbEhA2wH5(UBX5zle*u!0(npBXOo;q2PMSgM|a~osM=BLbjj2s5> V=EW5m>E)Y$vk0rgd6UJooB*Uq7@Gh9 delta 72 zcmcZ+awBBIEOuUDRSDihmp3k)%*1_;v4^`nHK`ymJ$14oi~Qs`<~Bx-%}<&67&)@y Wi!4%e%aS(#W)W6}^CpXFIROB@a2W{z diff --git a/tests/enrichers/test_metadata_enricher.py b/tests/enrichers/test_metadata_enricher.py index c6190ed..aedcf54 100644 --- a/tests/enrichers/test_metadata_enricher.py +++ b/tests/enrichers/test_metadata_enricher.py @@ -76,14 +76,14 @@ def test_get_metadata_error_handling(mock_run, mock_logger_error, enricher): mock_logger_error.assert_called_once() -@pytest.mark.skip(reason="Requires ExifTool to be installed. TODO mock") -def test_metadata_pickle(enricher, unpickle): - # Uses a pickle of a YouTube short +@patch("subprocess.run") +def test_metadata_pickle(mock_run, enricher, unpickle): + # Uses pickled values + mock_run.return_value = unpickle("metadata_enricher_exif.pickle") metadata = unpickle("metadata_enricher_ytshort_input.pickle") expected = unpickle("metadata_enricher_ytshort_expected.pickle") enricher.enrich(metadata) expected_media = expected.media actual_media = metadata.media assert len(expected_media) == len(actual_media) - assert actual_media[0].properties.get("metadata") == expected_media[0].properties.get("metadata") - assert metadata == expected \ No newline at end of file + assert actual_media[0].properties.get("metadata") == expected_media[0].properties.get("metadata") \ No newline at end of file From f0fd9bf44596485fb53ae4bd4347319d3a42806f Mon Sep 17 00:00:00 2001 From: erinhmclark Date: Tue, 18 Feb 2025 23:32:03 +0000 Subject: [PATCH 048/160] Updates tests to use pytest-mock. --- poetry.lock | 20 +- pyproject.toml | 1 + tests/conftest.py | 26 ++- tests/databases/test_api_db.py | 52 +++--- tests/databases/test_gsheet_db.py | 39 ++-- tests/enrichers/test_meta_enricher.py | 19 +- tests/enrichers/test_metadata_enricher.py | 43 +++-- tests/enrichers/test_pdq_hash_enricher.py | 62 +++---- tests/enrichers/test_screenshot_enricher.py | 174 +++++++++--------- tests/enrichers/test_ssl_enricher.py | 36 ++-- tests/enrichers/test_thumbnail_enricher.py | 141 +++++++------- tests/enrichers/test_whisper_enricher.py | 72 ++++---- .../test_instagram_api_extractor.py | 123 ++++++------- .../test_instagram_tbot_extractor.py | 55 +++--- tests/feeders/test_gsheet_feeder.py | 114 ++++++------ tests/feeders/test_gworksheet.py | 9 +- tests/storages/test_S3_storage.py | 113 +++++------- tests/storages/test_gdrive_storage.py | 61 +++--- tests/utils/test_misc.py | 24 ++- 19 files changed, 584 insertions(+), 600 deletions(-) diff --git a/poetry.lock b/poetry.lock index c8524b4..83b2860 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1815,6 +1815,24 @@ loguru = "*" [package.extras] test = ["pytest", "pytest-cov"] +[[package]] +name = "pytest-mock" +version = "3.14.0" +description = "Thin-wrapper around the mock package for easier use with pytest" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"}, + {file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"}, +] + +[package.dependencies] +pytest = ">=6.2.5" + +[package.extras] +dev = ["pre-commit", "pytest-asyncio", "tox"] + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -3166,4 +3184,4 @@ test = ["pytest (>=8.1,<9.0)", "pytest-rerunfailures (>=14.0,<15.0)"] [metadata] lock-version = "2.1" python-versions = ">=3.10,<3.13" -content-hash = "b3a6142d6495bc4c8741e9411d29352af219851e4b84b263f991e1bb6db1614e" +content-hash = "2d0a953383901fe12e97f6f56a76a9d8008788695425792eedbf739a18585188" diff --git a/pyproject.toml b/pyproject.toml index 9823833..29de7e4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,6 +63,7 @@ dependencies = [ pytest = "^8.3.4" autopep8 = "^2.3.1" pytest-loguru = "^0.4.0" +pytest-mock = "^3.14.0" [tool.poetry.group.docs.dependencies] sphinx = "^8.1.3" diff --git a/tests/conftest.py b/tests/conftest.py index 8deebff..a32ca48 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,7 +7,6 @@ from datetime import datetime, timezone from tempfile import TemporaryDirectory from typing import Dict, Tuple import hashlib -from unittest.mock import patch import pytest from auto_archiver.core.metadata import Metadata @@ -134,14 +133,29 @@ def unpickle(): @pytest.fixture -def mock_binary_dependencies(): - with patch("shutil.which") as mock_shutil_which: - # Mock all binary dependencies as available - mock_shutil_which.return_value = "/usr/bin/fake_binary" - yield mock_shutil_which +def mock_binary_dependencies(mocker): + mock_shutil_which = mocker.patch("shutil.which") + # Mock all binary dependencies as available + mock_shutil_which.return_value = "/usr/bin/fake_binary" + return mock_shutil_which @pytest.fixture def sample_datetime(): return datetime(2023, 1, 1, 12, 0, tzinfo=timezone.utc) + +@pytest.fixture(autouse=True) +def mock_sleep(mocker): + """Globally mock time.sleep to avoid delays.""" + return mocker.patch("time.sleep") + + +@pytest.fixture +def metadata(): + metadata = Metadata() + metadata.set("_processed_at", "2021-01-01T00:00:00") + metadata.set_title("Example Title") + metadata.set_content("Example Content") + metadata.set_url("https://example.com") + return metadata \ No newline at end of file diff --git a/tests/databases/test_api_db.py b/tests/databases/test_api_db.py index d07cb1a..6d7a2bc 100644 --- a/tests/databases/test_api_db.py +++ b/tests/databases/test_api_db.py @@ -1,5 +1,3 @@ -from unittest.mock import patch - import pytest from auto_archiver.core import Metadata @@ -35,35 +33,35 @@ def test_fetch_no_cache(api_db, metadata): assert api_db.fetch(metadata) is None -def test_fetch_fail_status(api_db, metadata): +def test_fetch_fail_status(api_db, metadata, mocker): # Test response fail in fetch method - with patch("auto_archiver.modules.api_db.api_db.requests.get") as mock_get: - mock_get.return_value.status_code = 400 - mock_get.return_value.json.return_value = {} - with patch("loguru.logger.error") as mock_error: - assert api_db.fetch(metadata) is False - mock_error.assert_called_once_with("AA API FAIL (400): {}") + mock_get = mocker.patch("auto_archiver.modules.api_db.api_db.requests.get") + mock_get.return_value.status_code = 400 + mock_get.return_value.json.return_value = {} + mock_error = mocker.patch("loguru.logger.error") + assert api_db.fetch(metadata) is False + mock_error.assert_called_once_with("AA API FAIL (400): {}") -def test_fetch(api_db, metadata): +def test_fetch(api_db, metadata, mocker): # Test successful fetch method - with patch("auto_archiver.modules.api_db.api_db.requests.get") as mock_get,\ - patch("auto_archiver.core.metadata.datetime.datetime") as mock_datetime: - mock_datetime.now.return_value = "2021-01-01T00:00:00" - mock_get.return_value.status_code = 200 - mock_get.return_value.json.return_value = [{"result": {}}, {"result": - {'media': [], 'metadata': {'_processed_at': '2021-01-01T00:00:00', 'url': 'https://example.com'}, - 'status': 'no archiver'}}] - assert api_db.fetch(metadata) == metadata + mock_get = mocker.patch("auto_archiver.modules.api_db.api_db.requests.get") + mock_datetime = mocker.patch("auto_archiver.core.metadata.datetime.datetime") + mock_datetime.now.return_value = "2021-01-01T00:00:00" + mock_get.return_value.status_code = 200 + mock_get.return_value.json.return_value = [{"result": {}}, {"result": + {'media': [], 'metadata': {'_processed_at': '2021-01-01T00:00:00', 'url': 'https://example.com'}, + 'status': 'no archiver'}}] + assert api_db.fetch(metadata) == metadata -def test_done_success(api_db, metadata): - with patch("auto_archiver.modules.api_db.api_db.requests.post") as mock_post: - mock_post.return_value.status_code = 201 - api_db.done(metadata) - mock_post.assert_called_once() - mock_post.assert_called_once_with("https://api.example.com/interop/submit-archive", - json={'author_id': 'Someone', 'url': 'https://example.com', - 'public': False, 'group_id': '123', 'tags': ['[', ']'], 'result': '{"status": "no archiver", "metadata": {"_processed_at": "2021-01-01T00:00:00", "url": "https://example.com"}, "media": []}'}, - headers={'Authorization': 'Bearer test-token'}) +def test_done_success(api_db, metadata, mocker): + mock_post = mocker.patch("auto_archiver.modules.api_db.api_db.requests.post") + mock_post.return_value.status_code = 201 + api_db.done(metadata) + mock_post.assert_called_once() + mock_post.assert_called_once_with("https://api.example.com/interop/submit-archive", + json={'author_id': 'Someone', 'url': 'https://example.com', + 'public': False, 'group_id': '123', 'tags': ['[', ']'], 'result': '{"status": "no archiver", "metadata": {"_processed_at": "2021-01-01T00:00:00", "url": "https://example.com"}, "media": []}'}, + headers={'Authorization': 'Bearer test-token'}) diff --git a/tests/databases/test_gsheet_db.py b/tests/databases/test_gsheet_db.py index 18a22f1..42a21b2 100644 --- a/tests/databases/test_gsheet_db.py +++ b/tests/databases/test_gsheet_db.py @@ -1,6 +1,4 @@ from datetime import datetime, timezone -from unittest.mock import MagicMock, patch - import pytest from auto_archiver.core import Metadata, Media @@ -9,8 +7,8 @@ from auto_archiver.modules.gsheet_feeder import GWorksheet @pytest.fixture -def mock_gworksheet(): - mock_gworksheet = MagicMock(spec=GWorksheet) +def mock_gworksheet(mocker): + mock_gworksheet = mocker.MagicMock(spec=GWorksheet) mock_gworksheet.col_exists.return_value = True mock_gworksheet.get_cell.return_value = "" mock_gworksheet.get_row.return_value = {} @@ -18,14 +16,14 @@ def mock_gworksheet(): @pytest.fixture -def mock_metadata(): - metadata: Metadata = MagicMock(spec=Metadata) +def mock_metadata(mocker): + metadata: Metadata = mocker.MagicMock(spec=Metadata) metadata.get_url.return_value = "http://example.com" metadata.status = "done" metadata.get_title.return_value = "Example Title" metadata.get.return_value = "Example Content" metadata.get_timestamp.return_value = "2025-01-01T00:00:00" - metadata.get_final_media.return_value = MagicMock(spec=Media) + metadata.get_final_media.return_value = mocker.MagicMock(spec=Media) metadata.get_all_media.return_value = [] metadata.get_media_by_id.return_value = None metadata.get_first_image.return_value = None @@ -47,21 +45,21 @@ def metadata(): @pytest.fixture -def mock_media(): +def mock_media(mocker): """Fixture for a mock Media object.""" - mock_media = MagicMock(spec=Media) + mock_media = mocker.MagicMock(spec=Media) mock_media.urls = ["http://example.com/media"] mock_media.get.return_value = "not-calculated" return mock_media @pytest.fixture -def gsheets_db(mock_gworksheet, setup_module): +def gsheets_db(mock_gworksheet, setup_module, mocker): db = setup_module("gsheet_db", { "allow_worksheets": "set()", "block_worksheets": "set()", "use_sheet_names_in_stored_paths": "True", }) - db._retrieve_gsheet = MagicMock(return_value=(mock_gworksheet, 1)) + db._retrieve_gsheet = mocker.MagicMock(return_value=(mock_gworksheet, 1)) return db @@ -109,27 +107,26 @@ def test_aborted(gsheets_db, mock_metadata, mock_gworksheet): mock_gworksheet.set_cell.assert_called_once_with(1, 'status', '') -def test_done(gsheets_db, metadata, mock_gworksheet, expected_calls): - with patch("auto_archiver.modules.gsheet_db.gsheet_db.get_current_timestamp", return_value='2025-02-01T00:00:00+00:00'): - gsheets_db.done(metadata) +def test_done(gsheets_db, metadata, mock_gworksheet, expected_calls, mocker): + mocker.patch("auto_archiver.modules.gsheet_db.gsheet_db.get_current_timestamp", return_value='2025-02-01T00:00:00+00:00') + gsheets_db.done(metadata) mock_gworksheet.batch_set_cell.assert_called_once_with(expected_calls) -def test_done_cached(gsheets_db, metadata, mock_gworksheet): - with patch("auto_archiver.modules.gsheet_db.gsheet_db.get_current_timestamp", return_value='2025-02-01T00:00:00+00:00'): - gsheets_db.done(metadata, cached=True) +def test_done_cached(gsheets_db, metadata, mock_gworksheet, mocker): + mocker.patch("auto_archiver.modules.gsheet_db.gsheet_db.get_current_timestamp", return_value='2025-02-01T00:00:00+00:00') + gsheets_db.done(metadata, cached=True) # Verify the status message includes "[cached]" call_args = mock_gworksheet.batch_set_cell.call_args[0][0] assert any(call[2].startswith("[cached]") for call in call_args) -def test_done_missing_media(gsheets_db, metadata, mock_gworksheet): +def test_done_missing_media(gsheets_db, metadata, mock_gworksheet, mocker): # clear media from metadata metadata.media = [] - with patch("auto_archiver.modules.gsheet_db.gsheet_db.get_current_timestamp", - return_value='2025-02-01T00:00:00+00:00'): - gsheets_db.done(metadata) + mocker.patch("auto_archiver.modules.gsheet_db.gsheet_db.get_current_timestamp", return_value='2025-02-01T00:00:00+00:00') + gsheets_db.done(metadata) # Verify nothing media-related gets updated call_args = mock_gworksheet.batch_set_cell.call_args[0][0] media_fields = {'archive', 'screenshot', 'thumbnail', 'wacz', 'replaywebpage'} diff --git a/tests/enrichers/test_meta_enricher.py b/tests/enrichers/test_meta_enricher.py index a09aaa9..cc283c0 100644 --- a/tests/enrichers/test_meta_enricher.py +++ b/tests/enrichers/test_meta_enricher.py @@ -1,6 +1,5 @@ import datetime from datetime import datetime, timedelta, timezone -from unittest.mock import MagicMock, patch import pytest @@ -9,18 +8,18 @@ from auto_archiver.modules.meta_enricher import MetaEnricher @pytest.fixture -def mock_metadata(): +def mock_metadata(mocker): """Creates a mock Metadata object.""" - mock: Metadata = MagicMock(spec=Metadata) + mock: Metadata = mocker.MagicMock(spec=Metadata) mock.get_url.return_value = "https://example.com" mock.is_empty.return_value = False # Default to not empty mock.get_all_media.return_value = [] return mock @pytest.fixture -def mock_media(): +def mock_media(mocker): """Creates a mock Media object.""" - mock: Media = MagicMock(spec=Media) + mock: Media = mocker.MagicMock(spec=Media) mock.filename = "mock_file.txt" return mock @@ -90,14 +89,14 @@ def test_enrich_file_sizes_no_media(meta_enricher, metadata): assert metadata.get("total_size") == "0.0 bytes" -def test_enrich_archive_duration(meta_enricher, metadata): +def test_enrich_archive_duration(meta_enricher, metadata, mocker): # Set fixed "processed at" time in the past processed_at = datetime.now(timezone.utc) - timedelta(minutes=10, seconds=30) metadata.set("_processed_at", processed_at) # patch datetime - with patch("datetime.datetime") as mock_datetime: - mock_now = datetime.now(timezone.utc) - mock_datetime.now.return_value = mock_now - meta_enricher.enrich_archive_duration(metadata) + mock_datetime = mocker.patch("datetime.datetime") + mock_now = datetime.now(timezone.utc) + mock_datetime.now.return_value = mock_now + meta_enricher.enrich_archive_duration(metadata) assert metadata.get("archive_duration_seconds") == 630 \ No newline at end of file diff --git a/tests/enrichers/test_metadata_enricher.py b/tests/enrichers/test_metadata_enricher.py index aedcf54..888837d 100644 --- a/tests/enrichers/test_metadata_enricher.py +++ b/tests/enrichers/test_metadata_enricher.py @@ -1,14 +1,13 @@ -from unittest.mock import MagicMock, patch, Mock import pytest -from auto_archiver.core import Metadata, Media +from auto_archiver.core import Media @pytest.fixture -def mock_media(): +def mock_media(mocker): """Creates a mock Media object.""" - mock: Media = MagicMock(spec=Media) + mock: Media = mocker.MagicMock(spec=Media) mock.filename = "mock_file.txt" return mock @@ -26,8 +25,8 @@ def enricher(setup_module, mock_binary_dependencies): ("", {}), ], ) -@patch("subprocess.run") -def test_get_metadata(mock_run, enricher, output, expected): +def test_get_metadata(enricher, output, expected, mocker): + mock_run = mocker.patch("subprocess.run") mock_run.return_value.stdout = output mock_run.return_value.stderr = "" mock_run.return_value.returncode = 0 @@ -39,17 +38,17 @@ def test_get_metadata(mock_run, enricher, output, expected): ) -@patch("subprocess.run") -def test_get_metadata_exiftool_not_found(mock_run, enricher): +def test_get_metadata_exiftool_not_found(enricher, mocker): + mock_run = mocker.patch("subprocess.run") mock_run.side_effect = FileNotFoundError result = enricher.get_metadata("test.jpg") assert result == {} -def test_enrich_sets_metadata(enricher): - media1 = Mock(filename="img1.jpg") - media2 = Mock(filename="img2.jpg") - metadata = Mock() +def test_enrich_sets_metadata(enricher, mocker): + media1 = mocker.Mock(filename="img1.jpg") + media2 = mocker.Mock(filename="img2.jpg") + metadata = mocker.Mock() metadata.media = [media1, media2] enricher.get_metadata = lambda f: {"key": "value"} if f == "img1.jpg" else {} @@ -60,24 +59,23 @@ def test_enrich_sets_metadata(enricher): assert metadata.media == [media1, media2] -def test_enrich_empty_media(enricher): - metadata = Mock() +def test_enrich_empty_media(enricher, mocker): + metadata = mocker.Mock() metadata.media = [] # Should not raise errors enricher.enrich(metadata) -@patch("loguru.logger.error") -@patch("subprocess.run") -def test_get_metadata_error_handling(mock_run, mock_logger_error, enricher): - mock_run.side_effect = Exception("Test error") +def test_get_metadata_error_handling(enricher, mocker): + mocker.patch("subprocess.run", side_effect=Exception("Test error")) + mock_log = mocker.patch("loguru.logger.error") result = enricher.get_metadata("test.jpg") assert result == {} - mock_logger_error.assert_called_once() + assert "Error occurred: " in mock_log.call_args[0][0] -@patch("subprocess.run") -def test_metadata_pickle(mock_run, enricher, unpickle): +def test_metadata_pickle(enricher, unpickle, mocker): + mock_run = mocker.patch("subprocess.run") # Uses pickled values mock_run.return_value = unpickle("metadata_enricher_exif.pickle") metadata = unpickle("metadata_enricher_ytshort_input.pickle") @@ -86,4 +84,5 @@ def test_metadata_pickle(mock_run, enricher, unpickle): expected_media = expected.media actual_media = metadata.media assert len(expected_media) == len(actual_media) - assert actual_media[0].properties.get("metadata") == expected_media[0].properties.get("metadata") \ No newline at end of file + assert actual_media[0].properties.get("metadata") == expected_media[0].properties.get("metadata") + diff --git a/tests/enrichers/test_pdq_hash_enricher.py b/tests/enrichers/test_pdq_hash_enricher.py index e90cd22..9653734 100644 --- a/tests/enrichers/test_pdq_hash_enricher.py +++ b/tests/enrichers/test_pdq_hash_enricher.py @@ -1,5 +1,3 @@ -from unittest.mock import patch - import pytest from PIL import UnidentifiedImageError @@ -21,11 +19,11 @@ def metadata_with_images(): return m -def test_successful_enrich(metadata_with_images): +def test_successful_enrich(metadata_with_images, mocker): with ( - patch("pdqhash.compute", return_value=([1, 0, 1, 0] * 64, 100)), - patch("PIL.Image.open"), - patch.object(Media, "is_image", return_value=True) as mock_is_image, + mocker.patch("pdqhash.compute", return_value=([1, 0, 1, 0] * 64, 100)), + mocker.patch("PIL.Image.open"), + mocker.patch.object(Media, "is_image", return_value=True) as mock_is_image, ): enricher = PdqHashEnricher() enricher.enrich(metadata_with_images) @@ -35,27 +33,24 @@ def test_successful_enrich(metadata_with_images): assert media.get("pdq_hash") is not None -def test_enrich_skip_non_image(metadata_with_images): - with ( - patch.object(Media, "is_image", return_value=False), - patch("pdqhash.compute") as mock_pdq, - ): - enricher = PdqHashEnricher() - enricher.enrich(metadata_with_images) - mock_pdq.assert_not_called() +def test_enrich_skip_non_image(metadata_with_images, mocker): + mocker.patch.object(Media, "is_image", return_value=False) + mock_pdq = mocker.patch("pdqhash.compute") + + enricher = PdqHashEnricher() + enricher.enrich(metadata_with_images) + mock_pdq.assert_not_called() -def test_enrich_handles_corrupted_image(metadata_with_images): - with ( - patch("PIL.Image.open", side_effect=UnidentifiedImageError("Corrupted image")), - patch("pdqhash.compute") as mock_pdq, - patch("loguru.logger.error") as mock_logger, - ): - enricher = PdqHashEnricher() - enricher.enrich(metadata_with_images) +def test_enrich_handles_corrupted_image(metadata_with_images, mocker): + mocker.patch("PIL.Image.open", side_effect=UnidentifiedImageError("Corrupted image")) + mock_pdq = mocker.patch("pdqhash.compute") + mock_logger = mocker.patch("loguru.logger.error") + enricher = PdqHashEnricher() + enricher.enrich(metadata_with_images) - assert mock_logger.call_count == len(metadata_with_images.media) - mock_pdq.assert_not_called() + assert mock_logger.call_count == len(metadata_with_images.media) + mock_pdq.assert_not_called() @pytest.mark.parametrize( @@ -66,19 +61,18 @@ def test_enrich_handles_corrupted_image(metadata_with_images): ("regular-image", True), ] ) -def test_enrich_excludes_by_filetype(media_id, should_have_hash): +def test_enrich_excludes_by_filetype(media_id, should_have_hash, mocker): metadata = Metadata() metadata.set_url("https://example.com") metadata.add_media(Media(filename="image.jpg").set("id", media_id)) - with ( - patch("pdqhash.compute", return_value=([1, 0, 1, 0] * 64, 100)), - patch("PIL.Image.open"), - patch.object(Media, "is_image", return_value=True), - ): - enricher = PdqHashEnricher() - enricher.enrich(metadata) + mocker.patch("pdqhash.compute", return_value=([1, 0, 1, 0] * 64, 100)) + mocker.patch("PIL.Image.open") + mocker.patch.object(Media, "is_image", return_value=True) - media_item = metadata.media[0] - assert (media_item.get("pdq_hash") is not None) == should_have_hash + enricher = PdqHashEnricher() + enricher.enrich(metadata) + + media_item = metadata.media[0] + assert (media_item.get("pdq_hash") is not None) == should_have_hash diff --git a/tests/enrichers/test_screenshot_enricher.py b/tests/enrichers/test_screenshot_enricher.py index 3998deb..25ca51d 100644 --- a/tests/enrichers/test_screenshot_enricher.py +++ b/tests/enrichers/test_screenshot_enricher.py @@ -1,5 +1,4 @@ import base64 -from unittest.mock import patch, MagicMock import pytest from selenium.common.exceptions import TimeoutException @@ -9,53 +8,47 @@ from auto_archiver.modules.screenshot_enricher import ScreenshotEnricher @pytest.fixture -def mock_selenium_env(): - # Patches Selenium calls and driver checks in one place. - with ( - patch("shutil.which") as mock_which, - patch("auto_archiver.utils.webdriver.CookieSettingDriver") as mock_driver_class, - patch( - "selenium.webdriver.common.selenium_manager.SeleniumManager.binary_paths" - ) as mock_binary_paths, - patch("pathlib.Path.is_file", return_value=True), - patch("subprocess.Popen") as mock_popen, - patch( - "selenium.webdriver.common.service.Service.is_connectable", - return_value=True, - ), - patch("selenium.webdriver.FirefoxOptions") as mock_firefox_options, - ): - # Mock driver existence - def mock_which_side_effect(dep): - return "/mock/geckodriver" if dep == "geckodriver" else None +def mock_selenium_env(mocker): + """Patches Selenium calls and driver checks in one place.""" - mock_which.side_effect = mock_which_side_effect - # Mock binary paths - mock_binary_paths.return_value = { - "driver_path": "/mock/driver", - "browser_path": "/mock/browser", - } - # Popen - mock_proc = MagicMock() - mock_proc.poll.return_value = None - mock_popen.return_value = mock_proc - # CookieSettingDriver -> returns a mock driver - mock_driver = MagicMock() - mock_driver_class.return_value = mock_driver - # FirefoxOptions - mock_options_instance = MagicMock() - mock_firefox_options.return_value = mock_options_instance - yield mock_driver, mock_driver_class, mock_options_instance + # Patch external dependencies + mock_which = mocker.patch("shutil.which") + mock_driver_class = mocker.patch("auto_archiver.utils.webdriver.CookieSettingDriver") + mock_binary_paths = mocker.patch("selenium.webdriver.common.selenium_manager.SeleniumManager.binary_paths") + mock_is_file = mocker.patch("pathlib.Path.is_file", return_value=True) + mock_popen = mocker.patch("subprocess.Popen") + mock_is_connectable = mocker.patch("selenium.webdriver.common.service.Service.is_connectable", return_value=True) + mock_firefox_options = mocker.patch("selenium.webdriver.FirefoxOptions") + # Define side effect for `shutil.which` + def mock_which_side_effect(dep): + return "/mock/geckodriver" if dep == "geckodriver" else None + mock_which.side_effect = mock_which_side_effect + + # Mock binary paths + mock_binary_paths.return_value = { + "driver_path": "/mock/driver", + "browser_path": "/mock/browser", + } + # Mock `subprocess.Popen` + mock_proc = mocker.MagicMock() + mock_proc.poll.return_value = None + mock_popen.return_value = mock_proc + # Mock `CookieSettingDriver` + mock_driver = mocker.MagicMock() + mock_driver_class.return_value = mock_driver + # Mock `FirefoxOptions` + mock_options_instance = mocker.MagicMock() + mock_firefox_options.return_value = mock_options_instance + yield mock_driver, mock_driver_class, mock_options_instance @pytest.fixture -def common_patches(tmp_path): - with ( - patch("auto_archiver.utils.url.is_auth_wall", return_value=False), - patch("os.path.join", return_value=str(tmp_path / "test.png")), - patch("time.sleep"), - ): - yield +def common_patches(tmp_path, mocker): + """Patches common utilities used across multiple tests.""" + mocker.patch("auto_archiver.utils.url.is_auth_wall", return_value=False) + mocker.patch("os.path.join", return_value=str(tmp_path / "test.png")) + mocker.patch("time.sleep") + yield @pytest.fixture @@ -117,37 +110,38 @@ def test_enrich_auth_wall( common_patches, url, is_auth, + mocker ): # Testing with and without is_auth_wall mock_driver, mock_driver_class, _ = mock_selenium_env - with patch("auto_archiver.utils.url.is_auth_wall", return_value=is_auth): - metadata_with_video.set_url(url) - screenshot_enricher.enrich(metadata_with_video) + mocker.patch("auto_archiver.utils.url.is_auth_wall", return_value=is_auth) + metadata_with_video.set_url(url) + screenshot_enricher.enrich(metadata_with_video) - if is_auth: - mock_driver.get.assert_not_called() - assert len(metadata_with_video.media) == 1 - assert metadata_with_video.media[0].properties.get("id") == "video1" - else: - mock_driver.get.assert_called_once_with(url) - assert len(metadata_with_video.media) == 2 - assert metadata_with_video.media[1].properties.get("id") == "screenshot" + if is_auth: + mock_driver.get.assert_not_called() + assert len(metadata_with_video.media) == 1 + assert metadata_with_video.media[0].properties.get("id") == "video1" + else: + mock_driver.get.assert_called_once_with(url) + assert len(metadata_with_video.media) == 2 + assert metadata_with_video.media[1].properties.get("id") == "screenshot" def test_handle_timeout_exception( - screenshot_enricher, metadata_with_video, mock_selenium_env + screenshot_enricher, metadata_with_video, mock_selenium_env, mocker ): mock_driver, mock_driver_class, mock_options_instance = mock_selenium_env mock_driver.get.side_effect = TimeoutException - with patch("loguru.logger.info") as mock_log: - screenshot_enricher.enrich(metadata_with_video) - mock_log.assert_called_once_with("TimeoutException loading page for screenshot") - assert len(metadata_with_video.media) == 1 + mock_log = mocker.patch("loguru.logger.info") + screenshot_enricher.enrich(metadata_with_video) + mock_log.assert_called_once_with("TimeoutException loading page for screenshot") + assert len(metadata_with_video.media) == 1 def test_handle_general_exception( - screenshot_enricher, metadata_with_video, mock_selenium_env + screenshot_enricher, metadata_with_video, mock_selenium_env, mocker ): """Test proper handling of unexpected general exceptions""" mock_driver, mock_driver_class, mock_options_instance = mock_selenium_env @@ -155,47 +149,43 @@ def test_handle_general_exception( mock_driver.get.return_value = None mock_driver.save_screenshot.side_effect = Exception("Unexpected Error") - with patch("loguru.logger.error") as mock_log: - screenshot_enricher.enrich(metadata_with_video) - # Verify that the exception was logged with the log - mock_log.assert_called_once_with( - "Got error while loading webdriver for screenshot enricher: Unexpected Error" - ) - # And no new media was added due to the error - assert len(metadata_with_video.media) == 1 + mock_log = mocker.patch("loguru.logger.error") + screenshot_enricher.enrich(metadata_with_video) + # Verify that the exception was logged with the log + mock_log.assert_called_once_with( + "Got error while loading webdriver for screenshot enricher: Unexpected Error" + ) + # And no new media was added due to the error + assert len(metadata_with_video.media) == 1 -def test_pdf_creation(screenshot_enricher, metadata_with_video, mock_selenium_env): +def test_pdf_creation(mocker, screenshot_enricher, metadata_with_video, mock_selenium_env): """Test PDF creation when save_to_pdf is enabled""" mock_driver, mock_driver_class, mock_options_instance = mock_selenium_env - # Override the save_to_pdf option screenshot_enricher.save_to_pdf = True # Mock the print_page method to return base64-encoded content - mock_driver.print_page.return_value = base64.b64encode(b"fake_pdf_content").decode( - "utf-8" + mock_driver.print_page.return_value = base64.b64encode(b"fake_pdf_content").decode("utf-8") + # Patch functions with mocker + mock_os_path_join = mocker.patch("os.path.join", side_effect=lambda *args: f"{args[-1]}") + mock_random_str = mocker.patch( + "auto_archiver.modules.screenshot_enricher.screenshot_enricher.random_str", + return_value="fixed123", ) - with ( - patch("os.path.join", side_effect=lambda *args: f"{args[-1]}"), - patch( - "auto_archiver.modules.screenshot_enricher.screenshot_enricher.random_str", - return_value="fixed123", - ), - patch("builtins.open", new_callable=MagicMock()) as mock_open, - patch("loguru.logger.error") as mock_log, - ): - screenshot_enricher.enrich(metadata_with_video) + mock_open = mocker.patch("builtins.open", new_callable=mocker.mock_open) + mock_log_error = mocker.patch("loguru.logger.error") - # Verify screenshot and PDF creation - mock_driver.save_screenshot.assert_called_once() - mock_driver.print_page.assert_called_once_with(mock_driver.print_options) + screenshot_enricher.enrich(metadata_with_video) + # Verify screenshot and PDF creation + mock_driver.save_screenshot.assert_called_once() + mock_driver.print_page.assert_called_once_with(mock_driver.print_options) + # Check that PDF file was opened and written + mock_open.assert_any_call("pdf_fixed123.pdf", "wb") - # Check that PDF file was opened and written - mock_open.assert_any_call("pdf_fixed123.pdf", "wb") - # Ensure both screenshot and PDF were added as media - assert len(metadata_with_video.media) == 3 # Original video + screenshot + PDF - assert metadata_with_video.media[1].properties.get("id") == "screenshot" - assert metadata_with_video.media[2].properties.get("id") == "pdf" + # Ensure both screenshot and PDF were added as media + assert len(metadata_with_video.media) == 3 + assert metadata_with_video.media[1].properties.get("id") == "screenshot" + assert metadata_with_video.media[2].properties.get("id") == "pdf" @pytest.fixture(autouse=True) diff --git a/tests/enrichers/test_ssl_enricher.py b/tests/enrichers/test_ssl_enricher.py index 29775f2..eb7ba6b 100644 --- a/tests/enrichers/test_ssl_enricher.py +++ b/tests/enrichers/test_ssl_enricher.py @@ -1,6 +1,4 @@ import ssl -from unittest.mock import patch, mock_open - import pytest from auto_archiver.core import Metadata, Media @@ -35,22 +33,22 @@ def test_empty_metadata(metadata, enricher): assert enricher.enrich(metadata) is None -def test_ssl_enrich(metadata, enricher): - with patch("ssl.get_server_certificate", return_value="TEST_CERT"), \ - patch("builtins.open", mock_open()) as mock_file: - media_len_before = len(metadata.media) +def test_ssl_enrich(metadata, enricher, mocker): + mocker.patch("ssl.get_server_certificate", return_value="TEST_CERT") + mock_file = mocker.patch("builtins.open", mocker.mock_open()) + media_len_before = len(metadata.media) + enricher.enrich(metadata) + + ssl.get_server_certificate.assert_called_once_with(("example.com", 443)) + mock_file.assert_called_once_with(f"{enricher.tmp_dir}/example-com.pem", "w") + mock_file().write.assert_called_once_with("TEST_CERT") + assert len(metadata.media) == media_len_before + 1 + # Ensure the certificate is added to metadata + assert any(media.filename.endswith("example-com.pem") for media in metadata.media) + + +def test_ssl_error_handling(enricher, metadata, mocker): + mocker.patch("ssl.get_server_certificate", side_effect=ssl.SSLError("SSL error")) + with pytest.raises(ssl.SSLError, match="SSL error"): enricher.enrich(metadata) - ssl.get_server_certificate.assert_called_once_with(("example.com", 443)) - mock_file.assert_called_once_with(f"{enricher.tmp_dir}/example-com.pem", "w") - mock_file().write.assert_called_once_with("TEST_CERT") - assert len(metadata.media) == media_len_before + 1 - # Ensure the certificate is added to metadata - assert any(media.filename.endswith("example-com.pem") for media in metadata.media) - - -def test_ssl_error_handling(enricher, metadata): - with patch("ssl.get_server_certificate", side_effect=ssl.SSLError("SSL error")): - with pytest.raises(ssl.SSLError, match="SSL error"): - enricher.enrich(metadata) - diff --git a/tests/enrichers/test_thumbnail_enricher.py b/tests/enrichers/test_thumbnail_enricher.py index 14cfa0e..effc25e 100644 --- a/tests/enrichers/test_thumbnail_enricher.py +++ b/tests/enrichers/test_thumbnail_enricher.py @@ -1,5 +1,4 @@ import pytest -from unittest.mock import patch, MagicMock from auto_archiver.core import Metadata, Media from auto_archiver.modules.thumbnail_enricher import ThumbnailEnricher @@ -22,32 +21,30 @@ def metadata_with_video(): @pytest.fixture -def mock_ffmpeg_environment(): +def mock_ffmpeg_environment(mocker): # Mocking all the ffmpeg calls in one place - with ( - patch("ffmpeg.input") as mock_ffmpeg_input, - patch("os.makedirs") as mock_makedirs, - patch.object(Media, "is_video", return_value=True), - patch( - "ffmpeg.probe", - return_value={ - "streams": [ - {"codec_type": "video", "duration": "120"} - ] # Default 2-minute duration, but can override in tests - }, - ) as mock_probe, - ): - mock_output = MagicMock() - mock_ffmpeg_input.return_value.filter.return_value.output.return_value = ( - mock_output - ) + mock_ffmpeg_input = mocker.patch("ffmpeg.input") + mock_makedirs = mocker.patch("os.makedirs") + mocker.patch.object(Media, "is_video", return_value=True), + mock_probe = mocker.patch( + "ffmpeg.probe", + return_value={ + "streams": [ + {"codec_type": "video", "duration": "120"} + ] # Default 2-minute duration, but can override in tests + }, + ) + mock_output = mocker.MagicMock() + mock_ffmpeg_input.return_value.filter.return_value.output.return_value = ( + mock_output + ) - yield { - "mock_ffmpeg_input": mock_ffmpeg_input, - "mock_makedirs": mock_makedirs, - "mock_output": mock_output, - "mock_probe": mock_probe, - } + return { + "mock_ffmpeg_input": mock_ffmpeg_input, + "mock_makedirs": mock_makedirs, + "mock_output": mock_output, + "mock_probe": mock_probe, + } @pytest.mark.parametrize("thumbnails_per_minute, max_thumbnails, expected_count", [ @@ -68,28 +65,26 @@ def test_enrich_thumbnail_limits( thumbnails = metadata_with_video.media[0].get("thumbnails") assert len(thumbnails) == expected_count -def test_enrich_handles_probe_failure(thumbnail_enricher, metadata_with_video): - with ( - patch("ffmpeg.probe", side_effect=Exception("Probe error")), - patch("os.makedirs"), - patch("loguru.logger.error") as mock_logger, - patch.object(Media, "is_video", return_value=True), - ): - thumbnail_enricher.enrich(metadata_with_video) - # Ensure error was logged - mock_logger.assert_called_with( - f"error getting duration of video video.mp4: Probe error" - ) - # Ensure no thumbnails were created - thumbnails = metadata_with_video.media[0].get("thumbnails") - assert thumbnails is None +def test_enrich_handles_probe_failure(thumbnail_enricher, metadata_with_video, mocker): + + mocker.patch("ffmpeg.probe", side_effect=Exception("Probe error")) + mocker.patch("os.makedirs") + mock_logger = mocker.patch("loguru.logger.error") + mocker.patch.object(Media, "is_video", return_value=True) + + thumbnail_enricher.enrich(metadata_with_video) + # Ensure error was logged + mock_logger.assert_called_with( + f"error getting duration of video video.mp4: Probe error" + ) + # Ensure no thumbnails were created + thumbnails = metadata_with_video.media[0].get("thumbnails") + assert thumbnails is None -def test_enrich_skips_non_video_files(thumbnail_enricher, metadata_with_video): - with ( - patch.object(Media, "is_video", return_value=False), - patch("ffmpeg.input") as mock_ffmpeg, - ): +def test_enrich_skips_non_video_files(thumbnail_enricher, metadata_with_video, mocker): + mocker.patch.object(Media, "is_video", return_value=False) + mock_ffmpeg = mocker.patch("ffmpeg.input") thumbnail_enricher.enrich(metadata_with_video) mock_ffmpeg.assert_not_called() @@ -102,21 +97,21 @@ def test_enrich_skips_non_video_files(thumbnail_enricher, metadata_with_video): (12, 20, 2), # test caught by t/min ]) def test_enrich_handles_short_video( - thumbnail_enricher, metadata_with_video, mock_ffmpeg_environment, thumbnails_per_minute, max_thumbnails, expected_count + thumbnail_enricher, metadata_with_video, mock_ffmpeg_environment, thumbnails_per_minute, max_thumbnails, expected_count, mocker ): # override mock duration fake_duration = 10 - with patch( + mocker.patch( "ffmpeg.probe", return_value={ "streams": [{"codec_type": "video", "duration": str(fake_duration)}]}, - ): - thumbnail_enricher.thumbnails_per_minute = thumbnails_per_minute - thumbnail_enricher.max_thumbnails = max_thumbnails + ) + thumbnail_enricher.thumbnails_per_minute = thumbnails_per_minute + thumbnail_enricher.max_thumbnails = max_thumbnails - thumbnail_enricher.enrich(metadata_with_video) - assert mock_ffmpeg_environment["mock_output"].run.call_count == expected_count - thumbnails = metadata_with_video.media[0].get("thumbnails") - assert len(thumbnails) == expected_count + thumbnail_enricher.enrich(metadata_with_video) + assert mock_ffmpeg_environment["mock_output"].run.call_count == expected_count + thumbnails = metadata_with_video.media[0].get("thumbnails") + assert len(thumbnails) == expected_count def test_uses_existing_duration( @@ -128,28 +123,26 @@ def test_uses_existing_duration( assert mock_ffmpeg_environment["mock_output"].run.call_count == 4 -def test_enrich_metadata_structure(thumbnail_enricher, metadata_with_video, mock_ffmpeg_environment): +def test_enrich_metadata_structure(thumbnail_enricher, metadata_with_video, mock_ffmpeg_environment, mocker): fake_duration = 120 - with patch("ffmpeg.probe", return_value={ - 'streams': [{'codec_type': 'video', 'duration': str(fake_duration)}] - }): - thumbnail_enricher.thumbnails_per_minute = 2 - thumbnail_enricher.max_thumbnails = 4 + mocker.patch("ffmpeg.probe", return_value={'streams': [{'codec_type': 'video', 'duration': str(fake_duration)}]}) + thumbnail_enricher.thumbnails_per_minute = 2 + thumbnail_enricher.max_thumbnails = 4 - thumbnail_enricher.enrich(metadata_with_video) + thumbnail_enricher.enrich(metadata_with_video) - media_item = metadata_with_video.media[0] - thumbnails = media_item.get("thumbnails") + media_item = metadata_with_video.media[0] + thumbnails = media_item.get("thumbnails") - # Assert normal metadata - assert media_item.get("id") == "video1" - assert media_item.get("duration") == fake_duration - # Evenly spaced timestamps - expected_timestamps = ["24.000s", "48.000s", "72.000s", "96.000s"] - assert thumbnails is not None - assert len(thumbnails) == 4 + # Assert normal metadata + assert media_item.get("id") == "video1" + assert media_item.get("duration") == fake_duration + # Evenly spaced timestamps + expected_timestamps = ["24.000s", "48.000s", "72.000s", "96.000s"] + assert thumbnails is not None + assert len(thumbnails) == 4 - for index, thumbnail in enumerate(thumbnails): - assert thumbnail.filename is not None - assert thumbnail.properties.get("id") == f"thumbnail_{index}" - assert thumbnail.properties.get("timestamp") == expected_timestamps[index] + for index, thumbnail in enumerate(thumbnails): + assert thumbnail.filename is not None + assert thumbnail.properties.get("id") == f"thumbnail_{index}" + assert thumbnail.properties.get("timestamp") == expected_timestamps[index] diff --git a/tests/enrichers/test_whisper_enricher.py b/tests/enrichers/test_whisper_enricher.py index 873198f..ee1844a 100644 --- a/tests/enrichers/test_whisper_enricher.py +++ b/tests/enrichers/test_whisper_enricher.py @@ -1,18 +1,14 @@ -import shutil -import sys import pytest -from unittest.mock import MagicMock, patch + from auto_archiver.core import Metadata, Media from auto_archiver.modules.s3_storage import S3Storage - from auto_archiver.modules.whisper_enricher import WhisperEnricher - TEST_S3_URL = "http://cdn.example.com/test.mp4" @pytest.fixture -def enricher(): +def enricher(mocker): """Fixture with mocked S3 and API dependencies""" config = { "api_endpoint": "http://testapi", @@ -22,7 +18,7 @@ def enricher(): "action": "translate", "steps": {"storages": ["s3_storage"]} } - mock_s3 = MagicMock(spec=S3Storage) + mock_s3 = mocker.MagicMock(spec=S3Storage) mock_s3.get_cdn_url.return_value = TEST_S3_URL instance = WhisperEnricher() instance.name = "whisper_enricher" @@ -43,16 +39,16 @@ def metadata(): @pytest.fixture -def mock_requests(): - with patch("auto_archiver.modules.whisper_enricher.whisper_enricher.requests") as mock_requests: - mock_response = MagicMock() - mock_response.status_code = 201 - mock_response.json.return_value = {"id": "job123"} - mock_requests.post.return_value = mock_response - yield mock_requests +def mock_requests(mocker): + mock_requests = mocker.patch("auto_archiver.modules.whisper_enricher.whisper_enricher.requests") + mock_response = mocker.MagicMock() + mock_response.status_code = 201 + mock_response.json.return_value = {"id": "job123"} + mock_requests.post.return_value = mock_response + yield mock_requests -def test_successful_job_submission(enricher, metadata, mock_requests): +def test_successful_job_submission(enricher, metadata, mock_requests, mocker): """Test successful media processing with S3 configured""" whisper, mock_s3 = enricher # Configure mock S3 URL to match test expectation @@ -65,13 +61,13 @@ def test_successful_job_submission(enricher, metadata, mock_requests): metadata.media = [m] # Mock the complete API interaction chain - mock_status_response = MagicMock() + mock_status_response = mocker.MagicMock() mock_status_response.status_code = 200 mock_status_response.json.return_value = { "status": "success", "meta": {} } - mock_artifacts_response = MagicMock() + mock_artifacts_response = mocker.MagicMock() mock_artifacts_response.status_code = 200 mock_artifacts_response.json.return_value = [{ "data": [{"start": 0, "end": 5, "text": "test transcript"}] @@ -93,35 +89,39 @@ def test_successful_job_submission(enricher, metadata, mock_requests): # Verify job status checks assert mock_requests.get.call_count == 2 assert "artifact_0_text" in metadata.media[0].get("whisper_model") - assert metadata.media[0].get("whisper_model") == {'artifact_0_text': 'test transcript', 'job_artifacts_check': 'http://testapi/jobs/job123/artifacts', 'job_id': 'job123', 'job_status_check': 'http://testapi/jobs/job123'} + assert metadata.media[0].get("whisper_model") == {'artifact_0_text': 'test transcript', + 'job_artifacts_check': 'http://testapi/jobs/job123/artifacts', + 'job_id': 'job123', + 'job_status_check': 'http://testapi/jobs/job123'} - -def test_submit_job(enricher): +def test_submit_job(enricher, mocker): """Test job submission method""" whisper, _ = enricher m = Media("test.mp4") m.add_url(TEST_S3_URL) - with patch("auto_archiver.modules.whisper_enricher.whisper_enricher.requests") as mock_requests: - mock_response = MagicMock() - mock_response.status_code = 201 - mock_response.json.return_value = {"id": "job123"} - mock_requests.post.return_value = mock_response - job_id = whisper.submit_job(m) + mock_requests = mocker.patch("auto_archiver.modules.whisper_enricher.whisper_enricher.requests") + mock_response = mocker.MagicMock() + mock_response.status_code = 201 + mock_response.json.return_value = {"id": "job123"} + mock_requests.post.return_value = mock_response + job_id = whisper.submit_job(m) assert job_id == "job123" -def test_submit_raises_status(enricher): + +def test_submit_raises_status(enricher, mocker): whisper, _ = enricher m = Media("test.mp4") m.add_url(TEST_S3_URL) - with patch("auto_archiver.modules.whisper_enricher.whisper_enricher.requests") as mock_requests: - mock_response = MagicMock() - mock_response.status_code = 400 - mock_response.json.return_value = {"id": "job123"} - mock_requests.post.return_value = mock_response - with pytest.raises(AssertionError) as exc_info: - whisper.submit_job(m) - assert str(exc_info.value) == "calling the whisper api http://testapi returned a non-success code: 400" + mock_requests = mocker.patch("auto_archiver.modules.whisper_enricher.whisper_enricher.requests") + mock_response = mocker.MagicMock() + mock_response.status_code = 400 + mock_response.json.return_value = {"id": "job123"} + mock_requests.post.return_value = mock_response + with pytest.raises(AssertionError) as exc_info: + whisper.submit_job(m) + assert str(exc_info.value) == "calling the whisper api http://testapi returned a non-success code: 400" + # @pytest.mark.parametrize("test_url, status", ["http://cdn.example.com/test.mp4",]) def test_submit_job_fails(enricher): @@ -131,5 +131,3 @@ def test_submit_job_fails(enricher): m.add_url("http://cdn.wrongurl.com/test.mp4") with pytest.raises(AssertionError): whisper.submit_job(m) - - diff --git a/tests/extractors/test_instagram_api_extractor.py b/tests/extractors/test_instagram_api_extractor.py index c119e3f..7eba8e9 100644 --- a/tests/extractors/test_instagram_api_extractor.py +++ b/tests/extractors/test_instagram_api_extractor.py @@ -1,15 +1,12 @@ from datetime import datetime -from typing import Type import pytest -from unittest.mock import patch, MagicMock from auto_archiver.core import Metadata from auto_archiver.modules.instagram_api_extractor.instagram_api_extractor import InstagramAPIExtractor from .test_extractor_base import TestExtractorBase - @pytest.fixture def mock_user_response(): return { @@ -115,74 +112,74 @@ class TestInstagramAPIExtractor(TestExtractorBase): # test gets text (metadata title) pass - def test_download_profile_basic(self, metadata, mock_user_response): + def test_download_profile_basic(self, metadata, mock_user_response, mocker): """Test basic profile download without full_profile""" - with patch.object(self.extractor, 'call_api') as mock_call, \ - patch.object(self.extractor, 'download_from_url') as mock_download: - # Mock API responses - mock_call.return_value = mock_user_response - mock_download.return_value = "profile.jpg" + mock_call = mocker.patch.object(self.extractor, 'call_api') + mock_download = mocker.patch.object(self.extractor, 'download_from_url') + # Mock API responses + mock_call.return_value = mock_user_response + mock_download.return_value = "profile.jpg" - result = self.extractor.download_profile(metadata, "test_user") - assert result.status == "insta profile: success" - assert result.get_title() == "Test User" - assert result.get("data") == self.extractor.cleanup_dict(mock_user_response["user"]) - # Verify profile picture download - mock_call.assert_called_once_with("v2/user/by/username", {"username": "test_user"}) - mock_download.assert_called_once_with("http://example.com/profile.jpg") - assert len(result.media) == 1 - assert result.media[0].filename == "profile.jpg" + result = self.extractor.download_profile(metadata, "test_user") + assert result.status == "insta profile: success" + assert result.get_title() == "Test User" + assert result.get("data") == self.extractor.cleanup_dict(mock_user_response["user"]) + # Verify profile picture download + mock_call.assert_called_once_with("v2/user/by/username", {"username": "test_user"}) + mock_download.assert_called_once_with("http://example.com/profile.jpg") + assert len(result.media) == 1 + assert result.media[0].filename == "profile.jpg" - def test_download_profile_full(self, metadata, mock_user_response, mock_story_response): + def test_download_profile_full(self, metadata, mock_user_response, mock_story_response, mocker): """Test full profile download with stories/posts""" - with patch.object(self.extractor, 'call_api') as mock_call, \ - patch.object(self.extractor, 'download_all_posts') as mock_posts, \ - patch.object(self.extractor, 'download_all_highlights') as mock_highlights, \ - patch.object(self.extractor, 'download_all_tagged') as mock_tagged, \ - patch.object(self.extractor, '_download_stories_reusable') as mock_stories: + mock_call = mocker.patch.object(self.extractor, 'call_api') + mock_posts = mocker.patch.object(self.extractor, 'download_all_posts') + mock_highlights = mocker.patch.object(self.extractor, 'download_all_highlights') + mock_tagged = mocker.patch.object(self.extractor, 'download_all_tagged') + mock_stories = mocker.patch.object(self.extractor, '_download_stories_reusable') - self.extractor.full_profile = True - mock_call.side_effect = [ - mock_user_response, - mock_story_response - ] - mock_highlights.return_value = None - mock_stories.return_value = mock_story_response - mock_posts.return_value = None - mock_tagged.return_value = None + self.extractor.full_profile = True + mock_call.side_effect = [ + mock_user_response, + mock_story_response + ] + mock_highlights.return_value = None + mock_stories.return_value = mock_story_response + mock_posts.return_value = None + mock_tagged.return_value = None - result = self.extractor.download_profile(metadata, "test_user") - assert result.get("#stories") == len(mock_story_response) - mock_posts.assert_called_once_with(result, "123") - assert "errors" not in result.metadata + result = self.extractor.download_profile(metadata, "test_user") + assert result.get("#stories") == len(mock_story_response) + mock_posts.assert_called_once_with(result, "123") + assert "errors" not in result.metadata - def test_download_profile_not_found(self, metadata): + def test_download_profile_not_found(self, metadata, mocker): """Test profile not found error""" - with patch.object(self.extractor, 'call_api') as mock_call: - mock_call.return_value = {"user": None} - with pytest.raises(AssertionError) as exc_info: - self.extractor.download_profile(metadata, "invalid_user") - assert "User invalid_user not found" in str(exc_info.value) + mock_call = mocker.patch.object(self.extractor, 'call_api') + mock_call.return_value = {"user": None} + with pytest.raises(AssertionError) as exc_info: + self.extractor.download_profile(metadata, "invalid_user") + assert "User invalid_user not found" in str(exc_info.value) - def test_download_profile_error_handling(self, metadata, mock_user_response): + def test_download_profile_error_handling(self, metadata, mock_user_response, mocker): """Test error handling in full profile mode""" - with (patch.object(self.extractor, 'call_api') as mock_call, \ - patch.object(self.extractor, 'download_all_highlights') as mock_highlights, \ - patch.object(self.extractor, 'download_all_tagged') as mock_tagged, \ - patch.object(self.extractor, '_download_stories_reusable') as stories_tagged, \ - patch.object(self.extractor, 'download_all_posts') as mock_posts - ): - self.extractor.full_profile = True - mock_call.side_effect = [ - mock_user_response, - Exception("Stories API failed"), - Exception("Posts API failed") - ] - mock_highlights.return_value = None - mock_tagged.return_value = None - stories_tagged.return_value = None - mock_posts.return_value = None - result = self.extractor.download_profile(metadata, "test_user") + mock_call = mocker.patch.object(self.extractor, 'call_api') + mock_highlights = mocker.patch.object(self.extractor, 'download_all_highlights') + mock_tagged = mocker.patch.object(self.extractor, 'download_all_tagged') + stories_tagged = mocker.patch.object(self.extractor, '_download_stories_reusable') + mock_posts = mocker.patch.object(self.extractor, 'download_all_posts') - assert result.is_success() - assert "Error downloading stories for test_user" in result.metadata["errors"] \ No newline at end of file + self.extractor.full_profile = True + mock_call.side_effect = [ + mock_user_response, + Exception("Stories API failed"), + Exception("Posts API failed") + ] + mock_highlights.return_value = None + mock_tagged.return_value = None + stories_tagged.return_value = None + mock_posts.return_value = None + result = self.extractor.download_profile(metadata, "test_user") + + assert result.is_success() + assert "Error downloading stories for test_user" in result.metadata["errors"] \ No newline at end of file diff --git a/tests/extractors/test_instagram_tbot_extractor.py b/tests/extractors/test_instagram_tbot_extractor.py index 9df9983..9238f89 100644 --- a/tests/extractors/test_instagram_tbot_extractor.py +++ b/tests/extractors/test_instagram_tbot_extractor.py @@ -1,5 +1,4 @@ import os -from unittest.mock import patch, MagicMock import pytest @@ -11,16 +10,10 @@ TESTFILES = os.path.join(os.path.dirname(__file__), "testfiles") @pytest.fixture -def patch_extractor_methods(request, setup_module): - with patch.object(InstagramTbotExtractor, '_prepare_session_file', return_value=None), \ - patch.object(InstagramTbotExtractor, '_initialize_telegram_client', return_value=None): - yield - -@pytest.fixture(autouse=True) -def mock_sleep(): - """Globally mock time.sleep to avoid delays.""" - with patch("time.sleep") as mock_sleep: - yield mock_sleep +def patch_extractor_methods(request, setup_module, mocker): + mocker.patch.object(InstagramTbotExtractor, '_prepare_session_file', return_value=None) + mocker.patch.object(InstagramTbotExtractor, '_initialize_telegram_client', return_value=None) + yield @pytest.fixture @@ -33,16 +26,16 @@ def metadata_sample(): @pytest.fixture -def mock_telegram_client(): +def mock_telegram_client(mocker): """Fixture to mock TelegramClient interactions.""" - with patch("auto_archiver.modules.instagram_tbot_extractor.client") as mock_client: - instance = MagicMock() - mock_client.return_value = instance - yield instance + mock_client = mocker.patch("auto_archiver.modules.instagram_tbot_extractor.client") + instance = mocker.MagicMock() + mock_client.return_value = instance + return instance @pytest.fixture -def extractor(setup_module, patch_extractor_methods): +def extractor(setup_module, patch_extractor_methods, mocker): extractor_module = "instagram_tbot_extractor" config = { "api_id": 12345, @@ -51,7 +44,7 @@ def extractor(setup_module, patch_extractor_methods): "timeout": 4 } extractor = setup_module(extractor_module, config) - extractor.client = MagicMock() + extractor.client = mocker.MagicMock() extractor.session_file = "test_session" return extractor @@ -60,20 +53,20 @@ def test_non_instagram_url(extractor, metadata_sample): metadata_sample.set_url("https://www.youtube.com") assert extractor.download(metadata_sample) is False -def test_download_success(extractor, metadata_sample): - with patch.object(extractor, "_send_url_to_bot", return_value=(MagicMock(), 101)), \ - patch.object(extractor, "_process_messages", return_value="Sample Instagram post caption"): - result = extractor.download(metadata_sample) + +def test_download_success(extractor, metadata_sample, mocker): + mocker.patch.object(extractor, "_send_url_to_bot", return_value=(mocker.MagicMock(), 101)) + mocker.patch.object(extractor, "_process_messages", return_value="Sample Instagram post caption") + result = extractor.download(metadata_sample) assert result.is_success() assert result.status == "insta-via-bot: success" assert result.metadata.get("title") == "Sample Instagram post caption" -def test_download_invalid(extractor, metadata_sample): - with patch.object(extractor, "_send_url_to_bot", return_value=(MagicMock(), 101)), \ - patch.object(extractor, "_process_messages", return_value="You must enter a URL to a post"): - assert extractor.download(metadata_sample) is False - +def test_download_invalid(extractor, metadata_sample, mocker): + mocker.patch.object(extractor, "_send_url_to_bot", return_value=(mocker.MagicMock(), 101)) + mocker.patch.object(extractor, "_process_messages", return_value="You must enter a URL to a post") + assert extractor.download(metadata_sample) is False @pytest.mark.skip(reason="Requires authentication.") @@ -89,8 +82,12 @@ class TestInstagramTbotExtractorReal(TestExtractorBase): } @pytest.mark.parametrize("url, expected_status, message, len_media", [ - ("https://www.instagram.com/p/C4QgLbrIKXG", "insta-via-bot: success", "Are you new to Bellingcat? - The way we share our investigations is different. 💭\nWe want you to read our story but also learn ou", 6), - ("https://www.instagram.com/reel/DEVLK8qoIbg/", "insta-via-bot: success", "Our volunteer community is at the centre of many incredible Bellingcat investigations and tools. Stephanie Ladel is one such vol", 3), + ("https://www.instagram.com/p/C4QgLbrIKXG", "insta-via-bot: success", + "Are you new to Bellingcat? - The way we share our investigations is different. 💭\nWe want you to read our story but also learn ou", + 6), + ("https://www.instagram.com/reel/DEVLK8qoIbg/", "insta-via-bot: success", + "Our volunteer community is at the centre of many incredible Bellingcat investigations and tools. Stephanie Ladel is one such vol", + 3), # instagram tbot not working (potentially intermittently?) for stories - replace with a live story to retest # ("https://www.instagram.com/stories/bellingcatofficial/3556336382743057476/", False, "Media not found or unavailable"), # Seems to be working intermittently for highlights diff --git a/tests/feeders/test_gsheet_feeder.py b/tests/feeders/test_gsheet_feeder.py index b86e329..7c5f501 100644 --- a/tests/feeders/test_gsheet_feeder.py +++ b/tests/feeders/test_gsheet_feeder.py @@ -2,27 +2,23 @@ from typing import Type import gspread import pytest -from unittest.mock import patch, MagicMock from auto_archiver.modules.gsheet_feeder import GsheetsFeeder from auto_archiver.core import Metadata, Feeder -def test_setup_without_sheet_and_sheet_id(setup_module): +def test_setup_without_sheet_and_sheet_id(setup_module, mocker): # Ensure setup() raises AssertionError if neither sheet nor sheet_id is set. - with patch("gspread.service_account"): - with pytest.raises(AssertionError): - setup_module( - "gsheet_feeder", - {"service_account": "dummy.json", "sheet": None, "sheet_id": None}, - ) + mocker.patch("gspread.service_account") + with pytest.raises(AssertionError): + setup_module( + "gsheet_feeder", + {"service_account": "dummy.json", "sheet": None, "sheet_id": None}, + ) @pytest.fixture -def gsheet_feeder(setup_module) -> GsheetsFeeder: - with patch("gspread.service_account"): - feeder = setup_module( - "gsheet_feeder", - { +def gsheet_feeder(setup_module, mocker) -> GsheetsFeeder: + config: dict = { "service_account": "dummy.json", "sheet": "test-auto-archiver", "sheet_id": None, @@ -46,9 +42,13 @@ def gsheet_feeder(setup_module) -> GsheetsFeeder: "allow_worksheets": set(), "block_worksheets": set(), "use_sheet_names_in_stored_paths": True, - }, - ) - feeder.gsheets_client = MagicMock() + } + mocker.patch("gspread.service_account") + feeder = setup_module( + "gsheet_feeder", + config + ) + feeder.gsheets_client = mocker.MagicMock() return feeder @@ -129,56 +129,56 @@ def test__set_metadata_with_folder(gsheet_feeder: GsheetsFeeder): ], ) def test_open_sheet_with_name_or_id( - setup_module, sheet, sheet_id, expected_method, expected_arg, description + setup_module, sheet, sheet_id, expected_method, expected_arg, description, mocker ): """Ensure open_sheet() correctly opens by name or ID based on configuration.""" - with patch("gspread.service_account") as mock_service_account: - mock_client = MagicMock() - mock_service_account.return_value = mock_client - mock_client.open.return_value = "MockSheet" - mock_client.open_by_key.return_value = "MockSheet" + mock_service_account = mocker.patch("gspread.service_account") + mock_client = mocker.MagicMock() + mock_service_account.return_value = mock_client + mock_client.open.return_value = "MockSheet" + mock_client.open_by_key.return_value = "MockSheet" - # Setup module with parameterized values - feeder = setup_module( - "gsheet_feeder", - {"service_account": "dummy.json", "sheet": sheet, "sheet_id": sheet_id}, - ) - sheet_result = feeder.open_sheet() - # Validate the correct method was called - getattr(mock_client, expected_method).assert_called_once_with( - expected_arg - ), f"Failed: {description}" - assert sheet_result == "MockSheet", f"Failed: {description}" + # Setup module with parameterized values + feeder = setup_module( + "gsheet_feeder", + {"service_account": "dummy.json", "sheet": sheet, "sheet_id": sheet_id}, + ) + sheet_result = feeder.open_sheet() + # Validate the correct method was called + getattr(mock_client, expected_method).assert_called_once_with( + expected_arg + ), f"Failed: {description}" + assert sheet_result == "MockSheet", f"Failed: {description}" @pytest.mark.usefixtures("setup_module") -def test_open_sheet_with_sheet_id(setup_module): +def test_open_sheet_with_sheet_id(setup_module, mocker): """Ensure open_sheet() correctly opens a sheet by ID.""" - with patch("gspread.service_account") as mock_service_account: - mock_client = MagicMock() - mock_service_account.return_value = mock_client - mock_client.open_by_key.return_value = "MockSheet" - feeder = setup_module( - "gsheet_feeder", - {"service_account": "dummy.json", "sheet": None, "sheet_id": "ABC123"}, - ) - sheet = feeder.open_sheet() - mock_client.open_by_key.assert_called_once_with("ABC123") - assert sheet == "MockSheet" + mock_service_account = mocker.patch("gspread.service_account") + mock_client = mocker.MagicMock() + mock_service_account.return_value = mock_client + mock_client.open_by_key.return_value = "MockSheet" + feeder = setup_module( + "gsheet_feeder", + {"service_account": "dummy.json", "sheet": None, "sheet_id": "ABC123"}, + ) + sheet = feeder.open_sheet() + mock_client.open_by_key.assert_called_once_with("ABC123") + assert sheet == "MockSheet" -def test_should_process_sheet(setup_module): - with patch("gspread.service_account"): - gdb = setup_module( - "gsheet_feeder", - { - "service_account": "dummy.json", - "sheet": "TestSheet", - "sheet_id": None, - "allow_worksheets": {"TestSheet", "Sheet2"}, - "block_worksheets": {"Sheet3"}, - }, - ) +def test_should_process_sheet(setup_module, mocker): + mocker.patch("gspread.service_account") + gdb = setup_module( + "gsheet_feeder", + { + "service_account": "dummy.json", + "sheet": "TestSheet", + "sheet_id": None, + "allow_worksheets": {"TestSheet", "Sheet2"}, + "block_worksheets": {"Sheet3"}, + }, + ) assert gdb.should_process_sheet("TestSheet") == True assert gdb.should_process_sheet("Sheet3") == False # False if allow_worksheets is set diff --git a/tests/feeders/test_gworksheet.py b/tests/feeders/test_gworksheet.py index 016cfb2..2b05504 100644 --- a/tests/feeders/test_gworksheet.py +++ b/tests/feeders/test_gworksheet.py @@ -1,14 +1,13 @@ # Note this isn't a feeder, but contained as utility of the gsheet feeder module import pytest -from unittest.mock import MagicMock from auto_archiver.modules.gsheet_feeder import GWorksheet class TestGWorksheet: @pytest.fixture - def mock_worksheet(self): - mock_ws = MagicMock() + def mock_worksheet(self, mocker): + mock_ws = mocker.MagicMock() mock_ws.get_values.return_value = [ ["Link", "Archive Status", "Archive Location", "Archive Date"], ["url1", "archived", "filepath1", "2023-01-01"], @@ -137,8 +136,8 @@ class TestGWorksheet: assert gworksheet.to_a1(row, col) == expected # Test empty worksheet - def test_empty_worksheet_initialization(self): - mock_ws = MagicMock() + def test_empty_worksheet_initialization(self, mocker): + mock_ws = mocker.MagicMock() mock_ws.get_values.return_value = [] g = GWorksheet(mock_ws) assert g.headers == [] diff --git a/tests/storages/test_S3_storage.py b/tests/storages/test_S3_storage.py index 2a5d026..fe60329 100644 --- a/tests/storages/test_S3_storage.py +++ b/tests/storages/test_S3_storage.py @@ -1,6 +1,5 @@ from typing import Type import pytest -from unittest.mock import MagicMock, patch from auto_archiver.core import Media from auto_archiver.modules.s3_storage import S3Storage @@ -11,7 +10,6 @@ class TestS3Storage: """ module_name: str = "s3_storage" storage: Type[S3Storage] - s3: MagicMock config: dict = { "path_generator": "flat", "filename_generator": "static", @@ -25,13 +23,14 @@ class TestS3Storage: "private": False, } - @patch('boto3.client') @pytest.fixture(autouse=True) - def setup_storage(self, setup_module): + def setup_storage(self, setup_module, mocker): + self.s3 = S3Storage() self.storage = setup_module(self.module_name, self.config) def test_client_initialization(self): """Test that S3 client is initialized with correct parameters""" + assert self.storage.s3 is not None assert self.storage.s3.meta.region_name == 'test-region' @@ -44,81 +43,63 @@ class TestS3Storage: media.key = "another/path.jpg" assert self.storage.get_cdn_url(media) == "https://cdn.example.com/another/path.jpg" - def test_uploadf_sets_acl_public(self): + def test_uploadf_sets_acl_public(self, mocker): media = Media("test.txt") - mock_file = MagicMock() - with patch.object(self.storage.s3, 'upload_fileobj') as mock_s3_upload, \ - patch.object(self.storage, 'is_upload_needed', return_value=True): - self.storage.uploadf(mock_file, media) - mock_s3_upload.assert_called_once_with( - mock_file, - Bucket='test-bucket', - Key=media.key, - ExtraArgs={'ACL': 'public-read', 'ContentType': 'text/plain'} - ) + mock_file = mocker.MagicMock() + mock_s3_upload = mocker.patch.object(self.storage.s3, 'upload_fileobj') + mocker.patch.object(self.storage, 'is_upload_needed', return_value=True) + self.storage.uploadf(mock_file, media) + mock_s3_upload.assert_called_once_with( + mock_file, + Bucket='test-bucket', + Key=media.key, + ExtraArgs={'ACL': 'public-read', 'ContentType': 'text/plain'} + ) - def test_upload_decision_logic(self): + def test_upload_decision_logic(self, mocker): """Test is_upload_needed under different conditions""" media = Media("test.txt") - # Test default state (random_no_duplicate=False) assert self.storage.is_upload_needed(media) is True - # Set duplicate checking config to true: - self.storage.random_no_duplicate = True - with patch('auto_archiver.modules.s3_storage.s3_storage.calculate_file_hash') as mock_calc_hash, \ - patch.object(self.storage, 'file_in_folder') as mock_file_in_folder: - mock_calc_hash.return_value = 'beepboop123beepboop123beepboop123' - mock_file_in_folder.return_value = 'existing_key.txt' - # Test duplicate result - assert self.storage.is_upload_needed(media) is False - assert media.key == 'existing_key.txt' - mock_file_in_folder.assert_called_with( - # (first 24 chars of hash) - 'no-dups/beepboop123beepboop123be' - ) + mock_calc_hash = mocker.patch('auto_archiver.modules.s3_storage.s3_storage.calculate_file_hash', return_value='beepboop123beepboop123beepboop123') + mock_file_in_folder = mocker.patch.object(self.storage, 'file_in_folder', return_value='existing_key.txt') + assert self.storage.is_upload_needed(media) is False + assert media.key == 'existing_key.txt' + mock_file_in_folder.assert_called_with('no-dups/beepboop123beepboop123be') - - @patch.object(S3Storage, 'file_in_folder') - def test_skips_upload_when_duplicate_exists(self, mock_file_in_folder): + def test_skips_upload_when_duplicate_exists(self, mocker): """Test that upload skips when file_in_folder finds existing object""" self.storage.random_no_duplicate = True - mock_file_in_folder.return_value = "existing_folder/existing_file.txt" - # Create test media with calculated hash + mock_file_in_folder = mocker.patch.object(S3Storage, 'file_in_folder', return_value="existing_folder/existing_file.txt") media = Media("test.txt") media.key = "original_path.txt" - with patch('auto_archiver.modules.s3_storage.s3_storage.calculate_file_hash') as mock_calculate_hash: - mock_calculate_hash.return_value = "beepboop123beepboop123beepboop123" - # Verify upload - assert self.storage.is_upload_needed(media) is False - assert media.key == "existing_folder/existing_file.txt" - assert media.get("previously archived") is True - with patch.object(self.storage.s3, 'upload_fileobj') as mock_upload: - result = self.storage.uploadf(None, media) - mock_upload.assert_not_called() - assert result is True + mock_calculate_hash = mocker.patch('auto_archiver.modules.s3_storage.s3_storage.calculate_file_hash', return_value="beepboop123beepboop123beepboop123") + assert self.storage.is_upload_needed(media) is False + assert media.key == "existing_folder/existing_file.txt" + assert media.get("previously archived") is True + mock_upload = mocker.patch.object(self.storage.s3, 'upload_fileobj') + result = self.storage.uploadf(None, media) + mock_upload.assert_not_called() + assert result is True - @patch.object(S3Storage, 'is_upload_needed') - def test_uploads_with_correct_parameters(self, mock_upload_needed): + def test_uploads_with_correct_parameters(self, mocker): media = Media("test.txt") media.key = "original_key.txt" - mock_upload_needed.return_value = True + mocker.patch.object(S3Storage, 'is_upload_needed', return_value=True) media.mimetype = 'image/png' - mock_file = MagicMock() + mock_file = mocker.MagicMock() + mock_upload = mocker.patch.object(self.storage.s3, 'upload_fileobj') + self.storage.uploadf(mock_file, media) + mock_upload.assert_called_once_with( + mock_file, + Bucket='test-bucket', + Key='original_key.txt', + ExtraArgs={ + 'ACL': 'public-read', + 'ContentType': 'image/png' + } + ) - with patch.object(self.storage.s3, 'upload_fileobj') as mock_upload: - self.storage.uploadf(mock_file, media) - # verify call occured with these params - mock_upload.assert_called_once_with( - mock_file, - Bucket='test-bucket', - Key='original_key.txt', - ExtraArgs={ - 'ACL': 'public-read', - 'ContentType': 'image/png' - } - ) - - def test_file_in_folder_exists(self): - with patch.object(self.storage.s3, 'list_objects') as mock_list_objects: - mock_list_objects.return_value = {'Contents': [{'Key': 'path/to/file.txt'}]} - assert self.storage.file_in_folder('path/to/') == 'path/to/file.txt' \ No newline at end of file + def test_file_in_folder_exists(self, mocker): + mock_list_objects = mocker.patch.object(self.storage.s3, 'list_objects', return_value={'Contents': [{'Key': 'path/to/file.txt'}]}) + assert self.storage.file_in_folder('path/to/') == 'path/to/file.txt' diff --git a/tests/storages/test_gdrive_storage.py b/tests/storages/test_gdrive_storage.py index aba0a25..f5ff87c 100644 --- a/tests/storages/test_gdrive_storage.py +++ b/tests/storages/test_gdrive_storage.py @@ -1,44 +1,57 @@ from typing import Type import pytest -from unittest.mock import MagicMock, patch +from oauth2client import service_account + from auto_archiver.core import Media from auto_archiver.modules.gdrive_storage import GDriveStorage from auto_archiver.core.metadata import Metadata from tests.storages.test_storage_base import TestStorageBase -class TestGDriveStorage: - """ - Test suite for GDriveStorage. - """ - +@pytest.fixture +def gdrive_storage(setup_module, mocker): module_name: str = "gdrive_storage" - storage: Type[GDriveStorage] + storage: GDriveStorage config: dict = {'path_generator': 'url', 'filename_generator': 'static', 'root_folder_id': "fake_root_folder_id", 'oauth_token': None, 'service_account': 'fake_service_account.json' } - - @pytest.fixture(autouse=True) - def gdrive(self, setup_module): - with patch('google.oauth2.service_account.Credentials.from_service_account_file') as mock_creds: - self.storage = setup_module(self.module_name, self.config) - - def test_initialize_fails_with_non_existent_creds(self): - """ - Test that the Google Drive service raises a FileNotFoundError when the service account file does not exist. - """ - # Act and Assert - with pytest.raises(FileNotFoundError) as exc_info: - self.storage.setup() - assert "No such file or directory" in str(exc_info.value) + mocker.patch('google.oauth2.service_account.Credentials.from_service_account_file') + return setup_module(module_name, config) - def test_path_parts(self): - media = Media(filename="test.jpg") - media.key = "folder1/folder2/test.jpg" +def test_initialize_fails_with_non_existent_creds(setup_module): + """Test that the Google Drive service raises a FileNotFoundError when the service account file does not exist. + (and isn't mocked) + """ + config: dict = {'path_generator': 'url', + 'filename_generator': 'static', + 'root_folder_id': "fake_root_folder_id", + 'oauth_token': None, + 'service_account': 'fake_service_account.json' + } + with pytest.raises(FileNotFoundError) as exc_info: + setup_module("gdrive_storage", config) + assert "No such file or directory" in str(exc_info.value) + + +def test_get_id_from_parent_and_name(gdrive_storage, mocker): + """Test _get_id_from_parent_and_name returns correct id from an API result.""" + fake_list = mocker.MagicMock() + fake_list.execute.return_value = {"files": [{"id": "123", "name": "testname"}]} + fake_service = mocker.MagicMock() + # mock the files.list return value + fake_service.files.return_value.list.return_value = fake_list + gdrive_storage.service = fake_service + result = gdrive_storage._get_id_from_parent_and_name("parent", "mock", retries=1, use_mime_type=False) + assert result == "123" + +def test_path_parts(): + media = Media(filename="test.jpg") + media.key = "folder1/folder2/test.jpg" + @pytest.mark.skip(reason="Requires real credentials") diff --git a/tests/utils/test_misc.py b/tests/utils/test_misc.py index e45c1c1..0023077 100644 --- a/tests/utils/test_misc.py +++ b/tests/utils/test_misc.py @@ -1,7 +1,6 @@ import hashlib import json from datetime import datetime, timezone -from unittest.mock import Mock, patch import pytest @@ -44,20 +43,19 @@ class TestURLExpansion: ("https://example.com", "https://example.com"), ("https://t.co/test", "https://expanded.url") ]) - def test_expand_url(self, input_url, expected): - mock_response = Mock() + def test_expand_url(self, input_url, expected, mocker): + mock_response = mocker.Mock() mock_response.url = "https://expanded.url" - with patch('requests.get', return_value=mock_response): + mocker.patch('requests.get', return_value=mock_response) + result = expand_url(input_url) + assert result == expected - result = expand_url(input_url) - assert result == expected - - def test_expand_url_handles_errors(self, caplog): - with patch('requests.get', side_effect=Exception("Connection error")): - url = "https://t.co/error" - result = expand_url(url) - assert result == url - assert f"Failed to expand url {url}" in caplog.text + def test_expand_url_handles_errors(self, caplog, mocker): + mocker.patch('requests.get', side_effect=Exception("Connection error")) + url = "https://t.co/error" + result = expand_url(url) + assert result == url + assert f"Failed to expand url {url}" in caplog.text class TestAttributeHandling: class Sample: From 10a5ad62b837b356058a1d0680ff3d3d7d9f5f8d Mon Sep 17 00:00:00 2001 From: erinhmclark Date: Wed, 19 Feb 2025 09:18:41 +0000 Subject: [PATCH 049/160] Include Atlos tests, metadata fixture. --- .../modules/atlos_db/__init__.py | 2 +- tests/conftest.py | 2 - tests/databases/test_api_db.py | 8 - tests/databases/test_atlos_db.py | 110 ++++++++++++++ tests/enrichers/test_meta_enricher.py | 8 - tests/feeders/test_atlos_feeder.py | 108 +++++++++++++ tests/storages/test_atlos_storage.py | 142 ++++++++++++++++++ 7 files changed, 361 insertions(+), 19 deletions(-) create mode 100644 tests/databases/test_atlos_db.py create mode 100644 tests/feeders/test_atlos_feeder.py create mode 100644 tests/storages/test_atlos_storage.py diff --git a/src/auto_archiver/modules/atlos_db/__init__.py b/src/auto_archiver/modules/atlos_db/__init__.py index 1552e39..e14d202 100644 --- a/src/auto_archiver/modules/atlos_db/__init__.py +++ b/src/auto_archiver/modules/atlos_db/__init__.py @@ -1 +1 @@ -from atlos_db import AtlosDb \ No newline at end of file +from .atlos_db import AtlosDb \ No newline at end of file diff --git a/tests/conftest.py b/tests/conftest.py index a32ca48..2927735 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -155,7 +155,5 @@ def mock_sleep(mocker): def metadata(): metadata = Metadata() metadata.set("_processed_at", "2021-01-01T00:00:00") - metadata.set_title("Example Title") - metadata.set_content("Example Content") metadata.set_url("https://example.com") return metadata \ No newline at end of file diff --git a/tests/databases/test_api_db.py b/tests/databases/test_api_db.py index 6d7a2bc..5d1ea84 100644 --- a/tests/databases/test_api_db.py +++ b/tests/databases/test_api_db.py @@ -19,14 +19,6 @@ def api_db(setup_module): return setup_module(AAApiDb, configs) -@pytest.fixture -def metadata(): - metadata = Metadata() - metadata.set("_processed_at", "2021-01-01T00:00:00") - metadata.set_url("https://example.com") - return metadata - - def test_fetch_no_cache(api_db, metadata): # Test fetch api_db.use_api_cache = False diff --git a/tests/databases/test_atlos_db.py b/tests/databases/test_atlos_db.py new file mode 100644 index 0000000..82c07ef --- /dev/null +++ b/tests/databases/test_atlos_db.py @@ -0,0 +1,110 @@ +import pytest +from datetime import datetime + +from auto_archiver.core import Metadata +from auto_archiver.modules.atlos_db import AtlosDb + + +class FakeAPIResponse: + """Simulate a response object.""" + + def __init__(self, data: dict, raise_error: bool = False) -> None: + self._data = data + self.raise_error = raise_error + + def raise_for_status(self) -> None: + if self.raise_error: + raise Exception("HTTP error") + + +@pytest.fixture +def atlos_db(setup_module) -> AtlosDb: + """Fixture for AtlosDb.""" + configs: dict = { + "api_token": "abc123", + "atlos_url": "https://platform.atlos.org", + } + return setup_module("atlos_db", configs) + + +def test_failed_no_atlos_id(atlos_db, metadata, mocker): + """Test failed() skips posting when no atlos_id present.""" + post_mock = mocker.patch("requests.post") + atlos_db.failed(metadata, "failure reason") + post_mock.assert_not_called() + + +def test_failed_with_atlos_id(atlos_db, metadata, mocker): + """Test failed() posts failure when atlos_id is present.""" + metadata.set("atlos_id", 42) + fake_resp = FakeAPIResponse({}, raise_error=False) + post_mock = mocker.patch("requests.post", return_value=fake_resp) + atlos_db.failed(metadata, "failure reason") + expected_url = ( + f"{atlos_db.atlos_url}/api/v2/source_material/metadata/42/auto_archiver" + ) + expected_headers = {"Authorization": f"Bearer {atlos_db.api_token}"} + expected_json = { + "metadata": {"processed": True, "status": "error", "error": "failure reason"} + } + post_mock.assert_called_once_with( + expected_url, headers=expected_headers, json=expected_json + ) + + +def test_failed_http_error(atlos_db, metadata, mocker): + """Test failed() raises exception on HTTP error.""" + metadata.set("atlos_id", 42) + fake_resp = FakeAPIResponse({}, raise_error=True) + mocker.patch("requests.post", return_value=fake_resp) + with pytest.raises(Exception, match="HTTP error"): + atlos_db.failed(metadata, "failure reason") + + +def test_fetch_returns_false(atlos_db): + """Test fetch() always returns False.""" + item = Metadata() + assert atlos_db.fetch(item) is False + + +def test_done_no_atlos_id(atlos_db, mocker): + """Test done() skips posting when no atlos_id present.""" + item = Metadata().set_url("http://example.com") + post_mock = mocker.patch("requests.post") + atlos_db.done(item) + post_mock.assert_not_called() + + +def test_done_with_atlos_id(atlos_db, metadata, mocker): + """Test done() posts success when atlos_id is present.""" + metadata.set("atlos_id", 99) + now = datetime.now() + metadata.set("timestamp", now) + fake_resp = FakeAPIResponse({}, raise_error=False) + post_mock = mocker.patch("requests.post", return_value=fake_resp) + atlos_db.done(metadata) + expected_url = ( + f"{atlos_db.atlos_url}/api/v2/source_material/metadata/99/auto_archiver" + ) + expected_headers = {"Authorization": f"Bearer {atlos_db.api_token}"} + expected_results = metadata.metadata.copy() + expected_results["timestamp"] = now.isoformat() + expected_json = { + "metadata": { + "processed": True, + "status": "success", + "results": expected_results, + } + } + post_mock.assert_called_once_with( + expected_url, headers=expected_headers, json=expected_json + ) + + +def test_done_http_error(atlos_db, metadata, mocker): + """Test done() raises exception on HTTP error.""" + metadata.set("atlos_id", 123) + fake_resp = FakeAPIResponse({}, raise_error=True) + mocker.patch("requests.post", return_value=fake_resp) + with pytest.raises(Exception, match="HTTP error"): + atlos_db.done(metadata) diff --git a/tests/enrichers/test_meta_enricher.py b/tests/enrichers/test_meta_enricher.py index cc283c0..476e25b 100644 --- a/tests/enrichers/test_meta_enricher.py +++ b/tests/enrichers/test_meta_enricher.py @@ -23,14 +23,6 @@ def mock_media(mocker): mock.filename = "mock_file.txt" return mock -@pytest.fixture -def metadata(): - m = Metadata() - m.set_url("https://example.com") - m.set_title("Test Title") - m.set_content("Test Content") - return m - @pytest.fixture(autouse=True) def meta_enricher(setup_module): diff --git a/tests/feeders/test_atlos_feeder.py b/tests/feeders/test_atlos_feeder.py new file mode 100644 index 0000000..f26bdc9 --- /dev/null +++ b/tests/feeders/test_atlos_feeder.py @@ -0,0 +1,108 @@ +import pytest +from auto_archiver.modules.atlos_feeder import AtlosFeeder + + +class FakeAPIResponse: + """Simulate a response object.""" + + def __init__(self, data: dict, raise_error: bool = False) -> None: + self._data = data + self.raise_error = raise_error + + def json(self) -> dict: + return self._data + + def raise_for_status(self) -> None: + if self.raise_error: + raise Exception("HTTP error") + + +@pytest.fixture +def atlos_feeder(setup_module) -> AtlosFeeder: + """Fixture for AtlosFeeder.""" + configs: dict = { + "api_token": "abc123", + "atlos_url": "https://platform.atlos.org", + } + return setup_module("atlos_feeder", configs) + + +@pytest.fixture +def mock_atlos_api(mocker): + """Fixture to mock requests to Atlos API.""" + def _mock_responses(responses): + mocker.patch( + "requests.get", + side_effect=[FakeAPIResponse(data) for data in responses], + ) + return _mock_responses + + +def test_atlos_feeder_iter_yields_valid_metadata(atlos_feeder, mock_atlos_api): + """Test valid items are yielded and invalid ones ignored.""" + mock_atlos_api([ + { + "next": None, + "results": [ + {"source_url": "http://example.com", "id": 1, + "metadata": {"auto_archiver": {"processed": False}}, + "visibility": "visible", "status": "complete"}, + {"source_url": "", "id": 2, + "metadata": {"auto_archiver": {"processed": False}}, + "visibility": "visible", "status": "complete"}, + {"source_url": "http://example.org", "id": 3, + "metadata": {"auto_archiver": {"processed": True}}, + "visibility": "visible", "status": "complete"}, + ], + } + ]) + + items = list(atlos_feeder) + assert len(items) == 1 + assert items[0].get_url() == "http://example.com" + assert items[0].get("atlos_id") == 1 + + +def test_atlos_feeder_multiple_pages(atlos_feeder, mock_atlos_api): + """Test iteration over multiple pages with valid items.""" + mock_atlos_api([ + { + "next": "cursor2", + "results": [ + {"source_url": "http://example1.com", "id": 10, + "metadata": {"auto_archiver": {"processed": False}}, + "visibility": "visible", "status": "complete"}, + ], + }, + { + "next": None, + "results": [ + {"source_url": "http://example2.com", "id": 20, + "metadata": {"auto_archiver": {"processed": False}}, + "visibility": "visible", "status": "complete"}, + ], + }, + ]) + + items = list(atlos_feeder) + assert len(items) == 2 + assert items[0].get_url() == "http://example1.com" + assert items[0].get("atlos_id") == 10 + assert items[1].get_url() == "http://example2.com" + assert items[1].get("atlos_id") == 20 + + +def test_atlos_feeder_no_results(atlos_feeder, mock_atlos_api): + """Test iteration stops when no results are returned.""" + mock_atlos_api([{"next": None, "results": []}]) + assert list(atlos_feeder) == [] + + +def test_atlos_feeder_http_error(atlos_feeder, mocker): + """Test raises an exception on HTTP error.""" + mocker.patch( + "requests.get", + return_value=FakeAPIResponse({"next": None, "results": []}, raise_error=True), + ) + with pytest.raises(Exception, match="HTTP error"): + list(atlos_feeder) diff --git a/tests/storages/test_atlos_storage.py b/tests/storages/test_atlos_storage.py new file mode 100644 index 0000000..7528456 --- /dev/null +++ b/tests/storages/test_atlos_storage.py @@ -0,0 +1,142 @@ +import os +import hashlib +import pytest +from auto_archiver.core import Media, Metadata +from auto_archiver.modules.atlos_storage import AtlosStorage + + +class FakeAPIResponse: + """Simulate a response object.""" + + def __init__(self, data: dict, raise_error: bool = False) -> None: + self._data = data + self.raise_error = raise_error + + def json(self) -> dict: + return self._data + + def raise_for_status(self) -> None: + if self.raise_error: + raise Exception("HTTP error") + + +@pytest.fixture +def atlos_storage(setup_module) -> AtlosStorage: + """Fixture for AtlosStorage.""" + configs: dict = { + "api_token": "abc123", + "atlos_url": "https://platform.atlos.org", + } + return setup_module("atlos_storage", configs) + + +@pytest.fixture +def media(tmp_path) -> Media: + """Fixture for Media.""" + content = b"media content" + file_path = tmp_path / "media.txt" + file_path.write_bytes(content) + media = Media(filename=str(file_path)) + media.properties = {"something": "Title"} + media.key = "key" + return media + + +def test_get_cdn_url(atlos_storage: AtlosStorage) -> None: + """Test get_cdn_url returns the configured atlos_url.""" + media = Media(filename="dummy.mp4") + url = atlos_storage.get_cdn_url(media) + assert url == atlos_storage.atlos_url + + +def test_hash(tmp_path, atlos_storage: AtlosStorage) -> None: + """Test _hash() computes the correct SHA-256 hash of a file.""" + content = b"hello world" + file_path = tmp_path / "test.txt" + file_path.write_bytes(content) + media = Media(filename="dummy.mp4") + media.filename = str(file_path) + expected_hash = hashlib.sha256(content).hexdigest() + assert atlos_storage._hash(media) == expected_hash + + +def test_upload_no_atlos_id(tmp_path, atlos_storage: AtlosStorage, media: Media, mocker) -> None: + """Test upload() returns False when metadata lacks atlos_id.""" + metadata = Metadata() # atlos_id not set + post_mock = mocker.patch("requests.post") + result = atlos_storage.upload(media, metadata) + assert result is False + post_mock.assert_not_called() + + +def test_upload_already_uploaded(atlos_storage: AtlosStorage, + metadata: Metadata, + media: Media, + tmp_path, + mocker) -> None: + """Test upload() returns True if media hash already exists.""" + content = b"media content" + metadata.set("atlos_id", 101) + media_hash = hashlib.sha256(content).hexdigest() + fake_get = FakeAPIResponse({ + "result": {"artifacts": [{"file_hash_sha256": media_hash}]} + }) + get_mock = mocker.patch("requests.get", return_value=fake_get) + post_mock = mocker.patch("requests.post") + result = atlos_storage.upload(media, metadata) + assert result is True + get_mock.assert_called_once() + post_mock.assert_not_called() + + +def test_upload_not_uploaded(tmp_path, atlos_storage: AtlosStorage, + metadata: Metadata, + media: Media, + mocker) -> None: + """Test upload() uploads media when not already present.""" + metadata.set("atlos_id", 202) + fake_get = FakeAPIResponse({ + "result": {"artifacts": [{"file_hash_sha256": "different_hash"}]} + }) + get_mock = mocker.patch("requests.get", return_value=fake_get) + fake_post = FakeAPIResponse({}, raise_error=False) + post_mock = mocker.patch("requests.post", return_value=fake_post) + result = atlos_storage.upload(media, metadata) + assert result is True + get_mock.assert_called_once() + post_mock.assert_called_once() + expected_url = f"{atlos_storage.atlos_url}/api/v2/source_material/upload/202" + expected_headers = {"Authorization": f"Bearer {atlos_storage.api_token}"} + expected_params = {"title": media.properties} + call_kwargs = post_mock.call_args.kwargs + assert call_kwargs["headers"] == expected_headers + assert call_kwargs["params"] == expected_params + # Verify the URL passed to requests.post. + posted_url = call_kwargs.get("url") or post_mock.call_args.args[0] + assert posted_url == expected_url + # Verify files parameter contains the correct filename. + file_tuple = call_kwargs["files"]["file"] + assert file_tuple[0] == os.path.basename(media.filename) + + +def test_upload_post_http_error(tmp_path, + atlos_storage: AtlosStorage, + metadata: Metadata, + media: Media, + mocker) -> None: + """Test upload() propagates HTTP error during POST.""" + metadata.set("atlos_id", 303) + fake_get = FakeAPIResponse({ + "result": {"artifacts": []} + }) + mocker.patch("requests.get", return_value=fake_get) + fake_post = FakeAPIResponse({}, raise_error=True) + mocker.patch("requests.post", return_value=fake_post) + with pytest.raises(Exception, match="HTTP error"): + atlos_storage.upload(media, metadata) + + +def test_uploadf_not_implemented(atlos_storage: AtlosStorage) -> None: + """Test uploadf() returns None (not implemented).""" + result = atlos_storage.uploadf(None, "dummy") + assert result is None From ddf2e7662498d4965fed371dffaf7d92a11a6b6d Mon Sep 17 00:00:00 2001 From: erinhmclark Date: Wed, 19 Feb 2025 09:24:34 +0000 Subject: [PATCH 050/160] Include Atlos Storage __init__.py for module recognition. --- src/auto_archiver/modules/atlos_storage/__init__.py | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/auto_archiver/modules/atlos_storage/__init__.py diff --git a/src/auto_archiver/modules/atlos_storage/__init__.py b/src/auto_archiver/modules/atlos_storage/__init__.py new file mode 100644 index 0000000..9e815c7 --- /dev/null +++ b/src/auto_archiver/modules/atlos_storage/__init__.py @@ -0,0 +1 @@ +from .atlos_storage import AtlosStorage \ No newline at end of file From eb60b271b91cbaabc65755dde9933dc0427ef82b Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Wed, 19 Feb 2025 10:28:35 +0000 Subject: [PATCH 051/160] Fix issue #200 --- src/auto_archiver/core/orchestrator.py | 35 ++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/src/auto_archiver/core/orchestrator.py b/src/auto_archiver/core/orchestrator.py index 208512a..f319e0d 100644 --- a/src/auto_archiver/core/orchestrator.py +++ b/src/auto_archiver/core/orchestrator.py @@ -95,6 +95,8 @@ class UniqueAppendAction(argparse.Action): class ArchivingOrchestrator: + setup_finished: bool = False + logger_id: int = None feeders: List[Type[Feeder]] extractors: List[Type[Extractor]] enrichers: List[Type[Enricher]] @@ -274,11 +276,18 @@ class ArchivingOrchestrator: def setup_logging(self, config): # setup loguru logging - logger.remove(0) # remove the default logger + try: + logger.remove(0) # remove the default logger + except ValueError: + pass + logging_config = config['logging'] - logger.add(sys.stderr, level=logging_config['level']) - if log_file := logging_config['file']: - logger.add(log_file) if not logging_config['rotation'] else logger.add(log_file, rotation=logging_config['rotation']) + + # add other logging info + if self.logger_id is None: # note - need direct comparison to None since need to consider falsy value 0 + self.logger_id = logger.add(sys.stderr, level=logging_config['level']) + if log_file := logging_config['file']: + logger.add(log_file) if not logging_config['rotation'] else logger.add(log_file, rotation=logging_config['rotation']) def install_modules(self, modules_by_type): """ @@ -359,7 +368,10 @@ class ArchivingOrchestrator: def setup_config(self, args: list) -> dict: """ Sets up the configuration file, merging the default config with the user's config + + This function should only ever be run once. """ + self.setup_basic_parser() # parse the known arguments for now (basically, we want the config file) @@ -378,8 +390,19 @@ class ArchivingOrchestrator: def setup(self, args: list): """ - Main entry point for the orchestrator, sets up the basic parser, loads the config file, and sets up the complete parser + Function to configure all setup of the orchestrator: setup configs and load modules. + + This method should only ever be called once """ + + if self.setup_finished: + logger.warning("The `setup_config()` function should only ever be run once. \ + If you need to re-run the setup, please re-instantiate a new instance of the orchestrator. \ + For code implementatations, you should call .setup_config() once then you may call .feed() \ + multiple times to archive multiple URLs.") + return + + self.setup_basic_parser() self.config = self.setup_config(args) logger.info(f"======== Welcome to the AUTO ARCHIVER ({__version__}) ==========") @@ -388,6 +411,8 @@ class ArchivingOrchestrator: # log out the modules that were loaded for module_type in BaseModule.MODULE_TYPES: logger.info(f"{module_type.upper()}S: " + ", ".join(m.display_name for m in getattr(self, f"{module_type}s"))) + + self.setup_finished = True def _command_line_run(self, args: list) -> Generator[Metadata]: """ From 222a94563f1f9ea5754a521aa391d033d260f47d Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Wed, 19 Feb 2025 10:29:05 +0000 Subject: [PATCH 052/160] WIP: Docs tidyups+add howto on logging and authentication (Authentication is WIP) --- docs/source/development/release.md | 2 +- docs/source/how_to.md | 46 +--------------- docs/source/how_to/authentication_how_to.md | 6 ++ docs/source/how_to/gsheets_setup.md | 44 +++++++++++++++ docs/source/how_to/logging.md | 55 +++++++++++++++++++ docs/source/index.md | 2 +- .../authentication.md | 39 +++++++++---- docs/source/installation/configurations.md | 2 +- docs/source/installation/installation.md | 1 + docs/source/modules/extractor.md | 2 +- src/auto_archiver/core/base_module.py | 35 +++++++++--- src/auto_archiver/core/config.py | 4 +- src/auto_archiver/core/orchestrator.py | 8 --- .../generic_extractor/generic_extractor.py | 7 ++- tests/extractors/test_generic_extractor.py | 2 +- 15 files changed, 173 insertions(+), 82 deletions(-) create mode 100644 docs/source/how_to/authentication_how_to.md create mode 100644 docs/source/how_to/gsheets_setup.md create mode 100644 docs/source/how_to/logging.md rename docs/source/{how_to => installation}/authentication.md (69%) diff --git a/docs/source/development/release.md b/docs/source/development/release.md index 6939e97..403dcb9 100644 --- a/docs/source/development/release.md +++ b/docs/source/development/release.md @@ -6,7 +6,7 @@ 1. Update the version number in [version.py](src/auto_archiver/version.py) 2. Go to github releases > new release > use `vx.y.z` for matching version notation 1. package is automatically updated in pypi - 2. docker image is automatically pushed to dockerhup + 2. docker image is automatically pushed to dockerhub diff --git a/docs/source/how_to.md b/docs/source/how_to.md index 25e1e1d..d8fe2e1 100644 --- a/docs/source/how_to.md +++ b/docs/source/how_to.md @@ -1,49 +1,6 @@ # How-To Guides -## How to use Google Sheets to load and store archive information -The `--gsheet_feeder.sheet` property is the name of the Google Sheet to check for URLs. -This sheet must have been shared with the Google Service account used by `gspread`. -This sheet must also have specific columns (case-insensitive) in the `header` - see the [Gsheet Feeder Docs](modules/autogen/feeder/gsheet_feeder.md) for more info. The default names of these columns and their purpose is: - -Inputs: - -* **Link** *(required)*: the URL of the post to archive -* **Destination folder**: custom folder for archived file (regardless of storage) - -Outputs: -* **Archive status** *(required)*: Status of archive operation -* **Archive location**: URL of archived post -* **Archive date**: Date archived -* **Thumbnail**: Embeds a thumbnail for the post in the spreadsheet -* **Timestamp**: Timestamp of original post -* **Title**: Post title -* **Text**: Post text -* **Screenshot**: Link to screenshot of post -* **Hash**: Hash of archived HTML file (which contains hashes of post media) - for checksums/verification -* **Perceptual Hash**: Perceptual hashes of found images - these can be used for de-duplication of content -* **WACZ**: Link to a WACZ web archive of post -* **ReplayWebpage**: Link to a ReplayWebpage viewer of the WACZ archive - -For example, this is a spreadsheet configured with all of the columns for the auto archiver and a few URLs to archive. (Note that the column names are not case sensitive.) - -![A screenshot of a Google Spreadsheet with column headers defined as above, and several Youtube and Twitter URLs in the "Link" column](../demo-before.png) - -Now the auto archiver can be invoked, with this command in this example: `docker run --rm -v $PWD/secrets:/app/secrets -v $PWD/local_archive:/app/local_archive bellingcat/auto-archiver:dockerize --config secrets/orchestration-global.yaml --gsheet_feeder.sheet "Auto archive test 2023-2"`. Note that the sheet name has been overridden/specified in the command line invocation. - -When the auto archiver starts running, it updates the "Archive status" column. - -![A screenshot of a Google Spreadsheet with column headers defined as above, and several Youtube and Twitter URLs in the "Link" column. The auto archiver has added "archive in progress" to one of the status columns.](../demo-progress.png) - -The links are downloaded and archived, and the spreadsheet is updated to the following: - -![A screenshot of a Google Spreadsheet with videos archived and metadata added per the description of the columns above.](../demo-after.png) - -Note that the first row is skipped, as it is assumed to be a header row (`--gsheet_feeder.header=1` and you can change it if you use more rows above). Rows with an empty URL column, or a non-empty archive column are also skipped. All sheets in the document will be checked. - -The "archive location" link contains the path of the archived file, in local storage, S3, or in Google Drive. - -![The archive result for a link in the demo sheet.](../demo-archive.png) - +The follow pages contain helpful how-to guides for comon use cases of the Auto-Archiver. --- ```{toctree} @@ -51,4 +8,5 @@ The "archive location" link contains the path of the archived file, in local sto :glob: how_to/* + ``` \ No newline at end of file diff --git a/docs/source/how_to/authentication_how_to.md b/docs/source/how_to/authentication_how_to.md new file mode 100644 index 0000000..ebf0f0c --- /dev/null +++ b/docs/source/how_to/authentication_how_to.md @@ -0,0 +1,6 @@ +# How to login (authenticate) to websites + +This how-to guide shows you how you can add authentication to Auto Archiver for a site you are trying to archive. In this example, we will authenticate on use Twitter/X.com using cookies, and on XXXX using username/password. + +```{note} This page is still under construction 🚧 +``` \ No newline at end of file diff --git a/docs/source/how_to/gsheets_setup.md b/docs/source/how_to/gsheets_setup.md new file mode 100644 index 0000000..f013534 --- /dev/null +++ b/docs/source/how_to/gsheets_setup.md @@ -0,0 +1,44 @@ +# Using Google Sheets + +The `--gsheet_feeder.sheet` property is the name of the Google Sheet to check for URLs. +This sheet must have been shared with the Google Service account used by `gspread`. +This sheet must also have specific columns (case-insensitive) in the `header` - see the [Gsheet Feeder Docs](modules/autogen/feeder/gsheet_feeder.md) for more info. The default names of these columns and their purpose is: + +Inputs: + +* **Link** *(required)*: the URL of the post to archive +* **Destination folder**: custom folder for archived file (regardless of storage) + +Outputs: +* **Archive status** *(required)*: Status of archive operation +* **Archive location**: URL of archived post +* **Archive date**: Date archived +* **Thumbnail**: Embeds a thumbnail for the post in the spreadsheet +* **Timestamp**: Timestamp of original post +* **Title**: Post title +* **Text**: Post text +* **Screenshot**: Link to screenshot of post +* **Hash**: Hash of archived HTML file (which contains hashes of post media) - for checksums/verification +* **Perceptual Hash**: Perceptual hashes of found images - these can be used for de-duplication of content +* **WACZ**: Link to a WACZ web archive of post +* **ReplayWebpage**: Link to a ReplayWebpage viewer of the WACZ archive + +For example, this is a spreadsheet configured with all of the columns for the auto archiver and a few URLs to archive. (Note that the column names are not case sensitive.) + +![A screenshot of a Google Spreadsheet with column headers defined as above, and several Youtube and Twitter URLs in the "Link" column](../demo-before.png) + +Now the auto archiver can be invoked, with this command in this example: `docker run --rm -v $PWD/secrets:/app/secrets -v $PWD/local_archive:/app/local_archive bellingcat/auto-archiver:dockerize --config secrets/orchestration-global.yaml --gsheet_feeder.sheet "Auto archive test 2023-2"`. Note that the sheet name has been overridden/specified in the command line invocation. + +When the auto archiver starts running, it updates the "Archive status" column. + +![A screenshot of a Google Spreadsheet with column headers defined as above, and several Youtube and Twitter URLs in the "Link" column. The auto archiver has added "archive in progress" to one of the status columns.](../demo-progress.png) + +The links are downloaded and archived, and the spreadsheet is updated to the following: + +![A screenshot of a Google Spreadsheet with videos archived and metadata added per the description of the columns above.](../demo-after.png) + +Note that the first row is skipped, as it is assumed to be a header row (`--gsheet_feeder.header=1` and you can change it if you use more rows above). Rows with an empty URL column, or a non-empty archive column are also skipped. All sheets in the document will be checked. + +The "archive location" link contains the path of the archived file, in local storage, S3, or in Google Drive. + +![The archive result for a link in the demo sheet.](../demo-archive.png) diff --git a/docs/source/how_to/logging.md b/docs/source/how_to/logging.md new file mode 100644 index 0000000..7ae3b18 --- /dev/null +++ b/docs/source/how_to/logging.md @@ -0,0 +1,55 @@ +# Logging + +Auto Archiver's logs can be helpful for debugging problematic archiving processes. This guide shows you how to use the logs to + +## Setting up logging + +Logging settings can be set on the command line or using the orchestration config file ([learn more](../installation/configuration)). A special `logging` section defines the logging options. + +#### Logging Level + +There are 7 logging levels in total, with 4 commonly used levels. They are: `DEBUG`, `INFO`, `WARNING` and `ERROR`. + +Change the warning level by setting the value in your orchestration config file: + +```{code} yaml +:caption: orchestration.yaml + +... +logging: + level: DEBUG # or INFO / WARNING / ERROR +... +``` + +For normal usage, it is recommended to use the `INFO` level, or if you prefer quieter logs with less information, you can use the `WARNING` level. If you encounter issues with the archiving, then it's recommended to enable the `DEBUG` level. + +```{note} To learn about all logging levels, see the [loguru documentation](https://loguru.readthedocs.io/en/stable/api/logger.html) +``` + +### Logging to a file + +As default, auto-archiver will log to the console. But if you wish to store your logs for future reference, or you are running the auto-archiver from within code a implementation, then you may with to enable file logging. This can be done by setting the `file:` config value in the logging settings. + +**Rotation:** For file logging, you can choose to 'rotate' your log files (creating new log files) so they do not get too large. Change this by setting the 'rotation' option in your logging settings. For a full list of rotation options, see the [loguru docs](https://loguru.readthedocs.io/en/stable/overview.html#easier-file-logging-with-rotation-retention-compression). + +```{code} yaml +:caption: orchestration.yaml + +logging: + ... + file: /my/log/file.log + rotation: 1 day +``` + +### Full logging example + +The below example logs only `WARNING` logs to the console and to the file `/my/file.log`, rotating that file once per week: + +```{code} yaml +:caption: orchestration.yaml + +logging: + level: WARNING + file: /my/file.log + rotation: 1 week +``` \ No newline at end of file diff --git a/docs/source/index.md b/docs/source/index.md index 6a7f769..53185ee 100644 --- a/docs/source/index.md +++ b/docs/source/index.md @@ -9,7 +9,7 @@ Overview contributing -installation/installation.rst +installation/installation core_modules.md how_to development/developer_guidelines diff --git a/docs/source/how_to/authentication.md b/docs/source/installation/authentication.md similarity index 69% rename from docs/source/how_to/authentication.md rename to docs/source/installation/authentication.md index 5f3bc48..be30425 100644 --- a/docs/source/how_to/authentication.md +++ b/docs/source/installation/authentication.md @@ -4,22 +4,42 @@ The Authentication framework for auto-archiver allows you to add login details f There are two main use cases for authentication: * Some websites require some kind of authentication in order to view the content. Examples include Facebook, Telegram etc. -* Some websites use anti-bot systems to block bot-like tools from accessig the website. Adding real login information to auto-archiver can sometimes bypass this. +* Some websites use anti-bot systems to block bot-like tools from accessing the website. Adding real login information to auto-archiver can sometimes bypass this. ## The Authentication Config -You can save your authentication information directly inside your orchestration config file, or as a separate file (for security/multi-deploy purposes). Whether storing your settings inside the orchestration file, or as a separate file, the configuration format is the same. +You can save your authentication information directly inside your orchestration config file, or as a separate file (for security/multi-deploy purposes). Whether storing your settings inside the orchestration file, or as a separate file, the configuration format is the same. Currently, auto-archiver supports the following authentication types: + +**Username & Password:** +- `username`: str - the username to use for login +- `password`: str - the password to use for login + +**API** +- `api_key`: str - the API key to use for login +- `api_secret`: str - the API secret to use for login + +**Cookies** +- `cookie`: str - a cookie string to use for login (specific to this site) +- `cookies_from_browser`: str - load cookies from this browser, for this site only. +- `cookies_file`: str - load cookies from this file, for this site only. + +```{note} + +The Username & Password, and API settings only work with the Generic Extractor. Other modules (like the screenshot enricher) can only use the `cookies` options. Furthermore, many sites can still detect bots and block username/password logins. Twitter/X and YouTube are two prominent ones that block username/password logging. + +One of the 'Cookies' options is recommended for the most robust archiving. +``` ```{code} yaml authentication: # optional file to load authentication information from, for security or multi-system deploy purposes load_from_file: path/to/authentication/file.txt - # optional setting to load cookies from the named browser on the system. + # optional setting to load cookies from the named browser on the system, for **ALL** websites cookies_from_browser: firefox - # optional setting to load cookies from a cookies.txt/cookies.jar file. See note below on extracting these + # optional setting to load cookies from a cookies.txt/cookies.jar file, for **ALL** websites. See note below on extracting these cookies_file: path/to/cookies.jar - twitter.com,x.com: + mysite.com: username: myusername password: 123 @@ -29,15 +49,10 @@ authentication: othersite.com: api_key: 123 api_secret: 1234 - -# All available options: - # - username: str - the username to use for login - # - password: str - the password to use for login - # - api_key: str - the API key to use for login - # - api_secret: str - the API secret to use for login - # - cookie: str - a cookie string to use for login (specific to this site) + ``` + ### Recommendations for authentication 1. **Store authentication information separately:** diff --git a/docs/source/installation/configurations.md b/docs/source/installation/configurations.md index 705b6c5..3e9cd08 100644 --- a/docs/source/installation/configurations.md +++ b/docs/source/installation/configurations.md @@ -23,7 +23,7 @@ A default `orchestration.yaml` will be created for you the first time you run au ## Configuring from the Command Line -You can run auto-archiver directy from the command line, without the need for a configuration file, command line arguments are parsed using the format `module_name.config_value`. For example, a config value of `api_key` in the `instagram_extractor` module would be passed on the command line with the flag `--instagram_extractor.api_key=API_KEY`. +You can run auto-archiver directly from the command line, without the need for a configuration file, command line arguments are parsed using the format `module_name.config_value`. For example, a config value of `api_key` in the `instagram_extractor` module would be passed on the command line with the flag `--instagram_extractor.api_key=API_KEY`. The command line arguments are useful for testing or editing config values and enabling/disabling modules on the fly. When you are happy with your settings, you can store them back in your configuration file by passing the `-s/--store` flag on the command line. diff --git a/docs/source/installation/installation.md b/docs/source/installation/installation.md index fdd3184..6f2e9d4 100644 --- a/docs/source/installation/installation.md +++ b/docs/source/installation/installation.md @@ -5,6 +5,7 @@ :hidden: configurations.md +authentication.md config_cheatsheet.md ``` diff --git a/docs/source/modules/extractor.md b/docs/source/modules/extractor.md index 7f218fb..2fb0fef 100644 --- a/docs/source/modules/extractor.md +++ b/docs/source/modules/extractor.md @@ -4,7 +4,7 @@ Extractor modules are used to extract the content of a given URL. Typically, one Extractors that are able to extract content from a wide range of websites include: 1. Generic Extractor: parses videos and images on sites using the powerful yt-dlp library. -2. Wayback Machine Extractor: sends pages to the Waygback machine for archiving, and stores the link. +2. Wayback Machine Extractor: sends pages to the Wayback machine for archiving, and stores the link. 3. WACZ Extractor: runs a web browser to 'browse' the URL and save a copy of the page in WACZ format. ```{include} autogen/extractor.md diff --git a/src/auto_archiver/core/base_module.py b/src/auto_archiver/core/base_module.py index dfdd5ad..1586585 100644 --- a/src/auto_archiver/core/base_module.py +++ b/src/auto_archiver/core/base_module.py @@ -63,12 +63,18 @@ class BaseModule(ABC): def config_setup(self, config: dict): authentication = config.get('authentication', {}) - # this is important. Each instance is given its own deepcopied config, so modules cannot # change values to affect other modules config = deepcopy(config) authentication = deepcopy(config.pop('authentication', {})) + # extract out concatenated sites + for key, val in copy(authentication).items(): + if "," in key: + for site in key.split(","): + authentication[site] = val + del authentication[key] + self.authentication = authentication self.config = config for key, val in config.get(self.name, {}).items(): @@ -102,7 +108,7 @@ class BaseModule(ABC): # TODO: think about if/how we can deal with sites that have multiple domains (main one is x.com/twitter.com) # for now the user must enter them both, like "x.com,twitter.com" in their config. Maybe we just hard-code? - site = UrlUtil.domain_for_url(site) + site = UrlUtil.domain_for_url(site).lstrip("www.") # add the 'www' version of the site to the list of sites to check authdict = {} @@ -128,17 +134,30 @@ class BaseModule(ABC): # collections.namedtuple('ParsedOptions', ('parser', 'options', 'urls', 'ydl_opts')) ytdlp_opts = getattr(parse_options(args), 'ydl_opts') return yt_dlp.YoutubeDL(ytdlp_opts).cookiejar + + get_cookiejar_options = None - # get the cookies jar, prefer the browser cookies than the file - if 'cookies_from_browser' in self.authentication: + # order of priority: + # 1. cookies_from_browser setting in site config + # 2. cookies_file setting in site config + # 3. cookies_from_browser setting in global config + # 4. cookies_file setting in global config + + if 'cookies_from_browser' in authdict: + get_cookiejar_options = ['--cookies-from-browser', authdict['cookies_from_browser']] + elif 'cookies_file' in authdict: + get_cookiejar_options = ['--cookies', authdict['cookies_file']] + elif 'cookies_from_browser' in self.authentication: authdict['cookies_from_browser'] = self.authentication['cookies_from_browser'] - if extract_cookies: - authdict['cookies_jar'] = get_ytdlp_cookiejar(['--cookies-from-browser', self.authentication['cookies_from_browser']]) + get_cookiejar_options = ['--cookies-from-browser', self.authentication['cookies_from_browser']] elif 'cookies_file' in self.authentication: authdict['cookies_file'] = self.authentication['cookies_file'] - if extract_cookies: - authdict['cookies_jar'] = get_ytdlp_cookiejar(['--cookies', self.authentication['cookies_file']]) + get_cookiejar_options = ['--cookies', self.authentication['cookies_file']] + + if get_cookiejar_options: + authdict['cookies_jar'] = get_ytdlp_cookiejar(get_cookiejar_options) + return authdict def repr(self): diff --git a/src/auto_archiver/core/config.py b/src/auto_archiver/core/config.py index 322ef6e..d335959 100644 --- a/src/auto_archiver/core/config.py +++ b/src/auto_archiver/core/config.py @@ -10,7 +10,7 @@ from ruamel.yaml import YAML, CommentedMap, add_representer from loguru import logger -from copy import deepcopy +from copy import deepcopy, copy from .module import BaseModule from typing import Any, List, Type, Tuple @@ -154,7 +154,7 @@ def read_yaml(yaml_filename: str) -> CommentedMap: if not config: config = EMPTY_CONFIG - + return config # TODO: make this tidier/find a way to notify of which keys should not be stored diff --git a/src/auto_archiver/core/orchestrator.py b/src/auto_archiver/core/orchestrator.py index 208512a..aa405dd 100644 --- a/src/auto_archiver/core/orchestrator.py +++ b/src/auto_archiver/core/orchestrator.py @@ -8,7 +8,6 @@ from __future__ import annotations from typing import Generator, Union, List, Type from urllib.parse import urlparse from ipaddress import ip_address -from copy import copy import argparse import os import sys @@ -75,13 +74,6 @@ class AuthenticationJsonParseAction(JsonParseAction): continue if not isinstance(key, str) or not isinstance(auth, dict): raise argparse.ArgumentTypeError(f"Authentication must be a dictionary of site names and their authentication methods. Valid global configs are {global_options}") - - # extract out concatenated sites - for key, val in copy(auth_dict).items(): - if "," in key: - for site in key.split(","): - auth_dict[site] = val - del auth_dict[key] setattr(namespace, self.dest, auth_dict) diff --git a/src/auto_archiver/modules/generic_extractor/generic_extractor.py b/src/auto_archiver/modules/generic_extractor/generic_extractor.py index 6bcb249..7a627ac 100644 --- a/src/auto_archiver/modules/generic_extractor/generic_extractor.py +++ b/src/auto_archiver/modules/generic_extractor/generic_extractor.py @@ -280,6 +280,7 @@ class GenericExtractor(Extractor): # set up auth auth = self.auth_for_site(url, extract_cookies=False) + # order of importance: username/pasword -> api_key -> cookie -> cookie_from_browser -> cookies_file if auth: if 'username' in auth and 'password' in auth: @@ -290,11 +291,11 @@ class GenericExtractor(Extractor): logger.debug(f'Using provided auth cookie for {url}') yt_dlp.utils.std_headers['cookie'] = auth['cookie'] elif 'cookie_from_browser' in auth: - logger.debug(f'Using extracted cookies from browser {self.cookies_from_browser} for {url}') + logger.debug(f'Using extracted cookies from browser {auth["cookies_from_browser"]} for {url}') ydl_options['cookiesfrombrowser'] = auth['cookies_from_browser'] elif 'cookies_file' in auth: - logger.debug(f'Using cookies from file {self.cookie_file} for {url}') - ydl_options['cookiesfile'] = auth['cookies_file'] + logger.debug(f'Using cookies from file {auth["cookies_file"]} for {url}') + ydl_options['cookiefile'] = auth['cookies_file'] ydl = yt_dlp.YoutubeDL(ydl_options) # allsubtitles and subtitleslangs not working as expected, so default lang is always "en" diff --git a/tests/extractors/test_generic_extractor.py b/tests/extractors/test_generic_extractor.py index 54f4d9c..33f35b7 100644 --- a/tests/extractors/test_generic_extractor.py +++ b/tests/extractors/test_generic_extractor.py @@ -68,7 +68,7 @@ class TestGenericExtractor(TestExtractorBase): "twitter.com/bellingcat/status/123", "https://www.youtube.com/watch?v=1" ]) - def test_download_nonexistend_media(self, make_item, url): + def test_download_nonexistent_media(self, make_item, url): """ Test to make sure that the extractor doesn't break on non-existend posts/media From a8ffb193254323d0734b1268fdc6541804968846 Mon Sep 17 00:00:00 2001 From: erinhmclark Date: Wed, 19 Feb 2025 10:40:54 +0000 Subject: [PATCH 053/160] Fix auth key name for cookies_from_browser. --- .../modules/generic_extractor/generic_extractor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/auto_archiver/modules/generic_extractor/generic_extractor.py b/src/auto_archiver/modules/generic_extractor/generic_extractor.py index 6bcb249..72fe3e0 100644 --- a/src/auto_archiver/modules/generic_extractor/generic_extractor.py +++ b/src/auto_archiver/modules/generic_extractor/generic_extractor.py @@ -280,7 +280,7 @@ class GenericExtractor(Extractor): # set up auth auth = self.auth_for_site(url, extract_cookies=False) - # order of importance: username/pasword -> api_key -> cookie -> cookie_from_browser -> cookies_file + # order of importance: username/pasword -> api_key -> cookie -> cookies_from_browser -> cookies_file if auth: if 'username' in auth and 'password' in auth: logger.debug(f'Using provided auth username and password for {url}') @@ -289,7 +289,7 @@ class GenericExtractor(Extractor): elif 'cookie' in auth: logger.debug(f'Using provided auth cookie for {url}') yt_dlp.utils.std_headers['cookie'] = auth['cookie'] - elif 'cookie_from_browser' in auth: + elif 'cookies_from_browser' in auth: logger.debug(f'Using extracted cookies from browser {self.cookies_from_browser} for {url}') ydl_options['cookiesfrombrowser'] = auth['cookies_from_browser'] elif 'cookies_file' in auth: From a9802dd004336ede086e58aac679ae8920de112c Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Wed, 19 Feb 2025 12:25:35 +0000 Subject: [PATCH 054/160] Remove the global _LAZY_LOADED_MODULES and allow each instance of ArchivingOrchestrator to load its own modules --- docs/scripts/scripts.py | 4 +- src/auto_archiver/core/__init__.py | 2 +- src/auto_archiver/core/base_module.py | 36 ++-- src/auto_archiver/core/config.py | 4 +- src/auto_archiver/core/consts.py | 23 ++ src/auto_archiver/core/module.py | 203 ++++++++++-------- src/auto_archiver/core/orchestrator.py | 45 ++-- src/auto_archiver/core/storage.py | 4 +- .../modules/html_formatter/html_formatter.py | 3 +- .../whisper_enricher/whisper_enricher.py | 3 +- tests/conftest.py | 10 +- tests/enrichers/test_hash_enricher.py | 4 +- tests/test_modules.py | 26 +-- tests/test_orchestrator.py | 19 +- 14 files changed, 203 insertions(+), 183 deletions(-) create mode 100644 src/auto_archiver/core/consts.py diff --git a/docs/scripts/scripts.py b/docs/scripts/scripts.py index 9712439..a5f2998 100644 --- a/docs/scripts/scripts.py +++ b/docs/scripts/scripts.py @@ -1,6 +1,6 @@ # iterate through all the modules in auto_archiver.modules and turn the __manifest__.py file into a markdown table from pathlib import Path -from auto_archiver.core.module import available_modules +from auto_archiver.core.module import ModuleFactory from auto_archiver.core.base_module import BaseModule from ruamel.yaml import YAML import io @@ -41,7 +41,7 @@ def generate_module_docs(): configs_cheatsheet = "\n## Configuration Options\n" configs_cheatsheet += header_row - for module in sorted(available_modules(with_manifest=True), key=lambda x: (x.requires_setup, x.name)): + for module in sorted(ModuleFactory().available_modules(), key=lambda x: (x.requires_setup, x.name)): # generate the markdown file from the __manifest__.py file. manifest = module.manifest diff --git a/src/auto_archiver/core/__init__.py b/src/auto_archiver/core/__init__.py index ae4c41c..78d9a3d 100644 --- a/src/auto_archiver/core/__init__.py +++ b/src/auto_archiver/core/__init__.py @@ -3,7 +3,7 @@ """ from .metadata import Metadata from .media import Media -from .module import BaseModule +from .base_module import BaseModule # cannot import ArchivingOrchestrator/Config to avoid circular dep # from .orchestrator import ArchivingOrchestrator diff --git a/src/auto_archiver/core/base_module.py b/src/auto_archiver/core/base_module.py index dfdd5ad..50ea3ff 100644 --- a/src/auto_archiver/core/base_module.py +++ b/src/auto_archiver/core/base_module.py @@ -1,13 +1,18 @@ -from urllib.parse import urlparse -from typing import Mapping, Any +from __future__ import annotations + +from typing import Mapping, Any, Type, TYPE_CHECKING from abc import ABC from copy import deepcopy, copy from tempfile import TemporaryDirectory from auto_archiver.utils import url as UrlUtil +from auto_archiver.core.consts import MODULE_TYPES as CONF_MODULE_TYPES from loguru import logger +if TYPE_CHECKING: + from .module import ModuleFactory + class BaseModule(ABC): """ @@ -17,41 +22,24 @@ class BaseModule(ABC): however modules can have a .setup() method to run any setup code (e.g. logging in to a site, spinning up a browser etc.) - See BaseModule.MODULE_TYPES for the types of modules you can create, noting that + See consts.MODULE_TYPES for the types of modules you can create, noting that a subclass can be of multiple types. For example, a module that extracts data from a website and stores it in a database would be both an 'extractor' and a 'database' module. Each module is a python package, and should have a __manifest__.py file in the same directory as the module file. The __manifest__.py specifies the module information - like name, author, version, dependencies etc. See BaseModule._DEFAULT_MANIFEST for the + like name, author, version, dependencies etc. See DEFAULT_MANIFEST for the default manifest structure. """ - MODULE_TYPES = [ - 'feeder', - 'extractor', - 'enricher', - 'database', - 'storage', - 'formatter' - ] - - _DEFAULT_MANIFEST = { - 'name': '', # the display name of the module - 'author': 'Bellingcat', # creator of the module, leave this as Bellingcat or set your own name! - 'type': [], # the type of the module, can be one or more of BaseModule.MODULE_TYPES - 'requires_setup': True, # whether or not this module requires additional setup such as setting API Keys or installing additional softare - 'description': '', # a description of the module - 'dependencies': {}, # external dependencies, e.g. python packages or binaries, in dictionary format - 'entry_point': '', # the entry point for the module, in the format 'module_name::ClassName'. This can be left blank to use the default entry point of module_name::ModuleName - 'version': '1.0', # the version of the module - 'configs': {} # any configuration options this module has, these will be exposed to the user in the config file or via the command line -} + MODULE_TYPES = CONF_MODULE_TYPES + # NOTE: these here are declard as class variables, but they are overridden by the instance variables in the __init__ method config: Mapping[str, Any] authentication: Mapping[str, Mapping[str, str]] name: str + module_factory: ModuleFactory # this is set by the orchestrator prior to archiving tmp_dir: TemporaryDirectory = None diff --git a/src/auto_archiver/core/config.py b/src/auto_archiver/core/config.py index 322ef6e..c3bc706 100644 --- a/src/auto_archiver/core/config.py +++ b/src/auto_archiver/core/config.py @@ -11,7 +11,7 @@ from ruamel.yaml import YAML, CommentedMap, add_representer from loguru import logger from copy import deepcopy -from .module import BaseModule +from auto_archiver.core.consts import MODULE_TYPES from typing import Any, List, Type, Tuple @@ -21,7 +21,7 @@ EMPTY_CONFIG = _yaml.load(""" # Auto Archiver Configuration # Steps are the modules that will be run in the order they are defined -steps:""" + "".join([f"\n {module}s: []" for module in BaseModule.MODULE_TYPES]) + \ +steps:""" + "".join([f"\n {module}s: []" for module in MODULE_TYPES]) + \ """ # Global configuration diff --git a/src/auto_archiver/core/consts.py b/src/auto_archiver/core/consts.py new file mode 100644 index 0000000..0fb81fb --- /dev/null +++ b/src/auto_archiver/core/consts.py @@ -0,0 +1,23 @@ + +MODULE_TYPES = [ + 'feeder', + 'extractor', + 'enricher', + 'database', + 'storage', + 'formatter' +] + +MANIFEST_FILE = "__manifest__.py" + +DEFAULT_MANIFEST = { + 'name': '', # the display name of the module + 'author': 'Bellingcat', # creator of the module, leave this as Bellingcat or set your own name! + 'type': [], # the type of the module, can be one or more of MODULE_TYPES + 'requires_setup': True, # whether or not this module requires additional setup such as setting API Keys or installing additional softare + 'description': '', # a description of the module + 'dependencies': {}, # external dependencies, e.g. python packages or binaries, in dictionary format + 'entry_point': '', # the entry point for the module, in the format 'module_name::ClassName'. This can be left blank to use the default entry point of module_name::ModuleName + 'version': '1.0', # the version of the module + 'configs': {} # any configuration options this module has, these will be exposed to the user in the config file or via the command line +} \ No newline at end of file diff --git a/src/auto_archiver/core/module.py b/src/auto_archiver/core/module.py index c81e26a..9556621 100644 --- a/src/auto_archiver/core/module.py +++ b/src/auto_archiver/core/module.py @@ -6,7 +6,7 @@ by handling user configuration, validating the steps properties, and implementin from __future__ import annotations from dataclasses import dataclass -from typing import List +from typing import List, TYPE_CHECKING import shutil import ast import copy @@ -16,99 +16,113 @@ import os from os.path import join from loguru import logger import auto_archiver -from .base_module import BaseModule +from auto_archiver.core.consts import DEFAULT_MANIFEST, MANIFEST_FILE -_LAZY_LOADED_MODULES = {} - -MANIFEST_FILE = "__manifest__.py" +if TYPE_CHECKING: + from .base_module import BaseModule -def setup_paths(paths: list[str]) -> None: - """ - Sets up the paths for the modules to be loaded from - - This is necessary for the modules to be imported correctly - - """ - for path in paths: - # check path exists, if it doesn't, log a warning - if not os.path.exists(path): - logger.warning(f"Path '{path}' does not exist. Skipping...") - continue +HAS_SETUP_PATHS = False - # see odoo/module/module.py -> initialize_sys_path - if path not in auto_archiver.modules.__path__: - auto_archiver.modules.__path__.append(path) +class ModuleFactory: - # sort based on the length of the path, so that the longest path is last in the list - auto_archiver.modules.__path__ = sorted(auto_archiver.modules.__path__, key=len, reverse=True) + def __init__(self): + self._lazy_modules = {} -def get_module(module_name: str, config: dict) -> BaseModule: - """ - Gets and sets up a module using the provided config - - This will actually load and instantiate the module, and load all its dependencies (i.e. not lazy) - - """ - return get_module_lazy(module_name).load(config) + def setup_paths(self, paths: list[str]) -> None: + """ + Sets up the paths for the modules to be loaded from + + This is necessary for the modules to be imported correctly + + """ + global HAS_SETUP_PATHS -def get_module_lazy(module_name: str, suppress_warnings: bool = False) -> LazyBaseModule: - """ - Lazily loads a module, returning a LazyBaseModule - - This has all the information about the module, but does not load the module itself or its dependencies - - To load an actual module, call .setup() on a lazy module - - """ - if module_name in _LAZY_LOADED_MODULES: - return _LAZY_LOADED_MODULES[module_name] - - available = available_modules(limit_to_modules=[module_name], suppress_warnings=suppress_warnings) - if not available: - raise IndexError(f"Module '{module_name}' not found. Are you sure it's installed/exists?") - return available[0] - -def available_modules(with_manifest: bool=False, limit_to_modules: List[str]= [], suppress_warnings: bool = False) -> List[LazyBaseModule]: - - # search through all valid 'modules' paths. Default is 'modules' in the current directory - - # see odoo/modules/module.py -> get_modules - def is_really_module(module_path): - if os.path.isfile(join(module_path, MANIFEST_FILE)): - return True - - all_modules = [] - - for module_folder in auto_archiver.modules.__path__: - # walk through each module in module_folder and check if it has a valid manifest - try: - possible_modules = os.listdir(module_folder) - except FileNotFoundError: - logger.warning(f"Module folder {module_folder} does not exist") - continue - - for possible_module in possible_modules: - if limit_to_modules and possible_module not in limit_to_modules: + for path in paths: + # check path exists, if it doesn't, log a warning + if not os.path.exists(path): + logger.warning(f"Path '{path}' does not exist. Skipping...") continue - possible_module_path = join(module_folder, possible_module) - if not is_really_module(possible_module_path): + # see odoo/module/module.py -> initialize_sys_path + if path not in auto_archiver.modules.__path__: + if HAS_SETUP_PATHS == True: + logger.warning(f"You are attempting to re-initialise the module paths with: '{path}' for a 2nd time. \ + This could lead to unexpected behaviour. It is recommended to only use a single modules path. \ + If you wish to load modules from different paths then load a 2nd python interpreter (e.g. using multiprocessing).") + auto_archiver.modules.__path__.append(path) + + # sort based on the length of the path, so that the longest path is last in the list + auto_archiver.modules.__path__ = sorted(auto_archiver.modules.__path__, key=len, reverse=True) + + HAS_SETUP_PATHS = True + + def get_module(self, module_name: str, config: dict) -> BaseModule: + """ + Gets and sets up a module using the provided config + + This will actually load and instantiate the module, and load all its dependencies (i.e. not lazy) + + """ + return self.get_module_lazy(module_name).load(config) + + def get_module_lazy(self, module_name: str, suppress_warnings: bool = False) -> LazyBaseModule: + """ + Lazily loads a module, returning a LazyBaseModule + + This has all the information about the module, but does not load the module itself or its dependencies + + To load an actual module, call .setup() on a lazy module + + """ + if module_name in self._lazy_modules: + return self._lazy_modules[module_name] + + available = self.available_modules(limit_to_modules=[module_name], suppress_warnings=suppress_warnings) + if not available: + raise IndexError(f"Module '{module_name}' not found. Are you sure it's installed/exists?") + return available[0] + + def available_modules(self, limit_to_modules: List[str]= [], suppress_warnings: bool = False) -> List[LazyBaseModule]: + + # search through all valid 'modules' paths. Default is 'modules' in the current directory + + # see odoo/modules/module.py -> get_modules + def is_really_module(module_path): + if os.path.isfile(join(module_path, MANIFEST_FILE)): + return True + + all_modules = [] + + for module_folder in auto_archiver.modules.__path__: + # walk through each module in module_folder and check if it has a valid manifest + try: + possible_modules = os.listdir(module_folder) + except FileNotFoundError: + logger.warning(f"Module folder {module_folder} does not exist") continue - if _LAZY_LOADED_MODULES.get(possible_module): - continue - lazy_module = LazyBaseModule(possible_module, possible_module_path) - _LAZY_LOADED_MODULES[possible_module] = lazy_module + for possible_module in possible_modules: + if limit_to_modules and possible_module not in limit_to_modules: + continue - all_modules.append(lazy_module) - - if not suppress_warnings: - for module in limit_to_modules: - if not any(module == m.name for m in all_modules): - logger.warning(f"Module '{module}' not found. Are you sure it's installed?") + possible_module_path = join(module_folder, possible_module) + if not is_really_module(possible_module_path): + continue + if self._lazy_modules.get(possible_module): + continue + lazy_module = LazyBaseModule(possible_module, possible_module_path, factory=self) - return all_modules + self._lazy_modules[possible_module] = lazy_module + + all_modules.append(lazy_module) + + if not suppress_warnings: + for module in limit_to_modules: + if not any(module == m.name for m in all_modules): + logger.warning(f"Module '{module}' not found. Are you sure it's installed?") + + return all_modules @dataclass class LazyBaseModule: @@ -123,14 +137,16 @@ class LazyBaseModule: type: list description: str path: str + module_factory: ModuleFactory _manifest: dict = None _instance: BaseModule = None _entry_point: str = None - def __init__(self, module_name, path): + def __init__(self, module_name, path, factory: ModuleFactory): self.name = module_name self.path = path + self.module_factory = factory @property def entry_point(self): @@ -161,7 +177,7 @@ class LazyBaseModule: return self._manifest # print(f"Loading manifest for module {module_path}") # load the manifest file - manifest = copy.deepcopy(BaseModule._DEFAULT_MANIFEST) + manifest = copy.deepcopy(DEFAULT_MANIFEST) with open(join(self.path, MANIFEST_FILE)) as f: try: @@ -189,13 +205,14 @@ class LazyBaseModule: # clear out any empty strings that a user may have erroneously added continue if not check(dep): - logger.error(f"Module '{self.name}' requires external dependency '{dep}' which is not available/setup. Have you installed the required dependencies for the '{self.name}' module? See the README for more information.") + logger.error(f"Module '{self.name}' requires external dependency '{dep}' which is not available/setup. \ + Have you installed the required dependencies for the '{self.name}' module? See the README for more information.") exit(1) def check_python_dep(dep): # first check if it's a module: try: - m = get_module_lazy(dep, suppress_warnings=True) + m = self.module_factory.get_module_lazy(dep, suppress_warnings=True) try: # we must now load this module and set it up with the config m.load(config) @@ -230,19 +247,21 @@ class LazyBaseModule: __import__(f'{qualname}.{file_name}', fromlist=[self.entry_point]) # finally, get the class instance instance: BaseModule = getattr(sys.modules[sub_qualname], class_name)() - if not getattr(instance, 'name', None): - instance.name = self.name - - if not getattr(instance, 'display_name', None): - instance.display_name = self.display_name - - self._instance = instance + # set the name, display name and module factory + instance.name = self.name + instance.display_name = self.display_name + instance.module_factory = self.module_factory + # merge the default config with the user config default_config = dict((k, v['default']) for k, v in self.configs.items() if v.get('default')) + config[self.name] = default_config | config.get(self.name, {}) instance.config_setup(config) instance.setup() + + # save the instance for future easy loading + self._instance = instance return instance def __repr__(self): diff --git a/src/auto_archiver/core/orchestrator.py b/src/auto_archiver/core/orchestrator.py index f319e0d..10d9215 100644 --- a/src/auto_archiver/core/orchestrator.py +++ b/src/auto_archiver/core/orchestrator.py @@ -5,7 +5,7 @@ """ from __future__ import annotations -from typing import Generator, Union, List, Type +from typing import Generator, Union, List, Type, TYPE_CHECKING from urllib.parse import urlparse from ipaddress import ip_address from copy import copy @@ -22,12 +22,14 @@ from rich_argparse import RichHelpFormatter from .metadata import Metadata, Media from auto_archiver.version import __version__ from .config import _yaml, read_yaml, store_yaml, to_dot_notation, merge_dicts, EMPTY_CONFIG, DefaultValidatingParser -from .module import available_modules, LazyBaseModule, get_module, setup_paths +from .module import ModuleFactory, LazyBaseModule from . import validators, Feeder, Extractor, Database, Storage, Formatter, Enricher -from .module import BaseModule - +from .consts import MODULE_TYPES from loguru import logger +if TYPE_CHECKING: + from .base_module import BaseModule + from .module import LazyBaseModule DEFAULT_CONFIG_FILE = "orchestration.yaml" @@ -95,8 +97,12 @@ class UniqueAppendAction(argparse.Action): class ArchivingOrchestrator: - setup_finished: bool = False - logger_id: int = None + # instance variables + module_factory: ModuleFactory + setup_finished: bool + logger_id: int + + # instance variables, used for convenience to access modules by step feeders: List[Type[Feeder]] extractors: List[Type[Extractor]] enrichers: List[Type[Enricher]] @@ -104,6 +110,11 @@ class ArchivingOrchestrator: storages: List[Type[Storage]] formatters: List[Type[Formatter]] + def __init__(self): + self.module_factory = ModuleFactory() + self.setup_finished = False + self.logger_id = None + def setup_basic_parser(self): parser = argparse.ArgumentParser( prog="auto-archiver", @@ -135,7 +146,7 @@ class ArchivingOrchestrator: ) self.add_modules_args(modules_parser) cli_modules, unused_args = modules_parser.parse_known_args(unused_args) - for module_type in BaseModule.MODULE_TYPES: + for module_type in MODULE_TYPES: yaml_config['steps'][f"{module_type}s"] = getattr(cli_modules, f"{module_type}s", []) or yaml_config['steps'].get(f"{module_type}s", []) parser = DefaultValidatingParser( @@ -157,15 +168,15 @@ class ArchivingOrchestrator: # TODO: if some steps are empty (e.g. 'feeders' is empty), should we default to the 'simple' ones? Or only if they are ALL empty? enabled_modules = [] # first loads the modules from the config file, then from the command line - for module_type in BaseModule.MODULE_TYPES: + for module_type in MODULE_TYPES: enabled_modules.extend(yaml_config['steps'].get(f"{module_type}s", [])) # clear out duplicates, but keep the order enabled_modules = list(dict.fromkeys(enabled_modules)) - avail_modules = available_modules(with_manifest=True, limit_to_modules=enabled_modules, suppress_warnings=True) + avail_modules = self.module_factory.available_modules(limit_to_modules=enabled_modules, suppress_warnings=True) self.add_individual_module_args(avail_modules, parser) elif basic_config.mode == 'simple': - simple_modules = [module for module in available_modules(with_manifest=True) if not module.requires_setup] + simple_modules = [module for module in self.module_factory.available_modules() if not module.requires_setup] self.add_individual_module_args(simple_modules, parser) # for simple mode, we use the cli_feeder and any modules that don't require setup @@ -178,7 +189,7 @@ class ArchivingOrchestrator: yaml_config['steps'].setdefault(f"{module_type}s", []).append(module.name) else: # load all modules, they're not using the 'simple' mode - self.add_individual_module_args(available_modules(with_manifest=True), parser) + self.add_individual_module_args(self.module_factory.available_modules(), parser) parser.set_defaults(**to_dot_notation(yaml_config)) @@ -208,7 +219,7 @@ class ArchivingOrchestrator: parser = self.parser # Module loading from the command line - for module_type in BaseModule.MODULE_TYPES: + for module_type in MODULE_TYPES: parser.add_argument(f'--{module_type}s', dest=f'{module_type}s', nargs='+', help=f'the {module_type}s to use', default=[], action=UniqueAppendAction) def add_additional_args(self, parser: argparse.ArgumentParser = None): @@ -234,7 +245,7 @@ class ArchivingOrchestrator: def add_individual_module_args(self, modules: list[LazyBaseModule] = None, parser: argparse.ArgumentParser = None) -> None: if not modules: - modules = available_modules(with_manifest=True) + modules = self.module_factory.available_modules() for module in modules: @@ -297,7 +308,7 @@ class ArchivingOrchestrator: """ invalid_modules = [] - for module_type in BaseModule.MODULE_TYPES: + for module_type in MODULE_TYPES: step_items = [] modules_to_load = modules_by_type[f"{module_type}s"] @@ -342,7 +353,7 @@ class ArchivingOrchestrator: if module in invalid_modules: continue try: - loaded_module: BaseModule = get_module(module, self.config) + loaded_module: BaseModule = self.module_factory.get_module(module, self.config) except (KeyboardInterrupt, Exception) as e: logger.error(f"Error during setup of modules: {e}\n{traceback.format_exc()}") if module_type == 'extractor' and loaded_module.name == module: @@ -378,7 +389,7 @@ class ArchivingOrchestrator: basic_config, unused_args = self.basic_parser.parse_known_args(args) # setup any custom module paths, so they'll show in the help and for arg parsing - setup_paths(basic_config.module_paths) + self.module_factory.setup_paths(basic_config.module_paths) # if help flag was called, then show the help if basic_config.help: @@ -409,7 +420,7 @@ class ArchivingOrchestrator: self.install_modules(self.config['steps']) # log out the modules that were loaded - for module_type in BaseModule.MODULE_TYPES: + for module_type in MODULE_TYPES: logger.info(f"{module_type.upper()}S: " + ", ".join(m.display_name for m in getattr(self, f"{module_type}s"))) self.setup_finished = True diff --git a/src/auto_archiver/core/storage.py b/src/auto_archiver/core/storage.py index 15d4705..1535eab 100644 --- a/src/auto_archiver/core/storage.py +++ b/src/auto_archiver/core/storage.py @@ -14,7 +14,7 @@ from auto_archiver.utils.misc import random_str from auto_archiver.core import Media, BaseModule, Metadata from auto_archiver.modules.hash_enricher.hash_enricher import HashEnricher -from auto_archiver.core.module import get_module + class Storage(BaseModule): """ @@ -74,7 +74,7 @@ class Storage(BaseModule): filename = random_str(24) elif filename_generator == "static": # load the hash_enricher module - he = get_module(HashEnricher, self.config) + he = self.module_factory.get_module(HashEnricher, self.config) hd = he.calculate_hash(media.filename) filename = hd[:24] else: diff --git a/src/auto_archiver/modules/html_formatter/html_formatter.py b/src/auto_archiver/modules/html_formatter/html_formatter.py index ce4e67b..deb4b44 100644 --- a/src/auto_archiver/modules/html_formatter/html_formatter.py +++ b/src/auto_archiver/modules/html_formatter/html_formatter.py @@ -10,7 +10,6 @@ from auto_archiver.version import __version__ from auto_archiver.core import Metadata, Media from auto_archiver.core import Formatter from auto_archiver.utils.misc import random_str -from auto_archiver.core.module import get_module class HtmlFormatter(Formatter): environment: Environment = None @@ -50,7 +49,7 @@ class HtmlFormatter(Formatter): final_media = Media(filename=html_path, _mimetype="text/html") # get the already instantiated hash_enricher module - he = get_module('hash_enricher', self.config) + he = self.module_factory.get_module('hash_enricher', self.config) if len(hd := he.calculate_hash(final_media.filename)): final_media.set("hash", f"{he.algorithm}:{hd}") diff --git a/src/auto_archiver/modules/whisper_enricher/whisper_enricher.py b/src/auto_archiver/modules/whisper_enricher/whisper_enricher.py index 89579f9..7179bdd 100644 --- a/src/auto_archiver/modules/whisper_enricher/whisper_enricher.py +++ b/src/auto_archiver/modules/whisper_enricher/whisper_enricher.py @@ -4,7 +4,6 @@ from loguru import logger from auto_archiver.core import Enricher from auto_archiver.core import Metadata, Media -from auto_archiver.core.module import get_module class WhisperEnricher(Enricher): """ @@ -15,7 +14,7 @@ class WhisperEnricher(Enricher): def setup(self) -> None: self.stores = self.config['steps']['storages'] - self.s3 = get_module("s3_storage", self.config) + self.s3 = self.module_factory.get_module("s3_storage", self.config) if not "s3_storage" in self.stores: logger.error("WhisperEnricher: To use the WhisperEnricher you need to use S3Storage so files are accessible publicly to the whisper service being called.") return diff --git a/tests/conftest.py b/tests/conftest.py index 8675fbc..eaa59b8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -8,7 +8,7 @@ from typing import Dict, Tuple import hashlib import pytest from auto_archiver.core.metadata import Metadata -from auto_archiver.core.module import get_module, _LAZY_LOADED_MODULES +from auto_archiver.core.module import ModuleFactory # Test names inserted into this list will be run last. This is useful for expensive/costly tests # that you only want to run if everything else succeeds (e.g. API calls). The order here is important @@ -20,19 +20,19 @@ TESTS_TO_RUN_LAST = ['test_twitter_api_archiver'] def setup_module(request): def _setup_module(module_name, config={}): + module_factory = ModuleFactory() + if isinstance(module_name, type): # get the module name: # if the class does not have a .name, use the name of the parent folder module_name = module_name.__module__.rsplit(".",2)[-2] - m = get_module(module_name, {module_name: config}) - + m = module_factory.get_module(module_name, {module_name: config}) # add the tmp_dir to the module tmp_dir = TemporaryDirectory() m.tmp_dir = tmp_dir.name - + def cleanup(): - _LAZY_LOADED_MODULES.pop(module_name) tmp_dir.cleanup() request.addfinalizer(cleanup) diff --git a/tests/enrichers/test_hash_enricher.py b/tests/enrichers/test_hash_enricher.py index 4b61fc2..c2fe67a 100644 --- a/tests/enrichers/test_hash_enricher.py +++ b/tests/enrichers/test_hash_enricher.py @@ -2,7 +2,7 @@ import pytest from auto_archiver.modules.hash_enricher import HashEnricher from auto_archiver.core import Metadata, Media -from auto_archiver.core.module import get_module_lazy +from auto_archiver.core.module import ModuleFactory @pytest.mark.parametrize("algorithm, filename, expected_hash", [ ("SHA-256", "tests/data/testfile_1.txt", "1b4f0e9851971998e732078544c96b36c3d01cedf7caa332359d6f1d83567014"), @@ -22,7 +22,7 @@ def test_default_config_values(setup_module): def test_config(): # test default config - c = get_module_lazy('hash_enricher').configs + c = ModuleFactory().get_module_lazy('hash_enricher').configs assert c["algorithm"]["default"] == "SHA-256" assert c["chunksize"]["default"] == 16000000 assert c["algorithm"]["choices"] == ["SHA-256", "SHA3-512"] diff --git a/tests/test_modules.py b/tests/test_modules.py index 854edb5..7a2b14d 100644 --- a/tests/test_modules.py +++ b/tests/test_modules.py @@ -1,24 +1,18 @@ import sys import pytest -from auto_archiver.core.module import get_module_lazy, BaseModule, LazyBaseModule, _LAZY_LOADED_MODULES +from auto_archiver.core.module import ModuleFactory, LazyBaseModule +from auto_archiver.core.base_module import BaseModule @pytest.fixture def example_module(): import auto_archiver + module_factory = ModuleFactory() + previous_path = auto_archiver.modules.__path__ auto_archiver.modules.__path__.append("tests/data/test_modules/") - module = get_module_lazy("example_module") - yield module - # cleanup - try: - del module._manifest - except AttributeError: - pass - del _LAZY_LOADED_MODULES["example_module"] - sys.modules.pop("auto_archiver.modules.example_module.example_module", None) - auto_archiver.modules.__path__ = previous_path + return module_factory.get_module_lazy("example_module") def test_get_module_lazy(example_module): assert example_module.name == "example_module" @@ -46,12 +40,14 @@ def test_module_dependency_check_loads_module(example_module): # monkey patch the manifest to include a nonexistnet dependency example_module.manifest["dependencies"]["python"] = ["hash_enricher"] + module_factory = example_module.module_factory + loaded_module = example_module.load({}) assert loaded_module is not None # check the dependency is loaded - assert _LAZY_LOADED_MODULES["hash_enricher"] is not None - assert _LAZY_LOADED_MODULES["hash_enricher"]._instance is not None + assert module_factory._lazy_modules["hash_enricher"] is not None + assert module_factory._lazy_modules["hash_enricher"]._instance is not None def test_load_module(example_module): @@ -69,7 +65,7 @@ def test_load_module(example_module): @pytest.mark.parametrize("module_name", ["local_storage", "generic_extractor", "html_formatter", "csv_db"]) def test_load_modules(module_name): # test that specific modules can be loaded - module = get_module_lazy(module_name) + module = ModuleFactory().get_module_lazy(module_name) assert module is not None assert isinstance(module, LazyBaseModule) assert module.name == module_name @@ -86,7 +82,7 @@ def test_load_modules(module_name): @pytest.mark.parametrize("module_name", ["local_storage", "generic_extractor", "html_formatter", "csv_db"]) def test_lazy_base_module(module_name): - lazy_module = get_module_lazy(module_name) + lazy_module = ModuleFactory().get_module_lazy(module_name) assert lazy_module is not None assert isinstance(lazy_module, LazyBaseModule) diff --git a/tests/test_orchestrator.py b/tests/test_orchestrator.py index f93f8b8..301e4d9 100644 --- a/tests/test_orchestrator.py +++ b/tests/test_orchestrator.py @@ -4,7 +4,7 @@ from argparse import ArgumentParser, ArgumentTypeError from auto_archiver.core.orchestrator import ArchivingOrchestrator from auto_archiver.version import __version__ from auto_archiver.core.config import read_yaml, store_yaml -from auto_archiver.core.module import _LAZY_LOADED_MODULES + TEST_ORCHESTRATION = "tests/data/test_orchestration.yaml" TEST_MODULES = "tests/data/test_modules/" @@ -17,22 +17,7 @@ def test_args(): @pytest.fixture def orchestrator(): - yield ArchivingOrchestrator() - # hack - the loguru logger starts with one logger, but if orchestrator has run before - # it'll remove the default logger, add it back in: - - from loguru import logger - - if not logger._core.handlers.get(0): - logger._core.handlers_count = 0 - logger.add(sys.stderr) - # and remove the custom logger - if logger._core.handlers.get(1): - logger.remove(1) - - # delete out any loaded modules - _LAZY_LOADED_MODULES.clear() - + return ArchivingOrchestrator() @pytest.fixture def basic_parser(orchestrator) -> ArgumentParser: From 47a634fc63e1ba7a821e9b60d52d9fb057f3499d Mon Sep 17 00:00:00 2001 From: erinhmclark Date: Wed, 19 Feb 2025 13:14:08 +0000 Subject: [PATCH 055/160] Add WACZ, Wayback and local storage tests. --- tests/enrichers/test_wacz_enricher.py | 112 ++++++++++++ tests/enrichers/test_wayback_enricher.py | 168 ++++++++++++++++++ .../test_instagram_tbot_extractor.py | 2 - tests/storages/test_local_storage.py | 54 ++++++ 4 files changed, 334 insertions(+), 2 deletions(-) create mode 100644 tests/enrichers/test_wacz_enricher.py create mode 100644 tests/enrichers/test_wayback_enricher.py create mode 100644 tests/storages/test_local_storage.py diff --git a/tests/enrichers/test_wacz_enricher.py b/tests/enrichers/test_wacz_enricher.py new file mode 100644 index 0000000..d55733d --- /dev/null +++ b/tests/enrichers/test_wacz_enricher.py @@ -0,0 +1,112 @@ +import os +from zipfile import ZipFile + +import pytest + +from auto_archiver.core import Metadata, Media + + +@pytest.fixture +def wacz_enricher(setup_module, mock_binary_dependencies): + configs: dict = { + "profile": None, + "docker_commands": None, + "timeout": 120, + "extract_media": False, + "extract_screenshot": True, + "socks_proxy_host": None, + "socks_proxy_port": None, + "proxy_server": None, + } + wacz = setup_module("wacz_enricher", configs) + return wacz + + +def test_setup_without_docker(wacz_enricher, mocker): + mocker.patch.dict(os.environ, {"RUNNING_IN_DOCKER": "1"}, clear=True) + wacz_enricher.setup() + assert not wacz_enricher.docker_in_docker + + +def test_setup_with_docker(wacz_enricher, mocker): + mocker.patch.dict(os.environ, {"WACZ_ENABLE_DOCKER": "1"}, clear=True) + wacz_enricher.setup() + assert wacz_enricher.use_docker + + +def test_already_ran(wacz_enricher, metadata, mocker): + metadata.add_media(Media("test.wacz"), id="browsertrix") + mock_log = mocker.patch("loguru.logger.info") + assert wacz_enricher.enrich(metadata) is True + assert "WACZ enricher had already been executed" in mock_log.call_args[0][0] + + +def test_basic_call_execution(wacz_enricher, mocker): + mock_run = mocker.patch("subprocess.run") + mock_run.return_value = mocker.Mock(returncode=0) + metadata = Metadata().set_url("https://example.com") + wacz_enricher.enrich(metadata) + assert mock_run.called + # Checks that the url is passed to the cmd + assert "--url https://example.com" in " ".join(mock_run.call_args[0][0]) + + +def test_download_success(wacz_enricher, mocker) -> None: + """Test download returns metadata on successful enrichment.""" + basic_metadata = Metadata().set_url("https://example.com") + mocker.patch.object(wacz_enricher, "enrich", return_value=True) + result = wacz_enricher.download(basic_metadata) + assert result is not None + assert isinstance(result, Metadata) + assert result.status == "wacz: success" + + +def test_enrich_already_executed(wacz_enricher, mocker) -> None: + """Test enrich if already executed.""" + mock_log = mocker.patch("loguru.logger.info") + metadata = Metadata().set_url("https://example.com") + media = Media(filename="some_file.wacz") + metadata.add_media(media, id="browsertrix") + result = wacz_enricher.enrich(metadata) + assert result is True + assert "WACZ enricher had already been executed:" in mock_log.call_args[0][0] + + +def test_enrich_subprocess_exception(wacz_enricher, mocker, tmp_path) -> None: + """Test enrich returns False when subprocess fails.""" + wacz_enricher.tmp_dir = str(tmp_path) + wacz_enricher.extract_media = False + wacz_enricher.extract_screenshot = True + mocker.patch("auto_archiver.utils.misc.random_str", return_value="TESTCOL") + mocker.patch("subprocess.run", side_effect=Exception("fail")) + basic_metadata = Metadata().set_url("https://example.com") + result = wacz_enricher.enrich(basic_metadata) + assert result is False + + +def test_extract_media(wacz_enricher, metadata, tmp_path, mocker) -> None: + """Test extract_media_from_wacz extracts screenshot media.""" + wacz_enricher.tmp_dir = str(tmp_path) + + # Create a *real* zip file so ZipFile won't fail. + wacz_file = tmp_path / "dummy.wacz" + with ZipFile(wacz_file, "w") as zf: + zf.writestr("dummy.txt", "test content") + + mocker.patch("os.listdir", return_value=[]) + warc_data = ( + b"WARC/1.0\r\n" + b"WARC-Type: resource\r\n" + b"Content-Type: image/png\r\n" + b"WARC-Target-URI: http://example.com/image.png\r\n" + b"Content-Length: 12\r\n" + b"\r\n" + b"image-bytes" + b"\r\n\r\nWARC/1.0\r\n\r\n" + ) + mock_file = mocker.mock_open(read_data=warc_data) + mocker.patch("builtins.open", mock_file) + metadata.add_media(Media("something.wacz"), "browsertrix") + wacz_enricher.extract_media_from_wacz(metadata, str(wacz_file)) + assert len(metadata.media) == 2 + assert metadata.media[1].properties.get("id") == "browsertrix-screenshot" diff --git a/tests/enrichers/test_wayback_enricher.py b/tests/enrichers/test_wayback_enricher.py new file mode 100644 index 0000000..88f4662 --- /dev/null +++ b/tests/enrichers/test_wayback_enricher.py @@ -0,0 +1,168 @@ +import json +import requests +import pytest +from auto_archiver.modules.wayback_extractor_enricher import WaybackExtractorEnricher +from auto_archiver.core import Metadata + + +@pytest.fixture +def mock_is_auth_wall(mocker): + """Fixture to mock is_auth_wall behavior.""" + def _mock_is_auth_wall(return_value: bool): + return mocker.patch("auto_archiver.utils.url.is_auth_wall", return_value=return_value) + return _mock_is_auth_wall + +@pytest.fixture +def mock_post_success(mocker): + """Fixture to mock POST requests with a successful response.""" + def _mock_post(json_data: dict = None, status_code: int = 200): + json_data = json_data or {"job_id": "job123"} + resp = mocker.Mock(status_code=status_code) + resp.json.return_value = json_data + return mocker.patch("requests.post", return_value=resp) + return _mock_post + +@pytest.fixture +def mock_get_success(mocker): + """Fixture to mock GET requests returning a completed archive status.""" + def _mock_get(json_data: dict = None, status_code: int = 200): + json_data = json_data or { + "status": "success", + "timestamp": "20250101010101", + "original_url": "https://example.com" + } + resp = mocker.Mock(status_code=status_code) + resp.json.return_value = json_data + return mocker.patch("requests.get", return_value=resp) + return _mock_get + +@pytest.fixture +def wayback_extractor_enricher(setup_module) -> WaybackExtractorEnricher: + configs: dict = { + "timeout": 5, + "if_not_archived_within": None, + "key": "somekey", + "secret": "secret", + "proxy_http": None, + "proxy_https": None, + } + return setup_module("wayback_extractor_enricher", configs) + + +def test_download_success( + wayback_extractor_enricher, + mock_is_auth_wall, + mock_post_success, + mock_get_success +): + mock_is_auth_wall(False) + mock_post_success() + mock_get_success() + # Basic metadata to allow merge + metadata = Metadata().set_url("https://example.com") + result = wayback_extractor_enricher.download(metadata) + assert result.get("wayback") == "https://web.archive.org/web/20250101010101/https://example.com" + +def test_enrich_auth_wall(wayback_extractor_enricher, metadata, mock_is_auth_wall): + mock_is_auth_wall(True) + result = wayback_extractor_enricher.enrich(metadata) + assert result is None + +def test_enrich_already_enriched(wayback_extractor_enricher, metadata): + metadata.set("wayback", "existing") + result = wayback_extractor_enricher.enrich(metadata) + assert result is True + +def test_enrich_post_failure( + wayback_extractor_enricher, + metadata, + mock_is_auth_wall, + mock_post_success +): + mock_is_auth_wall(False) + mock_post_success(json_data={"error": "server error"}, status_code=500) + result = wayback_extractor_enricher.enrich(metadata) + assert result is False + assert "Internet archive failed with status of 500" in metadata.get("wayback") + +def test_enrich_post_json_decode_error( + wayback_extractor_enricher, + metadata, + mock_is_auth_wall, + mocker +): + mock_is_auth_wall(False) + resp = mocker.Mock(status_code=200) + resp.json.side_effect = json.decoder.JSONDecodeError("msg", "doc", 0) + resp.text = "invalid json" + mocker.patch("requests.post", return_value=resp) + assert wayback_extractor_enricher.enrich(metadata) is False + +def test_enrich_no_job_id( + wayback_extractor_enricher, + metadata, + mock_is_auth_wall, + mock_post_success +): + mock_is_auth_wall(False) + mock_post_success(json_data={}) + assert wayback_extractor_enricher.enrich(metadata) is False + +def test_enrich_get_success( + wayback_extractor_enricher, + metadata, + mock_is_auth_wall, + mock_post_success, + mock_get_success +): + mock_is_auth_wall(False) + mock_post_success() + mock_get_success() + assert wayback_extractor_enricher.enrich(metadata) is True + assert metadata.get("wayback") == "https://web.archive.org/web/20250101010101/https://example.com" + assert metadata.get("check wayback") == "https://web.archive.org/web/*/https://example.com" + +def test_enrich_get_failure( + wayback_extractor_enricher, + metadata, + mock_is_auth_wall, + mock_post_success, + mock_get_success +): + mock_is_auth_wall(False) + mock_post_success() + mock_get_success(json_data={"status": "failed"}, status_code=400) + assert wayback_extractor_enricher.enrich(metadata) is False + +def test_enrich_get_request_exception( + wayback_extractor_enricher, + metadata, + mock_is_auth_wall, + mock_post_success, + mocker +): + mock_is_auth_wall(False) + mock_post_success() + mocker.patch("requests.get", side_effect=requests.exceptions.RequestException("error")) + mocker.patch("time.sleep", return_value=None) + # check it still enriches the job_id information + assert wayback_extractor_enricher.enrich(metadata) is True + assert metadata.get("wayback").get("job_id") == "job123" + +def test_enrich_get_json_decode_error( + wayback_extractor_enricher, + metadata, + mock_is_auth_wall, + mock_post_success, + mocker +): + mock_is_auth_wall(False) + mock_post_success() + resp = mocker.Mock() + resp.json.side_effect = json.decoder.JSONDecodeError("msg", "doc", 0) + resp.text = "invalid json" + mocker.patch("requests.get", return_value=resp) + mocker.patch("time.sleep", return_value=None) + # check it still enriches the job_id information + assert wayback_extractor_enricher.enrich(metadata) is True + assert metadata.get("wayback").get("job_id") == "job123" diff --git a/tests/extractors/test_instagram_tbot_extractor.py b/tests/extractors/test_instagram_tbot_extractor.py index 9238f89..f274728 100644 --- a/tests/extractors/test_instagram_tbot_extractor.py +++ b/tests/extractors/test_instagram_tbot_extractor.py @@ -6,8 +6,6 @@ from auto_archiver.core import Metadata from auto_archiver.modules.instagram_tbot_extractor import InstagramTbotExtractor from tests.extractors.test_extractor_base import TestExtractorBase -TESTFILES = os.path.join(os.path.dirname(__file__), "testfiles") - @pytest.fixture def patch_extractor_methods(request, setup_module, mocker): diff --git a/tests/storages/test_local_storage.py b/tests/storages/test_local_storage.py new file mode 100644 index 0000000..85f97c6 --- /dev/null +++ b/tests/storages/test_local_storage.py @@ -0,0 +1,54 @@ + +import os +from pathlib import Path + +import pytest + +from auto_archiver.core import Media +from auto_archiver.modules.local_storage import LocalStorage + + +@pytest.fixture +def local_storage(setup_module) -> LocalStorage: + configs: dict = { + "path_generator": "flat", + "filename_generator": "static", + "save_to": "./local_archive", + "save_absolute": False, + } + return setup_module("local_storage", configs) + + +@pytest.fixture +def sample_media(tmp_path) -> Media: + """Fixture creating a Media object with temporary source file""" + src_file = tmp_path / "source.txt" + src_file.write_text("test content") + return Media(key="subdir/test.txt", filename=str(src_file)) + + +def test_get_cdn_url_relative(local_storage): + media = Media(key="test.txt", filename="dummy.txt") + expected = os.path.join(local_storage.save_to, media.key) + assert local_storage.get_cdn_url(media) == expected + + + +def test_get_cdn_url_absolute(local_storage): + media = Media(key="test.txt", filename="dummy.txt") + local_storage.save_absolute = True + expected = os.path.abspath(os.path.join(local_storage.save_to, media.key)) + assert local_storage.get_cdn_url(media) == expected + +def test_upload_file_contents_and_metadata(local_storage, sample_media): + dest = os.path.join(local_storage.save_to, sample_media.key) + assert local_storage.upload(sample_media) is True + assert Path(sample_media.filename).read_text() == Path(dest).read_text() + + +def test_upload_nonexistent_source(local_storage): + media = Media(key="missing.txt", filename="nonexistent.txt") + with pytest.raises(FileNotFoundError): + local_storage.upload(media) + + From 04507577b68272365dd76725ac311ae396a5f301 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Wed, 19 Feb 2025 13:36:50 +0000 Subject: [PATCH 056/160] Version bump --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 9823833..636604d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api" [project] name = "auto-archiver" -version = "0.13.3" +version = "0.13.4" description = "Automatically archive links to videos, images, and social media content from Google Sheets (and more)." requires-python = ">=3.10,<3.13" From 6ea943b6808e40e60b8256c17ceafcb3a36522d0 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Thu, 20 Feb 2025 10:27:24 +0000 Subject: [PATCH 057/160] Fix link --- docs/source/installation/installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/installation/installation.md b/docs/source/installation/installation.md index 6f2e9d4..c728451 100644 --- a/docs/source/installation/installation.md +++ b/docs/source/installation/installation.md @@ -15,7 +15,7 @@ There are 3 main ways to use the auto-archiver: 3. Developer Install: [see the developer guidelines](../development/developer_guidelines) -But **you always need a configuration/orchestration file**, which is where you'll configure where/what/how to archive. Make sure you read [orchestration](#orchestration). +But **you always need a configuration/orchestration file**, which is where you'll configure where/what/how to archive. Make sure you read [](configurations). ## Installing with Docker From 5ccea8e44a3bab7b0554d69c1715a927b657002a Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Wed, 19 Feb 2025 15:30:13 +0000 Subject: [PATCH 058/160] Absolute paths in README for Github/PyPi/Dockerhub etc. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 368a904..bb8a671 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Read the [article about Auto Archiver on bellingcat.com](https://www.bellingcat. ## Installation -View the [Installation Guide](installation/installation.md) for full instructions +View the [Installation Guide](https://auto-archiver.readthedocs.io/en/latest/installation/installation.html) for full instructions To get started quickly using Docker: From 40b8359348e388d3a9c96506fbb21094ce1db117 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Wed, 19 Feb 2025 15:30:37 +0000 Subject: [PATCH 059/160] Implementation test with 2 x orchestrators with different configs --- tests/test_orchestrator.py | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/tests/test_orchestrator.py b/tests/test_orchestrator.py index 301e4d9..ee1b9af 100644 --- a/tests/test_orchestrator.py +++ b/tests/test_orchestrator.py @@ -4,7 +4,7 @@ from argparse import ArgumentParser, ArgumentTypeError from auto_archiver.core.orchestrator import ArchivingOrchestrator from auto_archiver.version import __version__ from auto_archiver.core.config import read_yaml, store_yaml - +from auto_archiver.core import Metadata TEST_ORCHESTRATION = "tests/data/test_orchestration.yaml" TEST_MODULES = "tests/data/test_modules/" @@ -160,4 +160,26 @@ def test_load_settings_for_module_from_commandline(orchestrator, test_args): assert len(orchestrator.feeders) == 1 assert orchestrator.feeders[0].name == "gsheet_feeder" - assert orchestrator.config['gsheet_feeder']['sheet_id'] == "123" \ No newline at end of file + assert orchestrator.config['gsheet_feeder']['sheet_id'] == "123" + + +def test_multiple_orchestrator(test_args): + + o1_args = test_args + ["--feeders", "gsheet_feeder"] + o1 = ArchivingOrchestrator() + + with pytest.raises(AssertionError) as exit_error: + # this should fail because the gsheet_feeder requires a sheet_id / sheet + o1.setup(o1_args) + + + + o2_args = test_args + ["--feeders", "example_module"] + o2 = ArchivingOrchestrator() + o2.setup(o2_args) + + assert o2.feeders[0].name == "example_module" + + output: Metadata = list(o2.feed()) + assert len(output) == 1 + assert output[0].get_url() == "https://example.com" \ No newline at end of file From 77b2b099c63071fe42c9542039e0bd0d92ac0058 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Wed, 19 Feb 2025 15:32:45 +0000 Subject: [PATCH 060/160] Replace exit() with raise exceptions. Better for code implementations exit() is reserved solely for command line-called areas now also assert is only recommended for debugging --- src/auto_archiver/core/config.py | 58 +++++++++++++++ src/auto_archiver/core/orchestrator.py | 97 ++++++-------------------- 2 files changed, 80 insertions(+), 75 deletions(-) diff --git a/src/auto_archiver/core/config.py b/src/auto_archiver/core/config.py index 18b0a2a..79ce7e2 100644 --- a/src/auto_archiver/core/config.py +++ b/src/auto_archiver/core/config.py @@ -7,6 +7,7 @@ flexible setup in various environments. import argparse from ruamel.yaml import YAML, CommentedMap, add_representer +import json from loguru import logger @@ -17,6 +18,8 @@ from typing import Any, List, Type, Tuple _yaml: YAML = YAML() +DEFAULT_CONFIG_FILE = "orchestration.yaml" + EMPTY_CONFIG = _yaml.load(""" # Auto Archiver Configuration # Steps are the modules that will be run in the order they are defined @@ -52,6 +55,57 @@ logging: """) # note: 'logging' is explicitly added above in order to better format the config file + +# Arg Parse Actions/Classes +class AuthenticationJsonParseAction(argparse.Action): + def __call__(self, parser, namespace, values, option_string=None): + + try: + auth_dict = json.loads(values) + setattr(namespace, self.dest, auth_dict) + except json.JSONDecodeError as e: + raise argparse.ArgumentTypeError(f"Invalid JSON input for argument '{self.dest}': {e}") + + def load_from_file(path): + try: + with open(path, 'r') as f: + try: + auth_dict = json.load(f) + except json.JSONDecodeError: + f.seek(0) + # maybe it's yaml, try that + auth_dict = _yaml.load(f) + if auth_dict.get('authentication'): + auth_dict = auth_dict['authentication'] + auth_dict['load_from_file'] = path + return auth_dict + except: + return None + + if isinstance(auth_dict, dict) and auth_dict.get('from_file'): + auth_dict = load_from_file(auth_dict['from_file']) + elif isinstance(auth_dict, str): + # if it's a string + auth_dict = load_from_file(auth_dict) + + if not isinstance(auth_dict, dict): + raise argparse.ArgumentTypeError("Authentication must be a dictionary of site names and their authentication methods") + global_options = ['cookies_from_browser', 'cookies_file', 'load_from_file'] + for key, auth in auth_dict.items(): + if key in global_options: + continue + if not isinstance(key, str) or not isinstance(auth, dict): + raise argparse.ArgumentTypeError(f"Authentication must be a dictionary of site names and their authentication methods. Valid global configs are {global_options}") + + setattr(namespace, self.dest, auth_dict) + + +class UniqueAppendAction(argparse.Action): + def __call__(self, parser, namespace, values, option_string=None): + for value in values: + if value not in getattr(namespace, self.dest): + getattr(namespace, self.dest).append(value) + class DefaultValidatingParser(argparse.ArgumentParser): def error(self, message): @@ -82,6 +136,7 @@ class DefaultValidatingParser(argparse.ArgumentParser): return super().parse_known_args(args, namespace) +# Config Utils def to_dot_notation(yaml_conf: CommentedMap | dict) -> dict: dotdict = {} @@ -171,3 +226,6 @@ def store_yaml(config: CommentedMap, yaml_filename: str) -> None: config_to_save.pop('urls', None) with open(yaml_filename, "w", encoding="utf-8") as outf: _yaml.dump(config_to_save, outf) + +def is_valid_config(config: CommentedMap) -> bool: + return config and config != EMPTY_CONFIG \ No newline at end of file diff --git a/src/auto_archiver/core/orchestrator.py b/src/auto_archiver/core/orchestrator.py index 03e3a5e..54f5c32 100644 --- a/src/auto_archiver/core/orchestrator.py +++ b/src/auto_archiver/core/orchestrator.py @@ -11,16 +11,16 @@ from ipaddress import ip_address import argparse import os import sys -import json from tempfile import TemporaryDirectory import traceback from rich_argparse import RichHelpFormatter - +from loguru import logger from .metadata import Metadata, Media from auto_archiver.version import __version__ -from .config import _yaml, read_yaml, store_yaml, to_dot_notation, merge_dicts, EMPTY_CONFIG, DefaultValidatingParser +from .config import read_yaml, store_yaml, to_dot_notation, merge_dicts, is_valid_config, \ + DefaultValidatingParser, UniqueAppendAction, AuthenticationJsonParseAction, DEFAULT_CONFIG_FILE from .module import ModuleFactory, LazyBaseModule from . import validators, Feeder, Extractor, Database, Storage, Formatter, Enricher from .consts import MODULE_TYPES @@ -30,63 +30,8 @@ if TYPE_CHECKING: from .base_module import BaseModule from .module import LazyBaseModule -DEFAULT_CONFIG_FILE = "orchestration.yaml" - - -class JsonParseAction(argparse.Action): - def __call__(self, parser, namespace, values, option_string=None): - try: - setattr(namespace, self.dest, json.loads(values)) - except json.JSONDecodeError as e: - raise argparse.ArgumentTypeError(f"Invalid JSON input for argument '{self.dest}': {e}") - - -class AuthenticationJsonParseAction(JsonParseAction): - def __call__(self, parser, namespace, values, option_string=None): - super().__call__(parser, namespace, values, option_string) - auth_dict = getattr(namespace, self.dest) - - def load_from_file(path): - try: - with open(path, 'r') as f: - try: - auth_dict = json.load(f) - except json.JSONDecodeError: - f.seek(0) - # maybe it's yaml, try that - auth_dict = _yaml.load(f) - if auth_dict.get('authentication'): - auth_dict = auth_dict['authentication'] - auth_dict['load_from_file'] = path - return auth_dict - except: - return None - - if isinstance(auth_dict, dict) and auth_dict.get('from_file'): - auth_dict = load_from_file(auth_dict['from_file']) - elif isinstance(auth_dict, str): - # if it's a string - auth_dict = load_from_file(auth_dict) - - if not isinstance(auth_dict, dict): - raise argparse.ArgumentTypeError("Authentication must be a dictionary of site names and their authentication methods") - global_options = ['cookies_from_browser', 'cookies_file', 'load_from_file'] - for key, auth in auth_dict.items(): - if key in global_options: - continue - if not isinstance(key, str) or not isinstance(auth, dict): - raise argparse.ArgumentTypeError(f"Authentication must be a dictionary of site names and their authentication methods. Valid global configs are {global_options}") - - setattr(namespace, self.dest, auth_dict) - - -class UniqueAppendAction(argparse.Action): - def __call__(self, parser, namespace, values, option_string=None): - for value in values: - if value not in getattr(namespace, self.dest): - getattr(namespace, self.dest).append(value) - - +class SetupError(ValueError): + pass class ArchivingOrchestrator: # instance variables @@ -155,7 +100,7 @@ class ArchivingOrchestrator: # TODO: BUG** - basic_config won't have steps in it, since these args aren't added to 'basic_parser' # but should we add them? Or should we just add them to the 'complete' parser? - if yaml_config != EMPTY_CONFIG: + if is_valid_config(yaml_config): # only load the modules enabled in config # TODO: if some steps are empty (e.g. 'feeders' is empty), should we default to the 'simple' ones? Or only if they are ALL empty? enabled_modules = [] @@ -304,27 +249,25 @@ class ArchivingOrchestrator: step_items = [] modules_to_load = modules_by_type[f"{module_type}s"] - assert modules_to_load, f"No {module_type}s were configured. Make sure to set at least one {module_type} in your configuration file or on the command line (using --{module_type}s)" + if not modules_to_load: + raise SetupError(f"No {module_type}s were configured. Make sure to set at least one {module_type} in your configuration file or on the command line (using --{module_type}s)") def check_steps_ok(): if not len(step_items): - logger.error(f"NO {module_type.upper()}S LOADED. Please check your configuration and try again.") if len(modules_to_load): - logger.error(f"Tried to load the following modules, but none were available: {modules_to_load}") - exit() + logger.error(f"Unable to load any {module_type}s. Tried the following, but none were available: {modules_to_load}") + raise SetupError(f"NO {module_type.upper()}S LOADED. Please check your configuration and try again.") + if (module_type == 'feeder' or module_type == 'formatter') and len(step_items) > 1: - logger.error(f"Only one {module_type} is allowed, found {len(step_items)} {module_type}s. Please remove one of the following from your configuration file: {modules_to_load}") - exit() + raise SetupError(f"Only one {module_type} is allowed, found {len(step_items)} {module_type}s. Please remove one of the following from your configuration file: {modules_to_load}") for module in modules_to_load: if module == 'cli_feeder': - # pseudo module, don't load it + # cli_feeder is a pseudo module, it just takes the command line args for [URLS] urls = self.config['urls'] if not urls: - logger.error("No URLs provided. Please provide at least one URL via the command line, or set up an alternative feeder. Use --help for more information.") - exit() - # cli_feeder is a pseudo module, it just takes the command line args + raise SetupError("No URLs provided. Please provide at least one URL via the command line, or set up an alternative feeder. Use --help for more information.") def feed(self) -> Generator[Metadata]: for url in urls: @@ -350,7 +293,7 @@ class ArchivingOrchestrator: logger.error(f"Error during setup of modules: {e}\n{traceback.format_exc()}") if module_type == 'extractor' and loaded_module.name == module: loaded_module.cleanup() - exit() + raise e if not loaded_module: invalid_modules.append(module) @@ -364,7 +307,7 @@ class ArchivingOrchestrator: def load_config(self, config_file: str) -> dict: if not os.path.exists(config_file) and config_file != DEFAULT_CONFIG_FILE: logger.error(f"The configuration file {config_file} was not found. Make sure the file exists and try again, or run without the --config file to use the default settings.") - exit() + raise FileNotFoundError(f"Configuration file {config_file} not found") return read_yaml(config_file) @@ -429,8 +372,12 @@ class ArchivingOrchestrator: If you wish to make code invocations yourself, you should use the 'setup' and 'feed' methods separately. To test configurations, without loading any modules you can also first call 'setup_configs' """ - self.setup(args) - return self.feed() + try: + self.setup(args) + return self.feed() + except Exception as e: + logger.exception(e) + exit(1) def cleanup(self) -> None: logger.info("Cleaning up") From 7734a551faf8178153997baf384442513887de2e Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Thu, 20 Feb 2025 10:57:30 +0000 Subject: [PATCH 061/160] Move 'assert_valid_url' out into utils, don't use assert but raise assert is recommended only for debugging --- src/auto_archiver/core/base_module.py | 7 ----- src/auto_archiver/core/orchestrator.py | 42 ++++++++++++++------------ src/auto_archiver/utils/url.py | 38 +++++++++++++++++++++++ 3 files changed, 61 insertions(+), 26 deletions(-) diff --git a/src/auto_archiver/core/base_module.py b/src/auto_archiver/core/base_module.py index 2f6ab20..c38db3b 100644 --- a/src/auto_archiver/core/base_module.py +++ b/src/auto_archiver/core/base_module.py @@ -56,13 +56,6 @@ class BaseModule(ABC): config = deepcopy(config) authentication = deepcopy(config.pop('authentication', {})) - # extract out concatenated sites - for key, val in copy(authentication).items(): - if "," in key: - for site in key.split(","): - authentication[site] = val - del authentication[key] - self.authentication = authentication self.config = config for key, val in config.get(self.name, {}).items(): diff --git a/src/auto_archiver/core/orchestrator.py b/src/auto_archiver/core/orchestrator.py index 54f5c32..c9980ff 100644 --- a/src/auto_archiver/core/orchestrator.py +++ b/src/auto_archiver/core/orchestrator.py @@ -6,13 +6,12 @@ from __future__ import annotations from typing import Generator, Union, List, Type, TYPE_CHECKING -from urllib.parse import urlparse -from ipaddress import ip_address import argparse import os import sys from tempfile import TemporaryDirectory import traceback +from copy import copy from rich_argparse import RichHelpFormatter from loguru import logger @@ -24,6 +23,7 @@ from .config import read_yaml, store_yaml, to_dot_notation, merge_dicts, is_vali from .module import ModuleFactory, LazyBaseModule from . import validators, Feeder, Extractor, Database, Storage, Formatter, Enricher from .consts import MODULE_TYPES +from auto_archiver.utils.url import check_url_or_raise from loguru import logger if TYPE_CHECKING: @@ -135,6 +135,9 @@ class ArchivingOrchestrator: # merge the new config with the old one config = merge_dicts(vars(parsed), yaml_config) + # set up the authentication dict as needed + config = self.setup_authentication(config) + # clean out args from the base_parser that we don't want in the config for key in vars(basic_config): config.pop(key, None) @@ -287,6 +290,7 @@ class ArchivingOrchestrator: if module in invalid_modules: continue + try: loaded_module: BaseModule = self.module_factory.get_module(module, self.config) except (KeyboardInterrupt, Exception) as e: @@ -442,8 +446,8 @@ class ArchivingOrchestrator: original_url = result.get_url().strip() try: - self.assert_valid_url(original_url) - except AssertionError as e: + check_url_or_raise(original_url) + except ValueError as e: logger.error(f"Error archiving URL {original_url}: {e}") raise e @@ -503,26 +507,26 @@ class ArchivingOrchestrator: logger.error(f"ERROR database {d.name}: {e}: {traceback.format_exc()}") return result + - def assert_valid_url(self, url: str) -> bool: + def setup_authentication(self, config: dict) -> dict: """ - Blocks localhost, private, reserved, and link-local IPs and all non-http/https schemes. + Setup authentication for all modules that require it + + Split up strings into multiple sites if they are comma separated """ - assert url.startswith("http://") or url.startswith("https://"), f"Invalid URL scheme" - parsed = urlparse(url) - assert parsed.scheme in ["http", "https"], f"Invalid URL scheme" - assert parsed.hostname, f"Invalid URL hostname" - assert parsed.hostname != "localhost", f"Invalid URL" + authentication = config.get('authentication', {}) - try: # special rules for IP addresses - ip = ip_address(parsed.hostname) - except ValueError: pass - else: - assert ip.is_global, f"Invalid IP used" - assert not ip.is_reserved, f"Invalid IP used" - assert not ip.is_link_local, f"Invalid IP used" - assert not ip.is_private, f"Invalid IP used" + # extract out concatenated sites + for key, val in copy(authentication).items(): + if "," in key: + for site in key.split(","): + authentication[site] = val + del authentication[key] + + config['authentication'] = authentication + return config # Helper Properties diff --git a/src/auto_archiver/utils/url.py b/src/auto_archiver/utils/url.py index 40884da..061f4aa 100644 --- a/src/auto_archiver/utils/url.py +++ b/src/auto_archiver/utils/url.py @@ -1,5 +1,6 @@ import re from urllib.parse import urlparse, urlunparse +from ipaddress import ip_address AUTHWALL_URLS = [ @@ -7,6 +8,43 @@ AUTHWALL_URLS = [ re.compile(r"https:\/\/www\.instagram\.com"), # instagram ] + +def check_url_or_raise(url: str) -> bool | ValueError: + """ + Blocks localhost, private, reserved, and link-local IPs and all non-http/https schemes. + """ + + + if not (url.startswith("http://") or url.startswith("https://")): + raise ValueError(f"Invalid URL scheme for url {url}") + + parsed = urlparse(url) + if not parsed.hostname: + raise ValueError(f"Invalid URL hostname for url {url}") + + if parsed.hostname == "localhost": + raise ValueError(f"Localhost URLs cannot be parsed for security reasons (for url {url})") + + if parsed.scheme not in ["http", "https"]: + raise ValueError(f"Invalid URL scheme, only http and https supported (for url {url})") + + try: # special rules for IP addresses + ip = ip_address(parsed.hostname) + except ValueError: + pass + + else: + if not ip.is_global: + raise ValueError(f"IP address {ip} is not globally reachable") + if ip.is_reserved: + raise ValueError(f"Reserved IP address {ip} used") + if ip.is_link_local: + raise ValueError(f"Link-local IP address {ip} used") + if ip.is_private: + raise ValueError(f"Private IP address {ip} used") + + return True + def domain_for_url(url: str) -> str: """ SECURITY: parse the domain using urllib to avoid any potential security issues From 4b51ec9ad53bbc83756ed7b9a978dc1d9f9e470c Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Thu, 20 Feb 2025 11:20:16 +0000 Subject: [PATCH 062/160] Remove dangling import --- src/auto_archiver/core/orchestrator.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/auto_archiver/core/orchestrator.py b/src/auto_archiver/core/orchestrator.py index c9980ff..ba84d28 100644 --- a/src/auto_archiver/core/orchestrator.py +++ b/src/auto_archiver/core/orchestrator.py @@ -24,7 +24,6 @@ from .module import ModuleFactory, LazyBaseModule from . import validators, Feeder, Extractor, Database, Storage, Formatter, Enricher from .consts import MODULE_TYPES from auto_archiver.utils.url import check_url_or_raise -from loguru import logger if TYPE_CHECKING: from .base_module import BaseModule From 49b6c320584b46321d5d49f929ec0acc8b045d37 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Thu, 20 Feb 2025 11:29:36 +0000 Subject: [PATCH 063/160] Fix the 'full' mode which creates a complete config file --- src/auto_archiver/core/config.py | 2 +- src/auto_archiver/core/module.py | 6 ++++-- src/auto_archiver/core/orchestrator.py | 10 ++++++++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/auto_archiver/core/config.py b/src/auto_archiver/core/config.py index 79ce7e2..2e5d273 100644 --- a/src/auto_archiver/core/config.py +++ b/src/auto_archiver/core/config.py @@ -22,8 +22,8 @@ DEFAULT_CONFIG_FILE = "orchestration.yaml" EMPTY_CONFIG = _yaml.load(""" # Auto Archiver Configuration -# Steps are the modules that will be run in the order they are defined +# Steps are the modules that will be run in the order they are defined steps:""" + "".join([f"\n {module}s: []" for module in MODULE_TYPES]) + \ """ diff --git a/src/auto_archiver/core/module.py b/src/auto_archiver/core/module.py index 9556621..9f20084 100644 --- a/src/auto_archiver/core/module.py +++ b/src/auto_archiver/core/module.py @@ -134,7 +134,6 @@ class LazyBaseModule: """ name: str - type: list description: str path: str module_factory: ModuleFactory @@ -148,6 +147,10 @@ class LazyBaseModule: self.path = path self.module_factory = factory + @property + def type(self): + return self.manifest['type'] + @property def entry_point(self): if not self._entry_point and not self.manifest['entry_point']: @@ -186,7 +189,6 @@ class LazyBaseModule: logger.error(f"Error loading manifest from file {self.path}/{MANIFEST_FILE}: {e}") self._manifest = manifest - self.type = manifest['type'] self._entry_point = manifest['entry_point'] self.description = manifest['description'] self.version = manifest['version'] diff --git a/src/auto_archiver/core/orchestrator.py b/src/auto_archiver/core/orchestrator.py index ba84d28..f4086e9 100644 --- a/src/auto_archiver/core/orchestrator.py +++ b/src/auto_archiver/core/orchestrator.py @@ -125,7 +125,13 @@ class ArchivingOrchestrator: yaml_config['steps'].setdefault(f"{module_type}s", []).append(module.name) else: # load all modules, they're not using the 'simple' mode - self.add_individual_module_args(self.module_factory.available_modules(), parser) + all_modules = self.module_factory.available_modules() + # add all the modules to the steps + for module in all_modules: + for module_type in module.type: + yaml_config['steps'].setdefault(f"{module_type}s", []).append(module.name) + + self.add_individual_module_args(all_modules, parser) parser.set_defaults(**to_dot_notation(yaml_config)) @@ -379,7 +385,7 @@ class ArchivingOrchestrator: self.setup(args) return self.feed() except Exception as e: - logger.exception(e) + logger.error(e) exit(1) def cleanup(self) -> None: From b978484a897ac4127e8601767e07c51105fa724d Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Thu, 20 Feb 2025 11:32:53 +0000 Subject: [PATCH 064/160] Rename wacz_enricher to wacz_extractor_enricher. Fixes #205 --- src/auto_archiver/modules/wacz_enricher/__init__.py | 1 - src/auto_archiver/modules/wacz_extractor_enricher/__init__.py | 1 + .../__manifest__.py | 4 ++-- .../wacz_extractor_enricher.py} | 0 4 files changed, 3 insertions(+), 3 deletions(-) delete mode 100644 src/auto_archiver/modules/wacz_enricher/__init__.py create mode 100644 src/auto_archiver/modules/wacz_extractor_enricher/__init__.py rename src/auto_archiver/modules/{wacz_enricher => wacz_extractor_enricher}/__manifest__.py (95%) rename src/auto_archiver/modules/{wacz_enricher/wacz_enricher.py => wacz_extractor_enricher/wacz_extractor_enricher.py} (100%) diff --git a/src/auto_archiver/modules/wacz_enricher/__init__.py b/src/auto_archiver/modules/wacz_enricher/__init__.py deleted file mode 100644 index 686b8d8..0000000 --- a/src/auto_archiver/modules/wacz_enricher/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .wacz_enricher import WaczExtractorEnricher diff --git a/src/auto_archiver/modules/wacz_extractor_enricher/__init__.py b/src/auto_archiver/modules/wacz_extractor_enricher/__init__.py new file mode 100644 index 0000000..b9a53e3 --- /dev/null +++ b/src/auto_archiver/modules/wacz_extractor_enricher/__init__.py @@ -0,0 +1 @@ +from .wacz_extractor_enricher import WaczExtractorEnricher diff --git a/src/auto_archiver/modules/wacz_enricher/__manifest__.py b/src/auto_archiver/modules/wacz_extractor_enricher/__manifest__.py similarity index 95% rename from src/auto_archiver/modules/wacz_enricher/__manifest__.py rename to src/auto_archiver/modules/wacz_extractor_enricher/__manifest__.py index bebfc9e..b8d6201 100644 --- a/src/auto_archiver/modules/wacz_enricher/__manifest__.py +++ b/src/auto_archiver/modules/wacz_extractor_enricher/__manifest__.py @@ -1,7 +1,7 @@ { - "name": "WACZ Enricher", + "name": "WACZ Enricher (and Extractor)", "type": ["enricher", "extractor"], - "entry_point": "wacz_enricher::WaczExtractorEnricher", + "entry_point": "wacz_extractor_enricher::WaczExtractorEnricher", "requires_setup": True, "dependencies": { "python": [ diff --git a/src/auto_archiver/modules/wacz_enricher/wacz_enricher.py b/src/auto_archiver/modules/wacz_extractor_enricher/wacz_extractor_enricher.py similarity index 100% rename from src/auto_archiver/modules/wacz_enricher/wacz_enricher.py rename to src/auto_archiver/modules/wacz_extractor_enricher/wacz_extractor_enricher.py From cbea551876a6b73484624d55463ceca8ef04e479 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Thu, 20 Feb 2025 11:33:00 +0000 Subject: [PATCH 065/160] Better display name for wayback machine to emphasise it's typically used as an enricher --- .../modules/wayback_extractor_enricher/__manifest__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/auto_archiver/modules/wayback_extractor_enricher/__manifest__.py b/src/auto_archiver/modules/wayback_extractor_enricher/__manifest__.py index 4832265..38a5610 100644 --- a/src/auto_archiver/modules/wayback_extractor_enricher/__manifest__.py +++ b/src/auto_archiver/modules/wayback_extractor_enricher/__manifest__.py @@ -1,5 +1,5 @@ { - "name": "Wayback Machine Enricher", + "name": "Wayback Machine Enricher (and Extractor)", "type": ["enricher", "extractor"], "entry_point": "wayback_extractor_enricher::WaybackExtractorEnricher", "requires_setup": True, From 061f29c885a6ac71b1b6fa581cd5b2b8322c788e Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Thu, 20 Feb 2025 11:43:23 +0000 Subject: [PATCH 066/160] How-to on updating config file to version 0.13+ --- docs/source/how_to/new_config_format.md | 125 +++++++++++++++++++++++ docs/source/installation/installation.md | 2 +- 2 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 docs/source/how_to/new_config_format.md diff --git a/docs/source/how_to/new_config_format.md b/docs/source/how_to/new_config_format.md new file mode 100644 index 0000000..c37c87b --- /dev/null +++ b/docs/source/how_to/new_config_format.md @@ -0,0 +1,125 @@ +# Upgrading to 0.13 Configuration Format + +```{note} This how-to is only relevant for people who used Auto-Archiver before February 2025 (versions prior to 0.13). + +If you are new to Auto-Archiver, then you are already using the latest configuration format and this how-to is not relevant for you. +``` + +Version 0.13 of Auto Archiver has breaking changes in the configuration format, which means earlier configuration formats will not work without slight modifications. + +## How do I know if I need to update my configuration format? + +There are two simple ways to check if you need to update your format: + +1. When you try and run auto-archiver using your existing configuration file, you get an error like the following: + +```AssertionError: No feeders were configured. Make sure to set at least one feeder in your configuration file or on the command line (using --feeders) +``` + +2. Within your configuration file, you have a `feeder:` option. This is the old format. An example old format: +```{yaml} +steps: + feeder: gsheet_feeder +... +``` + +## Updating your configuration file + +To update your configuration file, you can either: + +### 1. Manually edit the configuration file and change the values. + +This is recommended if you want to keep all your old settings. Follow the steps below to change the relevant settings: + +1. Feeder & Formatter Steps Settings + +The feeder and formatter settings have been changed from a single string to a list. + +`steps.feeder (string)` → `steps.feeders (list)` +`steps.formatter (string)` → `steps.formatters (list)` + +Example: +```{yaml} +steps: + feeder: cli_feeder + - telegram_archiver + ... + formatter: html_formatter + +# the above should be changed to: +steps: + feeders: + - cli_feeder + ... + formatters: + - html_formatter +``` + +```{note} Auto-Archiver still only supports one feeder and formatter, but from v0.13 onwards they must be added to the configuration file as a list. +``` + +2. Extractor (formerly Archiver) Steps Settings + +With v0.13 of Auto Archiver, the `archivers` have been renamed to `extractors` to reflect the work they actually do - extract information from a URL. Change the configuration by renaming: + +`steps.archivers` → `steps.extractors` + +The names of the actual modules have also changed, so for any extractor modules you have enabled, you will need to rename the `archiver` part to `extractor`. Some examples: + +`telethon_archiver` → `telethon_extractor` +`wacz_archiver_enricher` → `wacz_extractor_enricher` +`vk_archiver` → `vk_extractor` + +Additionally, the `youtube_archiver` has been renamed to `generic_extractor` and should be considere the default/fallback extractor. Read more about the [generic extractor](../modules/autogen/extractor/generic_extractor.md). + +Example: +```{yaml} +steps: + ... + archivers: + - telethon_archiver + - youtube_archiver + - vk_archiver + +# renaming 'archiver' to 'extractor', and renaming the youtube_archiver the above config will become: +steps: + ... + extractors: + - telethon_extractor + - vk_extractor + - generic_extractor + +``` + +3. Redundant / Obsolete Modules + +With v0.13 of Auto Archiver, the following modules have been removed and their features have been built in to the generic_extractor: + +* `twitter_archiver` - use the `generic_extractor` for general extraction, or the `twitter_api_extractor` for API access. +* `tiktok_archiver` - use the `generic_extractor` to extract TikTok videos. + +If you have either of these set in your configuration under `steps:` you should remove them. + + + +### 2. Auto-generate a new config, then copy over your settings. + +Using this method, you can have Auto Archiver auto-generate a configuration file for you, then you can copy over the desired settings from your old config file. This is probably the easiest method and quickest to setup, but it may require some trial and error as you copy over your settings. + +First, move your existing `orchestration.yaml` file to a different folder or rename it. + +Then, you can generate a `simple` or `full` config using: + +```{code} console +>>> # generate a simple config +>>> auto-archiver +>>> # config will be written to orchestration.yaml +>>> +>>> # generate a full config +>>> auto-archiver --mode=full +>>> +``` + +After this, copy over any settings from your old config to the new config. + + diff --git a/docs/source/installation/installation.md b/docs/source/installation/installation.md index c728451..fdfc41d 100644 --- a/docs/source/installation/installation.md +++ b/docs/source/installation/installation.md @@ -15,7 +15,7 @@ There are 3 main ways to use the auto-archiver: 3. Developer Install: [see the developer guidelines](../development/developer_guidelines) -But **you always need a configuration/orchestration file**, which is where you'll configure where/what/how to archive. Make sure you read [](configurations). +But **you always need a configuration/orchestration file**, which is where you'll configure where/what/how to archive. Make sure you read the [](configurations) page. ## Installing with Docker From 40488e086915464c5a0b24dd287f2f5894c8d0c8 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Thu, 20 Feb 2025 11:50:29 +0000 Subject: [PATCH 067/160] Use 'Auto Archiver' naming for consistency. auto-archiver is reserved in the docs for when talking about the command line usage --- docs/source/core_modules.md | 4 ++-- docs/source/development/creating_modules.md | 4 ++-- docs/source/how_to.md | 2 +- docs/source/how_to/new_config_format.md | 6 +++--- docs/source/modules/feeder.md | 4 ++-- src/auto_archiver/core/enricher.py | 2 +- src/auto_archiver/modules/api_db/__manifest__.py | 6 +++--- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/source/core_modules.md b/docs/source/core_modules.md index 3a8e5ec..58eff08 100644 --- a/docs/source/core_modules.md +++ b/docs/source/core_modules.md @@ -1,8 +1,8 @@ # Module Documentation -These pages describe the core modules that come with `auto-archiver` and provide the main functionality for archiving websites on the internet. There are five core module types: +These pages describe the core modules that come with Auto Archiver and provide the main functionality for archiving websites on the internet. There are five core module types: -1. Feeders - these 'feed' information (the URLs) from various sources to the `auto-archiver` for processing +1. Feeders - these 'feed' information (the URLs) from various sources to the Auto Archiver for processing 2. Extractors - these 'extract' the page data for a given URL that is fed in by a feeder 3. Enrichers - these 'enrich' the data extracted in the previous step with additional information 4. Storage - these 'store' the data in a persistent location (on disk, Google Drive etc.) diff --git a/docs/source/development/creating_modules.md b/docs/source/development/creating_modules.md index 0950251..49468a4 100644 --- a/docs/source/development/creating_modules.md +++ b/docs/source/development/creating_modules.md @@ -1,6 +1,6 @@ # Creating Your Own Modules -Modules are what's used to extend `auto-archiver` to process different websites or media, and/or transform the data in a way that suits your needs. In most cases, the [Core Modules](../core_modules.md) should be sufficient for every day use, but the most common use-cases for making your own Modules include: +Modules are what's used to extend Auto Archiver to process different websites or media, and/or transform the data in a way that suits your needs. In most cases, the [Core Modules](../core_modules.md) should be sufficient for every day use, but the most common use-cases for making your own Modules include: 1. Extracting data from a website which doesn't work with the current core extractors. 2. Enriching or altering the data before saving with additional information that the core enrichers do not offer. @@ -21,7 +21,7 @@ When done, you should have a module structure as follows: │ └── awesome_extractor.py ``` -Check out the [core modules](https://github.com/bellingcat/auto-archiver/tree/main/src/auto_archiver/modules) in the `auto-archiver` repository for examples of the folder structure for real-world modules. +Check out the [core modules](https://github.com/bellingcat/auto-archiver/tree/main/src/auto_archiver/modules) in the Auto Archiver repository for examples of the folder structure for real-world modules. ## Populating the Manifest File diff --git a/docs/source/how_to.md b/docs/source/how_to.md index d8fe2e1..e2238dd 100644 --- a/docs/source/how_to.md +++ b/docs/source/how_to.md @@ -1,6 +1,6 @@ # How-To Guides -The follow pages contain helpful how-to guides for comon use cases of the Auto-Archiver. +The follow pages contain helpful how-to guides for common use cases of the Auto Archiver. --- ```{toctree} diff --git a/docs/source/how_to/new_config_format.md b/docs/source/how_to/new_config_format.md index c37c87b..c5c9f05 100644 --- a/docs/source/how_to/new_config_format.md +++ b/docs/source/how_to/new_config_format.md @@ -1,8 +1,8 @@ # Upgrading to 0.13 Configuration Format -```{note} This how-to is only relevant for people who used Auto-Archiver before February 2025 (versions prior to 0.13). +```{note} This how-to is only relevant for people who used Auto Archiver before February 2025 (versions prior to 0.13). -If you are new to Auto-Archiver, then you are already using the latest configuration format and this how-to is not relevant for you. +If you are new to Auto Archiver, then you are already using the latest configuration format and this how-to is not relevant for you. ``` Version 0.13 of Auto Archiver has breaking changes in the configuration format, which means earlier configuration formats will not work without slight modifications. @@ -55,7 +55,7 @@ steps: - html_formatter ``` -```{note} Auto-Archiver still only supports one feeder and formatter, but from v0.13 onwards they must be added to the configuration file as a list. +```{note} Auto Archiver still only supports one feeder and formatter, but from v0.13 onwards they must be added to the configuration file as a list. ``` 2. Extractor (formerly Archiver) Steps Settings diff --git a/docs/source/modules/feeder.md b/docs/source/modules/feeder.md index ce5f7ca..886786b 100644 --- a/docs/source/modules/feeder.md +++ b/docs/source/modules/feeder.md @@ -1,8 +1,8 @@ # Feeder Modules -Feeder modules are used to feed URLs into the `auto-archiver` for processing. Feeders can take these URLs from a variety of sources, such as a file, a database, or the command line. +Feeder modules are used to feed URLs into the Auto Archiver for processing. Feeders can take these URLs from a variety of sources, such as a file, a database, or the command line. -The default feeder is the command line feeder (`cli_feeder`), which allows you to input URLs directly into the `auto-archiver` from the command line. +The default feeder is the command line feeder (`cli_feeder`), which allows you to input URLs directly into `auto-archiver` from the command line. Command line feeder usage: ```{code} bash diff --git a/src/auto_archiver/core/enricher.py b/src/auto_archiver/core/enricher.py index 45e75d7..a862223 100644 --- a/src/auto_archiver/core/enricher.py +++ b/src/auto_archiver/core/enricher.py @@ -13,7 +13,7 @@ from abc import abstractmethod from auto_archiver.core import Metadata, BaseModule class Enricher(BaseModule): - """Base classes and utilities for enrichers in the Auto-Archiver system. + """Base classes and utilities for enrichers in the Auto Archiver system. Enricher modules must implement the `enrich` method to define their behavior. """ diff --git a/src/auto_archiver/modules/api_db/__manifest__.py b/src/auto_archiver/modules/api_db/__manifest__.py index 8359174..e67b31a 100644 --- a/src/auto_archiver/modules/api_db/__manifest__.py +++ b/src/auto_archiver/modules/api_db/__manifest__.py @@ -1,5 +1,5 @@ { - "name": "Auto-Archiver API Database", + "name": "Auto Archiver API Database", "type": ["database"], "entry_point": "api_db::AAApiDb", "requires_setup": True, @@ -39,7 +39,7 @@ }, }, "description": """ - Provides integration with the Auto-Archiver API for querying and storing archival data. + Provides integration with the Auto Archiver API for querying and storing archival data. ### Features - **API Integration**: Supports querying for existing archives and submitting results. @@ -49,6 +49,6 @@ - **Optional Storage**: Archives results conditionally based on configuration. ### Setup -Requires access to an Auto-Archiver API instance and a valid API token. +Requires access to an Auto Archiver API instance and a valid API token. """, } From eda359a1ef1d8670c2c1e24f29e5fa12696d9dae Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Thu, 20 Feb 2025 13:10:23 +0000 Subject: [PATCH 068/160] Fix json loader - it should go in 'validators' not 'utils' Fixes #214 --- docs/scripts/scripts.py | 2 +- src/auto_archiver/core/config.py | 2 +- src/auto_archiver/core/validators.py | 6 +++++- src/auto_archiver/modules/gsheet_feeder/__manifest__.py | 2 +- src/auto_archiver/modules/gsheet_feeder/gsheet_feeder.py | 5 ++--- .../modules/telethon_extractor/__manifest__.py | 2 +- src/auto_archiver/utils/misc.py | 4 ---- 7 files changed, 11 insertions(+), 12 deletions(-) diff --git a/docs/scripts/scripts.py b/docs/scripts/scripts.py index a5f2998..d6fd392 100644 --- a/docs/scripts/scripts.py +++ b/docs/scripts/scripts.py @@ -68,7 +68,7 @@ def generate_module_docs(): config_yaml = {} for key, value in manifest['configs'].items(): type = value.get('type', 'string') - if type == 'auto_archiver.utils.json_loader': + if type == 'json_loader': value['type'] = 'json' elif type == 'str': type = "string" diff --git a/src/auto_archiver/core/config.py b/src/auto_archiver/core/config.py index 2e5d273..425f96c 100644 --- a/src/auto_archiver/core/config.py +++ b/src/auto_archiver/core/config.py @@ -208,7 +208,7 @@ def read_yaml(yaml_filename: str) -> CommentedMap: pass if not config: - config = EMPTY_CONFIG + config = deepcopy(EMPTY_CONFIG) return config diff --git a/src/auto_archiver/core/validators.py b/src/auto_archiver/core/validators.py index b868ddf..0d3f01f 100644 --- a/src/auto_archiver/core/validators.py +++ b/src/auto_archiver/core/validators.py @@ -1,6 +1,7 @@ # used as validators for config values. Should raise an exception if the value is invalid. from pathlib import Path import argparse +import json def example_validator(value): if "example" not in value: @@ -16,4 +17,7 @@ def positive_number(value): def valid_file(value): if not Path(value).is_file(): raise argparse.ArgumentTypeError(f"File '{value}' does not exist.") - return value \ No newline at end of file + return value + +def json_loader(cli_val): + return json.loads(cli_val) \ No newline at end of file diff --git a/src/auto_archiver/modules/gsheet_feeder/__manifest__.py b/src/auto_archiver/modules/gsheet_feeder/__manifest__.py index 77026ea..d8b112d 100644 --- a/src/auto_archiver/modules/gsheet_feeder/__manifest__.py +++ b/src/auto_archiver/modules/gsheet_feeder/__manifest__.py @@ -35,7 +35,7 @@ "replaywebpage": "replaywebpage", }, "help": "names of columns in the google sheet (stringified JSON object)", - "type": "auto_archiver.utils.json_loader", + "type": "json_loader", }, "allow_worksheets": { "default": set(), diff --git a/src/auto_archiver/modules/gsheet_feeder/gsheet_feeder.py b/src/auto_archiver/modules/gsheet_feeder/gsheet_feeder.py index 8612d02..7cf4263 100644 --- a/src/auto_archiver/modules/gsheet_feeder/gsheet_feeder.py +++ b/src/auto_archiver/modules/gsheet_feeder/gsheet_feeder.py @@ -24,9 +24,8 @@ class GsheetsFeeder(Feeder): def setup(self) -> None: self.gsheets_client = gspread.service_account(filename=self.service_account) # TODO mv to validators - assert self.sheet or self.sheet_id, ( - "You need to define either a 'sheet' name or a 'sheet_id' in your manifest." - ) + if not (self.sheet or self.sheet_id): + raise ValueError("You need to define either a 'sheet' name or a 'sheet_id' in your manifest.") def open_sheet(self): if self.sheet: diff --git a/src/auto_archiver/modules/telethon_extractor/__manifest__.py b/src/auto_archiver/modules/telethon_extractor/__manifest__.py index e16d9db..458428b 100644 --- a/src/auto_archiver/modules/telethon_extractor/__manifest__.py +++ b/src/auto_archiver/modules/telethon_extractor/__manifest__.py @@ -18,7 +18,7 @@ "channel_invites": { "default": {}, "help": "(JSON string) private channel invite links (format: t.me/joinchat/HASH OR t.me/+HASH) and (optional but important to avoid hanging for minutes on startup) channel id (format: CHANNEL_ID taken from a post url like https://t.me/c/CHANNEL_ID/1), the telegram account will join any new channels on setup", - "type": "auto_archiver.utils.json_loader", + "type": "json_loader", } }, "description": """ diff --git a/src/auto_archiver/utils/misc.py b/src/auto_archiver/utils/misc.py index 108deae..e46a93d 100644 --- a/src/auto_archiver/utils/misc.py +++ b/src/auto_archiver/utils/misc.py @@ -59,10 +59,6 @@ def random_str(length: int = 32) -> str: return str(uuid.uuid4()).replace("-", "")[:length] -def json_loader(cli_val): - return json.loads(cli_val) - - def calculate_file_hash(filename: str, hash_algo = hashlib.sha256, chunksize: int = 16000000) -> str: hash = hash_algo() with open(filename, "rb") as f: From 4174285898d1ffd8b3e903cea18984dc6cc08ec9 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Thu, 20 Feb 2025 13:13:01 +0000 Subject: [PATCH 069/160] Fix unit tests --- src/auto_archiver/modules/gsheet_feeder/gsheet_feeder.py | 2 +- tests/enrichers/test_wacz_enricher.py | 2 +- tests/feeders/test_gsheet_feeder.py | 2 +- tests/test_orchestrator.py | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/auto_archiver/modules/gsheet_feeder/gsheet_feeder.py b/src/auto_archiver/modules/gsheet_feeder/gsheet_feeder.py index 7cf4263..ea724e7 100644 --- a/src/auto_archiver/modules/gsheet_feeder/gsheet_feeder.py +++ b/src/auto_archiver/modules/gsheet_feeder/gsheet_feeder.py @@ -24,7 +24,7 @@ class GsheetsFeeder(Feeder): def setup(self) -> None: self.gsheets_client = gspread.service_account(filename=self.service_account) # TODO mv to validators - if not (self.sheet or self.sheet_id): + if not self.sheet and not self.sheet_id: raise ValueError("You need to define either a 'sheet' name or a 'sheet_id' in your manifest.") def open_sheet(self): diff --git a/tests/enrichers/test_wacz_enricher.py b/tests/enrichers/test_wacz_enricher.py index d55733d..ceab83b 100644 --- a/tests/enrichers/test_wacz_enricher.py +++ b/tests/enrichers/test_wacz_enricher.py @@ -18,7 +18,7 @@ def wacz_enricher(setup_module, mock_binary_dependencies): "socks_proxy_port": None, "proxy_server": None, } - wacz = setup_module("wacz_enricher", configs) + wacz = setup_module("wacz_extractor_enricher", configs) return wacz diff --git a/tests/feeders/test_gsheet_feeder.py b/tests/feeders/test_gsheet_feeder.py index 7c5f501..ef150d1 100644 --- a/tests/feeders/test_gsheet_feeder.py +++ b/tests/feeders/test_gsheet_feeder.py @@ -9,7 +9,7 @@ from auto_archiver.core import Metadata, Feeder def test_setup_without_sheet_and_sheet_id(setup_module, mocker): # Ensure setup() raises AssertionError if neither sheet nor sheet_id is set. mocker.patch("gspread.service_account") - with pytest.raises(AssertionError): + with pytest.raises(ValueError): setup_module( "gsheet_feeder", {"service_account": "dummy.json", "sheet": None, "sheet_id": None}, diff --git a/tests/test_orchestrator.py b/tests/test_orchestrator.py index ee1b9af..752adb8 100644 --- a/tests/test_orchestrator.py +++ b/tests/test_orchestrator.py @@ -165,10 +165,10 @@ def test_load_settings_for_module_from_commandline(orchestrator, test_args): def test_multiple_orchestrator(test_args): - o1_args = test_args + ["--feeders", "gsheet_feeder"] + o1_args = test_args + ["--feeders", "gsheet_feeder", "--gsheet_feeder.service_account", "tests/data/test_service_account.json"] o1 = ArchivingOrchestrator() - with pytest.raises(AssertionError) as exit_error: + with pytest.raises(ValueError) as exit_error: # this should fail because the gsheet_feeder requires a sheet_id / sheet o1.setup(o1_args) From 0bec71d203c11e111ef095044d541924cf2d816c Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Thu, 20 Feb 2025 15:33:50 +0000 Subject: [PATCH 070/160] Finish how to on authentication --- docs/source/how_to/authentication_how_to.md | 112 +++++++++++++++++++- docs/source/how_to/extract_cookies.png | Bin 0 -> 967176 bytes docs/source/how_to/new_config_format.md | 35 +++--- docs/source/installation/installation.md | 2 +- docs/source/modules/database.md | 2 +- docs/source/modules/enricher.md | 2 +- docs/source/modules/extractor.md | 2 +- docs/source/modules/feeder.md | 2 +- docs/source/modules/formatter.md | 2 +- docs/source/modules/storage.md | 2 +- src/auto_archiver/core/base_module.py | 2 + src/auto_archiver/core/orchestrator.py | 1 + 12 files changed, 136 insertions(+), 28 deletions(-) create mode 100644 docs/source/how_to/extract_cookies.png diff --git a/docs/source/how_to/authentication_how_to.md b/docs/source/how_to/authentication_how_to.md index ebf0f0c..8994271 100644 --- a/docs/source/how_to/authentication_how_to.md +++ b/docs/source/how_to/authentication_how_to.md @@ -1,6 +1,110 @@ -# How to login (authenticate) to websites +# Logging in to sites -This how-to guide shows you how you can add authentication to Auto Archiver for a site you are trying to archive. In this example, we will authenticate on use Twitter/X.com using cookies, and on XXXX using username/password. +This how-to guide shows you how you can use various authentication methods to allow you to login to a site you are trying to archive. This is useful for websites that require a user to be logged in to browse them, or for sites that restrict bots. -```{note} This page is still under construction 🚧 -``` \ No newline at end of file +In this How-To, we will authenticate on use Twitter/X.com using cookies, and on XXXX using username/password. + + + +## Using cookies to authenticate on Twitter/X + +It can be useful to archive tweets after logging in, since some tweets are only visible to authenticated users. One case is Tweets marked as 'Sensitive'. + +Take this tweet as an example: [https://x.com/SozinhoRamalho/status/1876710769913450647](https://x.com/SozinhoRamalho/status/1876710769913450647) + +This tweet has been marked as sensitive, so a normal run of Auto Archiver without a logged in session will fail to extract the tweet: + +```{code-block} console +:emphasize-lines: 3,4,5,6 + +>>> auto-archiver https://x.com/SozinhoRamalho/status/1876710769913450647 ✭ ✱ + ... +ERROR: [twitter] 1876710769913450647: NSFW tweet requires authentication. Use --cookies, +--cookies-from-browser, --username and --password, --netrc-cmd, or --netrc (twitter) to + provide account credentials. See https://github.com/yt-dlp/yt-dlp/wiki/FAQ#how-do-i-pass-cookies-to-yt-dlp + for how to manually pass cookies +[twitter] 1876710769913450647: Downloading guest token +[twitter] 1876710769913450647: Downloading GraphQL JSON +2025-02-20 15:06:13.362 | ERROR | auto_archiver.modules.generic_extractor.generic_extractor:download_for_extractor:248 - Error downloading metadata for post: NSFW tweet requires authentication. Use --cookies, --cookies-from-browser, --username and --password, --netrc-cmd, or --netrc (twitter) to provide account credentials. See https://github.com/yt-dlp/yt-dlp/wiki/FAQ#how-do-i-pass-cookies-to-yt-dlp for how to manually pass cookies +[generic] Extracting URL: https://x.com/SozinhoRamalho/status/1876710769913450647 +[generic] 1876710769913450647: Downloading webpage +WARNING: [generic] Falling back on generic information extractor +[generic] 1876710769913450647: Extracting information +ERROR: Unsupported URL: https://x.com/SozinhoRamalho/status/1876710769913450647 +2025-02-20 15:06:13.744 | INFO | auto_archiver.core.orchestrator:archive:483 - Trying extractor telegram_extractor for https://x.com/SozinhoRamalho/status/1876710769913450647 +2025-02-20 15:06:13.744 | SUCCESS | auto_archiver.modules.console_db.console_db:done:23 - DONE Metadata(status='nothing archived', metadata={'_processed_at': datetime.datetime(2025, 2, 20, 15, 6, 12, 473979, tzinfo=datetime.timezone.utc), 'url': 'https://x.com/SozinhoRamalho/status/1876710769913450647'}, media=[]) +... +``` + +To get round this limitation, we can use **cookies** (information about a logged in user) to mimic being logged in to Twitter. There are two ways to pass cookies to Auto Archiver. One is from a file, and the other is from a browser profile on your computer. + +In this tutorial, we will export the Twitter cookies from our browser and add them to Auto Archiver + +**1. Installing a cookie exporter extension** + +First, we need to install an extension in our browser to export the cookies for a certain site. The [FAQ on yt-dlp](https://github.com/yt-dlp/yt-dlp/wiki/FAQ#how-do-i-pass-cookies-to-yt-dlp) provides some suggestions: Get [cookies.txt LOCALLY](https://chromewebstore.google.com/detail/get-cookiestxt-locally/cclelndahbckbenkjhflpdbgdldlbecc) for Chrome or [cookies.txt](https://addons.mozilla.org/en-US/firefox/addon/cookies-txt/) for Firefox. + +**2. Export the cookies** + +```{note} See the note [here](../installation/authentication.md#recommendations-for-authentication) on why you shouldn't use your own personal account for achiving. +``` + +Once the extension is installed in your preferred browser, login to Twitter in this browser, and then activate the extension and export the cookies. You can choose to export all your cookies for your browser, or just cookies for this specific site. In the image below, we're only exporting cookies for Twitter/x.com: + +![extract cookies](extract_cookies.png) + + +**3. Adding the cookies file to Auto Archiver** + +You now will have a file called `cookies.txt` (tip: name it `twitter_cookies.txt` if you only exported cookies for Twitter), which needs to be added to Auto Archiver. + +Do this by going into your Auto Archiver configuration file, and editing the `authentication` section. We will add the `cookies_file` option for the site `x.com,twitter.com`. + +```{note} For websites that have multiple URLs (like x.com and twitter.com) you can 'reuse' the same login information without duplicating it using a comma separated list of domain names. +``` + +I've saved my `twitter_cookies.txt` file in a `secrets` folder, so here's how my authentication section looks now: + +```{code} yaml +:caption: orchestration.yaml + +... + +authentication: + x.com,twitter.com: + cookies_file: secrets/twitter_cookies.txt +... +``` + +**4. Re-run your archiving with the cookies enabled** + +Now, the next time we re-run Auto Archiver, the cookies from our logged-in session will be used by Auto Archiver, and restricted/sensitive tweets can be downloaded! + +```{code} console +>>> auto-archiver https://x.com/SozinhoRamalho/status/1876710769913450647 ✭ ✱ ◼ +... +2025-02-20 15:27:46.785 | WARNING | auto_archiver.modules.console_db.console_db:started:13 - STARTED Metadata(status='no archiver', metadata={'_processed_at': datetime.datetime(2025, 2, 20, 15, 27, 46, 785304, tzinfo=datetime.timezone.utc), 'url': 'https://x.com/SozinhoRamalho/status/1876710769913450647'}, media=[]) +2025-02-20 15:27:46.785 | INFO | auto_archiver.core.orchestrator:archive:483 - Trying extractor generic_extractor for https://x.com/SozinhoRamalho/status/1876710769913450647 +[twitter] Extracting URL: https://x.com/SozinhoRamalho/status/1876710769913450647 +... +2025-02-20 15:27:53.134 | INFO | auto_archiver.modules.local_storage.local_storage:upload:26 - ./local_archive/https-x-com-sozinhoramalho-status-1876710769913450647/06e8bacf27ac4bb983bf6280.html +2025-02-20 15:27:53.135 | SUCCESS | auto_archiver.modules.console_db.console_db:done:23 - DONE Metadata(status='yt-dlp_Twitter: success', +metadata={'_processed_at': datetime.datetime(2025, 2, 20, 15, 27, 48, 564738, tzinfo=datetime.timezone.utc), 'url': +'https://x.com/SozinhoRamalho/status/1876710769913450647', 'title': 'ignore tweet, testing sensitivity warning nudity https://t.co/t3u0hQsSB1', +... +``` + + +### Finishing Touches + +You've now successfully exported your cookies from a logged-in session in your browser, and used them to authenticate with Twitter and download a sensitive tweet. Congratulations! + +Finally,Some important things to remember: + +1. It's best not to use your own personal account for archiving. [Here's why](../installation/authentication.md#recommendations-for-authentication). +2. Cookies can be short-lived, so may need updating. Sometimes, a website session may 'expire' or a website may force you to login again. In these instances, you'll need to repeat the export step (step 2) after logging in again to update your cookies. + +## Authenticating on XXXX site with username/password + +```{note} This section is still under construction 🚧 +``` diff --git a/docs/source/how_to/extract_cookies.png b/docs/source/how_to/extract_cookies.png new file mode 100644 index 0000000000000000000000000000000000000000..73b791709623a29f289d6dda4dcbdc3bcec9108a GIT binary patch literal 967176 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

e4rp>7qmrUl>$Q=wWvcC=+mG4# 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

oAr-dEYG!JX%X7 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

b{Zbg_wQCu&V#i3Tg{g<5?Pfv=VP#{*n0l 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

bZ>+9S%AT6Po-+#SPmy@s!wg&z0Ge3;1E3 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 z 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 Date: Thu, 20 Feb 2025 15:45:32 +0000 Subject: [PATCH 071/160] Allow disabling logging in auto_archiver with logging: enabled: false --- src/auto_archiver/core/orchestrator.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/auto_archiver/core/orchestrator.py b/src/auto_archiver/core/orchestrator.py index c2dbb58..274fa9e 100644 --- a/src/auto_archiver/core/orchestrator.py +++ b/src/auto_archiver/core/orchestrator.py @@ -231,14 +231,20 @@ class ArchivingOrchestrator: self.basic_parser.exit() def setup_logging(self, config): + + logging_config = config['logging'] + + if logging_config.get('enabled', True) is False: + # disabled logging settings, they're set on a higher level + logger.disable('auto_archiver') + return + # setup loguru logging try: logger.remove(0) # remove the default logger except ValueError: pass - logging_config = config['logging'] - # add other logging info if self.logger_id is None: # note - need direct comparison to None since need to consider falsy value 0 self.logger_id = logger.add(sys.stderr, level=logging_config['level']) From 77212e8e3f4236cd9e05121d35ff2902beb92c82 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Thu, 20 Feb 2025 15:45:48 +0000 Subject: [PATCH 072/160] Finishing touches to the how-tos --- docs/source/how_to/logging.md | 18 +++++++++++++++++- docs/source/how_to/new_config_format.md | 19 ++++++++++--------- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/docs/source/how_to/logging.md b/docs/source/how_to/logging.md index 7ae3b18..d88882d 100644 --- a/docs/source/how_to/logging.md +++ b/docs/source/how_to/logging.md @@ -1,4 +1,4 @@ -# Logging +# Keeping Logs Auto Archiver's logs can be helpful for debugging problematic archiving processes. This guide shows you how to use the logs to @@ -6,6 +6,22 @@ Auto Archiver's logs can be helpful for debugging problematic archiving processe Logging settings can be set on the command line or using the orchestration config file ([learn more](../installation/configuration)). A special `logging` section defines the logging options. +#### Enabling or Disabling Logging + +Logging to the console is enabled by default. If you want to globally disable Auto Archiver's logging, then you can set `enabled: false` in your `logging` config: + +```{code} yaml + +... +logging: + enabled: false +... +``` + +```{note} +This will disable all logs from Auto Archiver, but it does not disable logs for other tools that the Auto Archiver uses (for example: yt-dlp, firefox or ffmpeg). These logs will still appear in your console. +``` + #### Logging Level There are 7 logging levels in total, with 4 commonly used levels. They are: `DEBUG`, `INFO`, `WARNING` and `ERROR`. diff --git a/docs/source/how_to/new_config_format.md b/docs/source/how_to/new_config_format.md index 54ddb8c..6c12276 100644 --- a/docs/source/how_to/new_config_format.md +++ b/docs/source/how_to/new_config_format.md @@ -42,10 +42,11 @@ The feeder and formatter settings have been changed from a single string to a li - `steps.formatter (string)` → `steps.formatters (list)` Example: -```{yaml} + +```{code} yaml + steps: feeder: cli_feeder - - telegram_archiver ... formatter: html_formatter @@ -54,8 +55,8 @@ steps: feeders: - cli_feeder ... - formatters: - - html_formatter + formatters: + - html_formatter ``` ```{note} Auto Archiver still only supports one feeder and formatter, but from v0.13 onwards they must be added to the configuration file as a list. @@ -63,7 +64,7 @@ steps: #### b) Extractor (formerly Archiver) Steps Settings -With v0.13 of Auto Archiver, the `archivers` have been renamed to `extractors` to reflect the work they actually do - extract information from a URL. Change the configuration by renaming: +With v0.13 of Auto Archiver, `archivers` have been renamed to `extractors` to better reflect what they actually do - extract information from a URL. Change the configuration by renaming: - `steps.archivers` → `steps.extractors` @@ -74,10 +75,10 @@ The names of the actual modules have also changed, so for any extractor modules - `wayback_archiver_enricher` → `wayback_extractor_enricher` - `vk_archiver` → `vk_extractor` -Additionally, the `youtube_archiver` has been renamed to `generic_extractor` and should be considere the default/fallback extractor. Read more about the [generic extractor](../modules/autogen/extractor/generic_extractor.md). +Additionally, the `youtube_archiver` has been renamed to `generic_extractor` as it is considered the default/fallback extractor. Read more about the [generic extractor](../modules/autogen/extractor/generic_extractor.md). Example: -```{yaml} +```{code} yaml steps: ... archivers: @@ -115,10 +116,10 @@ Then, you can generate a `simple` or `full` config using: >>> # generate a simple config >>> auto-archiver >>> # config will be written to orchestration.yaml ->>> +>>> >>> # generate a full config >>> auto-archiver --mode=full ->>> +>>> ``` After this, copy over any settings from your old config to the new config. From 091a19e25c57306555952dbd479e2aa9551653f3 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Fri, 21 Feb 2025 16:52:30 +0000 Subject: [PATCH 073/160] Further docs improvements/tidy ups --- docs/scripts/scripts.py | 27 ++++++-- docs/source/bc.png | Bin 0 -> 43453 bytes docs/source/conf.py | 15 +++- docs/source/index.md | 4 +- docs/source/installation/configurations.md | 15 ++-- docs/source/installation/installation.md | 77 ++++++--------------- docs/source/installation/requirements.md | 14 ++++ docs/source/installation/setup.md | 76 ++++++++++++++++++++ src/auto_archiver/core/config.py | 2 +- 9 files changed, 159 insertions(+), 71 deletions(-) create mode 100644 docs/source/bc.png create mode 100644 docs/source/installation/requirements.md create mode 100644 docs/source/installation/setup.md diff --git a/docs/scripts/scripts.py b/docs/scripts/scripts.py index d6fd392..66ba14d 100644 --- a/docs/scripts/scripts.py +++ b/docs/scripts/scripts.py @@ -3,6 +3,7 @@ from pathlib import Path from auto_archiver.core.module import ModuleFactory from auto_archiver.core.base_module import BaseModule from ruamel.yaml import YAML +from ruamel.yaml.comments import CommentedMap import io MODULES_FOLDER = Path(__file__).parent.parent.parent.parent / "src" / "auto_archiver" / "modules" @@ -30,6 +31,7 @@ steps: ... {config_string} + """ def generate_module_docs(): @@ -38,8 +40,9 @@ def generate_module_docs(): modules_by_type = {} header_row = "| " + " | ".join(TABLE_HEADER) + "|\n" + "| --- " * len(TABLE_HEADER) + "|\n" - configs_cheatsheet = "\n## Configuration Options\n" - configs_cheatsheet += header_row + global_table = "\n## Configuration Options\n" + header_row + + global_yaml = yaml.load("""\n# Module configuration\nplaceholder: {}""") for module in sorted(ModuleFactory().available_modules(), key=lambda x: (x.requires_setup, x.name)): # generate the markdown file from the __manifest__.py file. @@ -66,6 +69,11 @@ def generate_module_docs(): config_table = header_row config_yaml = {} + + global_yaml[module.name] = CommentedMap() + global_yaml.yaml_set_comment_before_after_key(module.name, f"\n\n{module.display_name} configuration options") + + for key, value in manifest['configs'].items(): type = value.get('type', 'string') if type == 'json_loader': @@ -75,10 +83,16 @@ def generate_module_docs(): default = value.get('default', '') config_yaml[key] = default + + global_yaml[module.name][key] = default + + if value.get('help', ''): + global_yaml[module.name].yaml_add_eol_comment(value.get('help', ''), key) + help = "**Required**. " if value.get('required', False) else "Optional. " help += value.get('help', '') config_table += f"| `{module.name}.{key}` | {help} | {value.get('default', '')} | {type} |\n" - configs_cheatsheet += f"| `{module.name}.{key}` | {help} | {default} | {type} |\n" + global_table += f"| `{module.name}.{key}` | {help} | {default} | {type} |\n" readme_str += "\n## Configuration Options\n" readme_str += "\n### YAML\n" @@ -103,8 +117,13 @@ def generate_module_docs(): f.write(readme_str) generate_index(modules_by_type) + del global_yaml['placeholder'] + global_string = io.BytesIO() + global_yaml = yaml.dump(global_yaml, global_string) + global_string = global_string.getvalue().decode('utf-8') + global_yaml = f"```yaml\n{global_string}\n```" with open(SAVE_FOLDER / "configs_cheatsheet.md", "w") as f: - f.write(configs_cheatsheet) + f.write("### Configuration File\n" + global_yaml + "\n### Command Line\n" + global_table) def generate_index(modules_by_type): diff --git a/docs/source/bc.png b/docs/source/bc.png new file mode 100644 index 0000000000000000000000000000000000000000..766529b5ddd3a1db2a2eebbddbab6b0700434b62 GIT binary patch literal 43453 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 nR}b;8(O!X?|3{rwG3Qz{kqc?pU$;ndtujxF!3R literal 0 HcmV?d00001 diff --git a/docs/source/conf.py b/docs/source/conf.py index 5b1ad9b..ee6416e 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -3,9 +3,11 @@ import sys import os from importlib.metadata import metadata +from datetime import datetime sys.path.append(os.path.abspath('../scripts')) from scripts import generate_module_docs +from auto_archiver.version import __version__ # -- Project Hooks ----------------------------------------------------------- # convert the module __manifest__.py files into markdown files @@ -15,7 +17,8 @@ generate_module_docs() # -- Project information ----------------------------------------------------- package_metadata = metadata("auto-archiver") project = package_metadata["name"] -authors = "Bellingcat" +copyright = str(datetime.now().year) +author = "Bellingcat" release = package_metadata["version"] language = 'en' @@ -32,7 +35,7 @@ extensions = [ ] templates_path = ['_templates'] -exclude_patterns = [] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", ""] # -- AutoAPI Configuration --------------------------------------------------- @@ -76,6 +79,14 @@ source_suffix = { html_theme = 'sphinx_book_theme' html_static_path = ["../_static"] html_css_files = ["custom.css"] +html_title = f"Auto Archiver v{__version__}" +html_logo = "bc.png" +html_theme_options = { + "repository_url": "https://github.com/bellingcat/auto-archiver", + "use_repository_button": True, +} + + copybutton_prompt_text = r">>> |\.\.\." copybutton_prompt_is_regexp = True diff --git a/docs/source/index.md b/docs/source/index.md index 53185ee..74b7969 100644 --- a/docs/source/index.md +++ b/docs/source/index.md @@ -8,10 +8,10 @@ :caption: Contents: Overview -contributing -installation/installation +installation/setup core_modules.md how_to +contributing development/developer_guidelines autoapi/index.rst ``` \ No newline at end of file diff --git a/docs/source/installation/configurations.md b/docs/source/installation/configurations.md index 3e9cd08..e3aa76e 100644 --- a/docs/source/installation/configurations.md +++ b/docs/source/installation/configurations.md @@ -1,13 +1,18 @@ # Configuration -This section of the documentation provides guidelines for configuring the tool. +The recommended way to configure auto-archiver for first-time users is to [run the Auto Archiver](setup.md#running) and have it auto-generate a default configuration for you. Then, if needed, you can edit the configuration file using one of the following methods. -## Configuring using a file -The recommended way to configure auto-archiver for long-term and deployed projects is a configuration file, typically called `orchestration.yaml`. This is a YAML file containing all the settings for your entire workflow. +## 1. Configuration file -The structure of orchestration file is split into 2 parts: `steps` (what [steps](../flow_overview.md) to use) and `configurations` (settings for different modules), here's a simplification: +The configuration file is typically called `orchestration.yaml` and stored in the `secrets` folder on your desktop. The configuration file contains all the settings for your entire Auto Archiver workflow in one easy-to-find place. + +If you want to have Auto Archiver run with the recommended 'basic' setup, + +### Advanced Configuration + +The structure of orchestration file is split into 2 parts: `steps` (what [steps](../flow_overview.md) to use) and `configurations` (settings for individual modules). A default `orchestration.yaml` will be created for you the first time you run auto-archiver (without any arguments). Here's what it looks like: @@ -21,7 +26,7 @@ A default `orchestration.yaml` will be created for you the first time you run au -## Configuring from the Command Line +## 2. Command Line configuration You can run auto-archiver directly from the command line, without the need for a configuration file, command line arguments are parsed using the format `module_name.config_value`. For example, a config value of `api_key` in the `instagram_extractor` module would be passed on the command line with the flag `--instagram_extractor.api_key=API_KEY`. diff --git a/docs/source/installation/installation.md b/docs/source/installation/installation.md index bd03e7e..eff0720 100644 --- a/docs/source/installation/installation.md +++ b/docs/source/installation/installation.md @@ -1,81 +1,44 @@ -# Installing Auto Archiver +# Installation -```{toctree} -:maxdepth: 1 -:hidden: +There are 3 main ways to use the auto-archiver. We recommend the 'docker' method for most uses. This installs all the requirements in one command. -configurations.md -authentication.md -config_cheatsheet.md -``` - -There are 3 main ways to use the auto-archiver: -1. Easiest: [via docker](#installing-with-docker) +1. Easiest (recommended): [via docker](#installing-with-docker) 2. Local Install: [using pip](#installing-locally-with-pip) 3. Developer Install: [see the developer guidelines](../development/developer_guidelines) - -But **you always need a configuration/orchestration file**, which is where you'll configure where/what/how to archive. Make sure you read the [](configurations) page. - - -## Installing with Docker +## 1. Installing with Docker [![dockeri.co](https://dockerico.blankenship.io/image/bellingcat/auto-archiver)](https://hub.docker.com/r/bellingcat/auto-archiver) -Docker works like a virtual machine running inside your computer, it isolates everything and makes installation simple. Since it is an isolated environment when you need to pass it your orchestration file or get downloaded media out of docker you will need to connect folders on your machine with folders inside docker with the `-v` volume flag. +Docker works like a virtual machine running inside your computer, making installation simple. You'll need to first set up Docker, and then download the Auto Archiver 'image': -1. Install [docker](https://docs.docker.com/get-docker/) -2. Pull the auto-archiver docker [image](https://hub.docker.com/r/bellingcat/auto-archiver) with `docker pull bellingcat/auto-archiver` -3. Run the docker image locally in a container: `docker run --rm -v $PWD/secrets:/app/secrets -v $PWD/local_archive:/app/local_archive bellingcat/auto-archiver --config secrets/orchestration.yaml` breaking this command down: - 1. `docker run` tells docker to start a new container (an instance of the image) - 2. `--rm` makes sure this container is removed after execution (less garbage locally) - 3. `-v $PWD/secrets:/app/secrets` - your secrets folder - 1. `-v` is a volume flag which means a folder that you have on your computer will be connected to a folder inside the docker container - 2. `$PWD/secrets` points to a `secrets/` folder in your current working directory (where your console points to), we use this folder as a best practice to hold all the secrets/tokens/passwords/... you use - 3. `/app/secrets` points to the path the docker container where this image can be found - 4. `-v $PWD/local_archive:/app/local_archive` - (optional) if you use local_storage - 1. `-v` same as above, this is a volume instruction - 2. `$PWD/local_archive` is a folder `local_archive/` in case you want to archive locally and have the files accessible outside docker - 3. `/app/local_archive` is a folder inside docker that you can reference in your orchestration.yml file +**a) Download and install docker** -### Example invocations +Go to the [Docker website](https://docs.docker.com/get-docker/) and download right version for your operating system. -The invocations below will run the auto-archiver Docker image using a configuration file that you have specified +**b) Pull the Auto Archiver docker image** + +Open your command line terminal, and copy-paste / type: ```bash -# all the configurations come from ./secrets/orchestration.yaml -docker run --rm -v $PWD/secrets:/app/secrets -v $PWD/local_archive:/app/local_archive bellingcat/auto-archiver --config secrets/orchestration.yaml -# uses the same configurations but for another google docs sheet -# with a header on row 2 and with some different column names -# notice that columns is a dictionary so you need to pass it as JSON and it will override only the values provided -docker run --rm -v $PWD/secrets:/app/secrets -v $PWD/local_archive:/app/local_archive bellingcat/auto-archiver --config secrets/orchestration.yaml --gsheet_feeder.sheet="use it on another sheets doc" --gsheet_feeder.header=2 --gsheet_feeder.columns='{"url": "link"}' -# all the configurations come from orchestration.yaml and specifies that s3 files should be private -docker run --rm -v $PWD/secrets:/app/secrets -v $PWD/local_archive:/app/local_archive bellingcat/auto-archiver --config secrets/orchestration.yaml --s3_storage.private=1 +docker pull bellingcat/auto-archiver ``` -## Installing Locally with Pip +This will download the docker image, which may take a while. + +That's it, all done! You're now ready to set up [your configuration file](configurations.md). Or, if you want to use the recommended defaults, then you can [run Auto Archiver immediately](setup.md#running-a-docker-install). + +------------ + +## 2. Installing Locally with Pip 1. Make sure you have python 3.10 or higher installed 2. Install the package with your preferred package manager: `pip/pipenv/conda install auto-archiver` or `poetry add auto-archiver` 3. Test it's installed with `auto-archiver --help` -4. Install other local dependency requirements (for ) -5. Run it with your orchestration file and pass any flags you want in the command line `auto-archiver --config secrets/orchestration.yaml` if your orchestration file is inside a `secrets/`, which we advise +4. Install other local dependency requirements (for example `ffmpeg`, `firefox`) -### Example invocations - -Once all your [local requirements](#installing-local-requirements) are correctly installed, the - -```bash -# all the configurations come from ./secrets/orchestration.yaml -auto-archiver --config secrets/orchestration.yaml -# uses the same configurations but for another google docs sheet -# with a header on row 2 and with some different column names -# notice that columns is a dictionary so you need to pass it as JSON and it will override only the values provided -auto-archiver --config secrets/orchestration.yaml --gsheet_feeder.sheet="use it on another sheets doc" --gsheet_feeder.header=2 --gsheet_feeder.columns='{"url": "link"}' -# all the configurations come from orchestration.yaml and specifies that s3 files should be private -auto-archiver --config secrets/orchestration.yaml --s3_storage.private=1 -``` +After this, you're ready to set up your [your configuration file](configurations.md), or if you want to use the recommended defaults, then you can [run Auto Archiver immediately](setup.md#running-a-local-install). ### Installing Local Requirements diff --git a/docs/source/installation/requirements.md b/docs/source/installation/requirements.md new file mode 100644 index 0000000..b820272 --- /dev/null +++ b/docs/source/installation/requirements.md @@ -0,0 +1,14 @@ +# Requirements + +Using the Auto Archiver is very simple, but ideally you have some familiarity with using the command line to run programs. ([Command line crash course](https://developer.mozilla.org/en-US/docs/Learn_web_development/Getting_started/Environment_setup/Command_line)). + +### System Requirements + +* Auto Archiver works on any Windows, macOS and Linux computer +* If you're using the **local install** method, then you should make sure to have python3.10+ installed + +### Storage Requirements + +By default, Auto Archiver uses your local computer storage for any downloaded media (videos, images etc.). If you're downloading large files, this may take up a lot of your local computer's space (more than 5GB of space). + +If your storage space is limited, then you may want to set up an [alternative storage method](../modules/storage.md) for your media. \ No newline at end of file diff --git a/docs/source/installation/setup.md b/docs/source/installation/setup.md new file mode 100644 index 0000000..8d1a6f5 --- /dev/null +++ b/docs/source/installation/setup.md @@ -0,0 +1,76 @@ +# Getting Started + +```{toctree} +:maxdepth: 1 +:hidden: + +installation.md +configurations.md +authentication.md +requirements.md +config_cheatsheet.md +``` + +## Getting Started + +To get started with Auto Archiver, there are 3 main steps you need to complete. + +1. [Install Auto Archiver](installation.md) +2. [Setup up your configuration](configurations.md) (if you are ok with the default settings, you can skip this step) +3. Run the archiving process + +The way you run the Auto Archiver depends on how you installed it (docker install or local install) + +### Running a Docker Install + +If you installed Auto Archiver using docker, open up your terminal, and copy-paste / type the following command: + +```bash +docker run --rm -v $PWD/secrets:/app/secrets -v $PWD/local_archive:/app/local_archive bellingcat/auto-archiver + ``` + +breaking this command down: + 1. `docker run` tells docker to start a new container (an instance of the image) + 2. `--rm` makes sure this container is removed after execution (less garbage locally) + 3. `-v $PWD/secrets:/app/secrets` - your secrets folder with settings + 1. `-v` is a volume flag which means a folder that you have on your computer will be connected to a folder inside the docker container + 2. `$PWD/secrets` points to a `secrets/` folder in your current working directory (where your console points to), we use this folder as a best practice to hold all the secrets/tokens/passwords/... you use + 3. `/app/secrets` points to the path the docker container where this image can be found + 4. `-v $PWD/local_archive:/app/local_archive` - (optional) if you use local_storage + 1. `-v` same as above, this is a volume instruction + 2. `$PWD/local_archive` is a folder `local_archive/` in case you want to archive locally and have the files accessible outside docker + 3. `/app/local_archive` is a folder inside docker that you can reference in your orchestration.yml file + +### Example invocations + +The invocations below will run the auto-archiver Docker image using a configuration file that you have specified + +```bash +# Have auto-archiver run with the default settings, generating a settings file in ./secrets/orchestration.yaml +docker run --rm -v $PWD/secrets:/app/secrets -v $PWD/local_archive:/app/local_archive bellingcat/auto-archiver + +# uses the same configuration, but with the `gsheet_feeder`, a header on row 2 and with some different column names +# notice that columns is a dictionary so you need to pass it as JSON and it will override only the values provided +docker run --rm -v $PWD/secrets:/app/secrets -v $PWD/local_archive:/app/local_archive bellingcat/auto-archiver --feeders=gsheet_feeder --gsheet_feeder.sheet="use it on another sheets doc" --gsheet_feeder.header=2 --gsheet_feeder.columns='{"url": "link"}' +# Runs auto-archiver for the first time, but in 'full' mode, enabling all modules to get a full settings file +docker run --rm -v $PWD/secrets:/app/secrets -v $PWD/local_archive:/app/local_archive bellingcat/auto-archiver --mode full +``` + +------------ + +### Running a Local Install + +### Example invocations + +Once all your [local requirements](#installing-local-requirements) are correctly installed, the + +```bash +# all the configurations come from ./secrets/orchestration.yaml +auto-archiver --config secrets/orchestration.yaml +# uses the same configurations but for another google docs sheet +# with a header on row 2 and with some different column names +# notice that columns is a dictionary so you need to pass it as JSON and it will override only the values provided +auto-archiver --config secrets/orchestration.yaml --gsheet_feeder.sheet="use it on another sheets doc" --gsheet_feeder.header=2 --gsheet_feeder.columns='{"url": "link"}' +# all the configurations come from orchestration.yaml and specifies that s3 files should be private +auto-archiver --config secrets/orchestration.yaml --s3_storage.private=1 +``` diff --git a/src/auto_archiver/core/config.py b/src/auto_archiver/core/config.py index 425f96c..66d2ffb 100644 --- a/src/auto_archiver/core/config.py +++ b/src/auto_archiver/core/config.py @@ -18,7 +18,7 @@ from typing import Any, List, Type, Tuple _yaml: YAML = YAML() -DEFAULT_CONFIG_FILE = "orchestration.yaml" +DEFAULT_CONFIG_FILE = "secrets/orchestration.yaml" EMPTY_CONFIG = _yaml.load(""" # Auto Archiver Configuration From 7562938151e833685c861ef5d09c7f0c64e335d4 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Fri, 21 Feb 2025 18:04:48 +0000 Subject: [PATCH 074/160] Proof of concept for settings page --- .gitignore | 1 + scripts/generate_settings_page.py | 83 ++++++++++++++++++++++++ scripts/settings.html | 103 ++++++++++++++++++++++++++++++ 3 files changed, 187 insertions(+) create mode 100644 scripts/generate_settings_page.py create mode 100644 scripts/settings.html diff --git a/.gitignore b/.gitignore index 701de43..4b8779f 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,4 @@ dist* docs/_build/ docs/source/autoapi/ docs/source/modules/autogen/ +scripts/settings_page.html diff --git a/scripts/generate_settings_page.py b/scripts/generate_settings_page.py new file mode 100644 index 0000000..2b74cf9 --- /dev/null +++ b/scripts/generate_settings_page.py @@ -0,0 +1,83 @@ +import os +from auto_archiver.core.module import ModuleFactory +from auto_archiver.core.consts import MODULE_TYPES +import jinja2 + +# Get available modules +module_factory = ModuleFactory() +available_modules = module_factory.available_modules() + +modules_by_type = {} +# Categorize modules by type +for module in available_modules: + for type in module.type: + modules_by_type.setdefault(type, []).append(module) + + +module_sections = "" +# Add module sections +for module_type in MODULE_TYPES: + module_sections += f"

{module_type}

" + for module in modules_by_type[module_type]: + module_name = module.name + module_sections += f""" +
+ + +
+ """ + module_sections += "
" + +# Add module configuration sections + +all_modules_ordered_by_type = sorted(available_modules, key=lambda x: (MODULE_TYPES.index(x.type[0]), not x.requires_setup)) + +module_configs = "" + +for module in all_modules_ordered_by_type: + if not module.configs: + continue + module_configs += f"

{module.display_name} Configuration

" + for option, value in module.configs.items(): + # create a human readable label + option = option.replace('_', ' ').title() + + # type - if value has 'choices', then it's a select + if 'choices' in value: + module_configs += f""" +
+ +
" + elif value.get('type') == 'bool' or isinstance(value.get('default', None), bool): + module_configs += f""" +
+ + +
+ """ + else: + module_configs += f""" +
+ + +
+ """ + module_configs += "
" + +# format the settings.html jinja page with the module sections and module configuration sections +settings_page = "settings.html" +template_loader = jinja2.FileSystemLoader(searchpath="./") +template_env = jinja2.Environment(loader=template_loader) +template = template_env.get_template(settings_page) +html_content = template.render(module_sections=module_sections, module_configs=module_configs) + +# Write HTML content to file +output_file = '/Users/patrick/Developer/auto-archiver/scripts/settings_page.html' +with open(output_file, 'w') as file: + file.write(html_content) + +print(f"Settings page generated at {output_file}") \ No newline at end of file diff --git a/scripts/settings.html b/scripts/settings.html new file mode 100644 index 0000000..bd11edd --- /dev/null +++ b/scripts/settings.html @@ -0,0 +1,103 @@ + + + + + + Module Settings + + + + + +

Module Settings

+
+ + + +

Steps

+ + {{module_sections}} + +

Module Configuration Section

+ {{module_configs}} + + +
+ + \ No newline at end of file From 1c17629ac6b46ad5917bbbc23e0c74ce28771806 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Fri, 21 Feb 2025 18:54:27 +0000 Subject: [PATCH 075/160] Tweaks --- scripts/generate_settings_page.py | 20 +++++++++++--------- scripts/settings.html | 2 ++ 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/scripts/generate_settings_page.py b/scripts/generate_settings_page.py index 2b74cf9..022dcc9 100644 --- a/scripts/generate_settings_page.py +++ b/scripts/generate_settings_page.py @@ -10,18 +10,20 @@ available_modules = module_factory.available_modules() modules_by_type = {} # Categorize modules by type for module in available_modules: - for type in module.type: + for type in module.manifest.get('type', []): modules_by_type.setdefault(type, []).append(module) module_sections = "" # Add module sections for module_type in MODULE_TYPES: - module_sections += f"

{module_type}

" + module_sections += f"

{module_type.title()}s

" + # make this section in rows, max 8 modules per row for module in modules_by_type[module_type]: + module_name = module.name module_sections += f""" -
+
@@ -43,29 +45,29 @@ for module in all_modules_ordered_by_type: option = option.replace('_', ' ').title() # type - if value has 'choices', then it's a select + module_configs += "
" if 'choices' in value: module_configs += f""" -
" + module_configs += "" elif value.get('type') == 'bool' or isinstance(value.get('default', None), bool): module_configs += f""" -
-
""" else: module_configs += f""" -
-
""" + # add help text + if 'help' in value: + module_configs += f"
{value.get('help')}
" + module_configs += "
" module_configs += "
" # format the settings.html jinja page with the module sections and module configuration sections diff --git a/scripts/settings.html b/scripts/settings.html index bd11edd..c5bdea9 100644 --- a/scripts/settings.html +++ b/scripts/settings.html @@ -7,6 +7,8 @@ - - - -

Module Settings

-
- - - -

Steps

- - {{module_sections}} - -

Module Configuration Section

- {{module_configs}} - - -
- - \ No newline at end of file diff --git a/scripts/html/settings.html b/scripts/html/settings.html deleted file mode 100644 index e69de29..0000000 diff --git a/scripts/settings/.gitignore b/scripts/settings/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/scripts/settings/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/scripts/settings/index.html b/scripts/settings/index.html new file mode 100644 index 0000000..d2e8d75 --- /dev/null +++ b/scripts/settings/index.html @@ -0,0 +1,20 @@ + + + + + + + + + + + Auto Archiver Settings + + +
+ + + diff --git a/scripts/settings/package-lock.json b/scripts/settings/package-lock.json new file mode 100644 index 0000000..6ef055b --- /dev/null +++ b/scripts/settings/package-lock.json @@ -0,0 +1,3683 @@ +{ + "name": "material-ui-vite-ts", + "version": "5.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "material-ui-vite-ts", + "version": "5.0.0", + "dependencies": { + "@emotion/react": "latest", + "@emotion/styled": "latest", + "@mui/icons-material": "latest", + "@mui/material": "latest", + "react": "latest", + "react-dom": "latest", + "react-markdown": "^10.0.0", + "yaml": "^2.7.0" + }, + "devDependencies": { + "@types/react": "latest", + "@types/react-dom": "latest", + "@vitejs/plugin-react": "latest", + "typescript": "latest", + "vite": "latest", + "vite-plugin-singlefile": "^2.1.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.26.8", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz", + "integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.9.tgz", + "integrity": "sha512-lWBYIrF7qK5+GjY5Uy+/hEgp8OJWOD/rpy74GplYRhEauvbHDeFB8t5hPOZxCZ0Oxf4Cc36tK51/l3ymJysrKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.9", + "@babel/helper-compilation-targets": "^7.26.5", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.9", + "@babel/parser": "^7.26.9", + "@babel/template": "^7.26.9", + "@babel/traverse": "^7.26.9", + "@babel/types": "^7.26.9", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/generator": { + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.9.tgz", + "integrity": "sha512-kEWdzjOAUMW4hAyrzJ0ZaTOu9OmpyDIQicIh0zg0EEcEkYXZb2TjtBhnHi2ViX7PKwZqF4xwqfAm299/QMP3lg==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.26.9", + "@babel/types": "^7.26.9", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz", + "integrity": "sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.26.5", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", + "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz", + "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", + "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.9.tgz", + "integrity": "sha512-Mz/4+y8udxBKdmzt/UjPACs4G3j5SshJJEFFKxlCGPydG4JAHXxjWjAwjd09tf6oINvl1VfMJo+nB7H2YKQ0dA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.26.9", + "@babel/types": "^7.26.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.9.tgz", + "integrity": "sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.26.9" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.25.9.tgz", + "integrity": "sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.25.9.tgz", + "integrity": "sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.9.tgz", + "integrity": "sha512-aA63XwOkcl4xxQa3HjPMqOP6LiK0ZDv3mUPYEFXkpHbaFjtGggE1A61FjFzJnB+p7/oy2gA8E+rcBNl/zC1tMg==", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz", + "integrity": "sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/parser": "^7.26.9", + "@babel/types": "^7.26.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.9.tgz", + "integrity": "sha512-ZYW7L+pL8ahU5fXmNbPF+iZFHCv5scFak7MZ9bwaRPLUhHh7QQEMjZUg0HevihoqCM5iSYHN61EyCoZvqC+bxg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.9", + "@babel/parser": "^7.26.9", + "@babel/template": "^7.26.9", + "@babel/types": "^7.26.9", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.9.tgz", + "integrity": "sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emotion/babel-plugin": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/cache": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz", + "integrity": "sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" + }, + "node_modules/@emotion/react": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "license": "MIT", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", + "license": "MIT" + }, + "node_modules/@emotion/styled": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.0.tgz", + "integrity": "sha512-XxfOnXFffatap2IyCeJyNov3kiDQWoR08gPUQxvbL7fxKryGBKUZUkG6Hz48DZwVrJSVh9sJboyV1Ds4OW6SgA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/is-prop-valid": "^1.3.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==", + "license": "MIT" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz", + "integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz", + "integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz", + "integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz", + "integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz", + "integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz", + "integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz", + "integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz", + "integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz", + "integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz", + "integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz", + "integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz", + "integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz", + "integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz", + "integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz", + "integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz", + "integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz", + "integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz", + "integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz", + "integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz", + "integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz", + "integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz", + "integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz", + "integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz", + "integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz", + "integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@mui/core-downloads-tracker": { + "version": "6.4.5", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.4.5.tgz", + "integrity": "sha512-zoXvHU1YuoodgMlPS+epP084Pqv9V+Vg+5IGv9n/7IIFVQ2nkTngYHYxElCq8pdTTbDcgji+nNh0lxri2abWgA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + } + }, + "node_modules/@mui/icons-material": { + "version": "6.4.5", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.4.5.tgz", + "integrity": "sha512-4A//t8Nrc+4u4pbVhGarIFU98zpuB5AV9hTNzgXx1ySZJ1tWtx+i/1SbQ8PtGJxWeXlljhwimZJNPQ3x0CiIFw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@mui/material": "^6.4.5", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material": { + "version": "6.4.5", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.4.5.tgz", + "integrity": "sha512-5eyEgSXocIeV1JkXs8mYyJXU0aFyXZIWI5kq2g/mCnIgJe594lkOBNAKnCIaGVfQTu2T6TTEHF8/hHIqpiIRGA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/core-downloads-tracker": "^6.4.5", + "@mui/system": "^6.4.3", + "@mui/types": "^7.2.21", + "@mui/utils": "^6.4.3", + "@popperjs/core": "^2.11.8", + "@types/react-transition-group": "^4.4.12", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1", + "react-is": "^19.0.0", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@mui/material-pigment-css": "^6.4.3", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@mui/material-pigment-css": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/private-theming": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.4.3.tgz", + "integrity": "sha512-7x9HaNwDCeoERc4BoEWLieuzKzXu5ZrhRnEM6AUcRXUScQLvF1NFkTlP59+IJfTbEMgcGg1wWHApyoqcksrBpQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/utils": "^6.4.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.4.3.tgz", + "integrity": "sha512-OC402VfK+ra2+f12Gef8maY7Y9n7B6CZcoQ9u7mIkh/7PKwW/xH81xwX+yW+Ak1zBT3HYcVjh2X82k5cKMFGoQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@emotion/cache": "^11.13.5", + "@emotion/serialize": "^1.3.3", + "@emotion/sheet": "^1.4.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/system": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.4.3.tgz", + "integrity": "sha512-Q0iDwnH3+xoxQ0pqVbt8hFdzhq1g2XzzR4Y5pVcICTNtoCLJmpJS3vI4y/OIM1FHFmpfmiEC2IRIq7YcZ8nsmg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/private-theming": "^6.4.3", + "@mui/styled-engine": "^6.4.3", + "@mui/types": "^7.2.21", + "@mui/utils": "^6.4.3", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/types": { + "version": "7.2.21", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.21.tgz", + "integrity": "sha512-6HstngiUxNqLU+/DPqlUJDIPbzUBxIVHb1MmXP0eTWDIROiCR2viugXpEif0PPe2mLqqakPzzRClWAnK+8UJww==", + "license": "MIT", + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.4.3.tgz", + "integrity": "sha512-jxHRHh3BqVXE9ABxDm+Tc3wlBooYz/4XPa0+4AI+iF38rV1/+btJmSUgG4shDtSWVs/I97aDn5jBCt6SF2Uq2A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/types": "^7.2.21", + "@types/prop-types": "^15.7.14", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^19.0.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.8.tgz", + "integrity": "sha512-q217OSE8DTp8AFHuNHXo0Y86e1wtlfVrXiAlwkIvGRQv9zbc6mE3sjIVfwI8sYUyNxwOg0j/Vm1RKM04JcWLJw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.8.tgz", + "integrity": "sha512-Gigjz7mNWaOL9wCggvoK3jEIUUbGul656opstjaUSGC3eT0BM7PofdAJaBfPFWWkXNVAXbaQtC99OCg4sJv70Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.8.tgz", + "integrity": "sha512-02rVdZ5tgdUNRxIUrFdcMBZQoaPMrxtwSb+/hOfBdqkatYHR3lZ2A2EGyHq2sGOd0Owk80oV3snlDASC24He3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.8.tgz", + "integrity": "sha512-qIP/elwR/tq/dYRx3lgwK31jkZvMiD6qUtOycLhTzCvrjbZ3LjQnEM9rNhSGpbLXVJYQ3rq39A6Re0h9tU2ynw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.8.tgz", + "integrity": "sha512-IQNVXL9iY6NniYbTaOKdrlVP3XIqazBgJOVkddzJlqnCpRi/yAeSOa8PLcECFSQochzqApIOE1GHNu3pCz+BDA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.8.tgz", + "integrity": "sha512-TYXcHghgnCqYFiE3FT5QwXtOZqDj5GmaFNTNt3jNC+vh22dc/ukG2cG+pi75QO4kACohZzidsq7yKTKwq/Jq7Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.8.tgz", + "integrity": "sha512-A4iphFGNkWRd+5m3VIGuqHnG3MVnqKe7Al57u9mwgbyZ2/xF9Jio72MaY7xxh+Y87VAHmGQr73qoKL9HPbXj1g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.8.tgz", + "integrity": "sha512-S0lqKLfTm5u+QTxlFiAnb2J/2dgQqRy/XvziPtDd1rKZFXHTyYLoVL58M/XFwDI01AQCDIevGLbQrMAtdyanpA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.8.tgz", + "integrity": "sha512-jpz9YOuPiSkL4G4pqKrus0pn9aYwpImGkosRKwNi+sJSkz+WU3anZe6hi73StLOQdfXYXC7hUfsQlTnjMd3s1A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.8.tgz", + "integrity": "sha512-KdSfaROOUJXgTVxJNAZ3KwkRc5nggDk+06P6lgi1HLv1hskgvxHUKZ4xtwHkVYJ1Rep4GNo+uEfycCRRxht7+Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.8.tgz", + "integrity": "sha512-NyF4gcxwkMFRjgXBM6g2lkT58OWztZvw5KkV2K0qqSnUEqCVcqdh2jN4gQrTn/YUpAcNKyFHfoOZEer9nwo6uQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.8.tgz", + "integrity": "sha512-LMJc999GkhGvktHU85zNTDImZVUCJ1z/MbAJTnviiWmmjyckP5aQsHtcujMjpNdMZPT2rQEDBlJfubhs3jsMfw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.8.tgz", + "integrity": "sha512-xAQCAHPj8nJq1PI3z8CIZzXuXCstquz7cIOL73HHdXiRcKk8Ywwqtx2wrIy23EcTn4aZ2fLJNBB8d0tQENPCmw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.8.tgz", + "integrity": "sha512-DdePVk1NDEuc3fOe3dPPTb+rjMtuFw89gw6gVWxQFAuEqqSdDKnrwzZHrUYdac7A7dXl9Q2Vflxpme15gUWQFA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.8.tgz", + "integrity": "sha512-8y7ED8gjxITUltTUEJLQdgpbPh1sUQ0kMTmufRF/Ns5tI9TNMNlhWtmPKKHCU0SilX+3MJkZ0zERYYGIVBYHIA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.8.tgz", + "integrity": "sha512-SCXcP0ZpGFIe7Ge+McxY5zKxiEI5ra+GT3QRxL0pMMtxPfpyLAKleZODi1zdRHkz5/BhueUrYtYVgubqe9JBNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.8.tgz", + "integrity": "sha512-YHYsgzZgFJzTRbth4h7Or0m5O74Yda+hLin0irAIobkLQFRQd1qWmnoVfwmKm9TXIZVAD0nZ+GEb2ICicLyCnQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.8.tgz", + "integrity": "sha512-r3NRQrXkHr4uWy5TOjTpTYojR9XmF0j/RYgKCef+Ag46FWUTltm5ziticv8LdNsDMehjJ543x/+TJAek/xBA2w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.8.tgz", + "integrity": "sha512-U0FaE5O1BCpZSeE6gBl3c5ObhePQSfk9vDRToMmTkbhCOgW4jqvtS5LGyQ76L1fH8sM0keRp4uDTsbjiUyjk0g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", + "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "license": "MIT" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.14", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", + "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==", + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.0.10", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.10.tgz", + "integrity": "sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==", + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.0.4", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.0.4.tgz", + "integrity": "sha512-4fSQ8vWFkg+TGhePfUzVmat3eC14TXYSsiiDSLI0dVLsrm9gZFABjPy/Qu6TKgl1tq1Bu1yDsuQgY3A3DOjCcg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.0.0" + } + }, + "node_modules/@types/react-transition-group": { + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", + "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "license": "ISC" + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.4.tgz", + "integrity": "sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.26.0", + "@babel/plugin-transform-react-jsx-self": "^7.25.9", + "@babel/plugin-transform-react-jsx-source": "^7.25.9", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.14.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" + } + }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", + "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001701", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001701.tgz", + "integrity": "sha512-faRs/AW3jA9nTwmJBSO1PQ6L/EOgsB5HMQQq4iCu5zhPgVVgO/pZRHlmatwijZKetFw8/Pr4q6dEN8sJuq8qTw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cosmiconfig/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", + "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.105", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.105.tgz", + "integrity": "sha512-ccp7LocdXx3yBhwiG0qTQ7XFrK48Ua2pxIxBdJO8cbddp/MvbBtPFzvnTchtyHQTsgqqczO8cdmAIbpMa0u2+g==", + "dev": true, + "license": "ISC" + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/esbuild": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz", + "integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.0", + "@esbuild/android-arm": "0.25.0", + "@esbuild/android-arm64": "0.25.0", + "@esbuild/android-x64": "0.25.0", + "@esbuild/darwin-arm64": "0.25.0", + "@esbuild/darwin-x64": "0.25.0", + "@esbuild/freebsd-arm64": "0.25.0", + "@esbuild/freebsd-x64": "0.25.0", + "@esbuild/linux-arm": "0.25.0", + "@esbuild/linux-arm64": "0.25.0", + "@esbuild/linux-ia32": "0.25.0", + "@esbuild/linux-loong64": "0.25.0", + "@esbuild/linux-mips64el": "0.25.0", + "@esbuild/linux-ppc64": "0.25.0", + "@esbuild/linux-riscv64": "0.25.0", + "@esbuild/linux-s390x": "0.25.0", + "@esbuild/linux-x64": "0.25.0", + "@esbuild/netbsd-arm64": "0.25.0", + "@esbuild/netbsd-x64": "0.25.0", + "@esbuild/openbsd-arm64": "0.25.0", + "@esbuild/openbsd-x64": "0.25.0", + "@esbuild/sunos-x64": "0.25.0", + "@esbuild/win32-arm64": "0.25.0", + "@esbuild/win32-ia32": "0.25.0", + "@esbuild/win32-x64": "0.25.0" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "license": "MIT" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.5.tgz", + "integrity": "sha512-gHD+HoFxOMmmXLuq9f2dZDMQHVcplCVpMfBNRpJsF03yyLZvJGzsFORe8orVuYDX9k2w0VH0uF8oryFd1whqKQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-object": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/html-url-attributes": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inline-style-parser": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", + "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==", + "license": "MIT" + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", + "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.1.tgz", + "integrity": "sha512-eBPdkcoCNvYcxQOAKAlceo5SNdzZWfF+FcSupREAzdAh9rRmE239CEQAiTwIgblwnoM8zzj35sZ5ZwvSEOF6Kw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.2.tgz", + "integrity": "sha512-FKjQKbxd1cibWMM1P9N+H8TwlgGgSkWZMmfuVucLCHaYqeSvJ0hFeHsIa65pA2nYbes0f8LDHPMrd9X7Ujxg9w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.4.tgz", + "integrity": "sha512-N6hXjrin2GTJDe3MVjf5FuXpm12PGm80BrUAeub9XFXca8JZbP+oIwY4LJSVwFUCL1IPm/WwSVUN7goFHmSGGQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.1.tgz", + "integrity": "sha512-534m2WhVTddrcKVepwmVEVnUAmtrx9bfIjNoQHRqfnvdaHQiFytEhJoTgpWJvDEXCO5gLTQh3wYC1PgOJA4NSQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "license": "MIT" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/property-information": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.0.0.tgz", + "integrity": "sha512-7D/qOz/+Y4X/rzSB6jKxKUsQnphO046ei8qxG59mtM3RG3DHgTK81HrxrmoDVINJb8NKT5ZsRbwHvQ6B68Iyhg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-is": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.0.0.tgz", + "integrity": "sha512-H91OHcwjZsbq3ClIDHMzBShc1rotbfACdWENsmEf0IFvZ3FgGPtdHMcsv45bQ1hAbgdfiA8SnxTKfDS+x/8m2g==", + "license": "MIT" + }, + "node_modules/react-markdown": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.0.0.tgz", + "integrity": "sha512-4mTz7Sya/YQ1jYOrkwO73VcFdkFJ8L8I9ehCxdcV0XrClHyOJGKbBk5FR4OOOG+HnyKw5u+C/Aby9TwinCteYA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=18", + "react": ">=18" + } + }, + "node_modules/react-refresh": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "license": "MIT" + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.1.tgz", + "integrity": "sha512-g/osARvjkBXb6Wo0XvAeXQohVta8i84ACbenPpoSsxTOQH/Ae0/RGP4WZgnMH5pMLpsj4FG7OHmcIcXxpza8eQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/rollup": { + "version": "4.34.8", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.8.tgz", + "integrity": "sha512-489gTVMzAYdiZHFVA/ig/iYFllCcWFHMvUHI1rpFmkoUtRlQxqh6/yiNqnYibjMZ2b/+FUQwldG+aLsEt6bglQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.34.8", + "@rollup/rollup-android-arm64": "4.34.8", + "@rollup/rollup-darwin-arm64": "4.34.8", + "@rollup/rollup-darwin-x64": "4.34.8", + "@rollup/rollup-freebsd-arm64": "4.34.8", + "@rollup/rollup-freebsd-x64": "4.34.8", + "@rollup/rollup-linux-arm-gnueabihf": "4.34.8", + "@rollup/rollup-linux-arm-musleabihf": "4.34.8", + "@rollup/rollup-linux-arm64-gnu": "4.34.8", + "@rollup/rollup-linux-arm64-musl": "4.34.8", + "@rollup/rollup-linux-loongarch64-gnu": "4.34.8", + "@rollup/rollup-linux-powerpc64le-gnu": "4.34.8", + "@rollup/rollup-linux-riscv64-gnu": "4.34.8", + "@rollup/rollup-linux-s390x-gnu": "4.34.8", + "@rollup/rollup-linux-x64-gnu": "4.34.8", + "@rollup/rollup-linux-x64-musl": "4.34.8", + "@rollup/rollup-win32-arm64-msvc": "4.34.8", + "@rollup/rollup-win32-ia32-msvc": "4.34.8", + "@rollup/rollup-win32-x64-msvc": "4.34.8", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/style-to-object": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.8.tgz", + "integrity": "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.4" + } + }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "license": "MIT" + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/typescript": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", + "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz", + "integrity": "sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vite": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.0.tgz", + "integrity": "sha512-7dPxoo+WsT/64rDcwoOjk76XHj+TqNTIvHKcuMQ1k4/SeHDaQt5GFAeLYzrimZrMpn/O6DtdI03WUjdxuPM0oQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "postcss": "^8.5.3", + "rollup": "^4.30.1" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-plugin-singlefile": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/vite-plugin-singlefile/-/vite-plugin-singlefile-2.1.0.tgz", + "integrity": "sha512-7tJo+UgZABlKpY/nubth/wxJ4+pUGREPnEwNOknxwl2MM0zTvF14KTU4Ln1lc140gjLLV5mjDrvuoquU7OZqCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">18.0.0" + }, + "peerDependencies": { + "rollup": "^4.28.1", + "vite": "^5.4.11 || ^6.0.0" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz", + "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/scripts/settings/package.json b/scripts/settings/package.json new file mode 100644 index 0000000..6608693 --- /dev/null +++ b/scripts/settings/package.json @@ -0,0 +1,29 @@ +{ + "name": "material-ui-vite-ts", + "private": true, + "version": "5.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@emotion/react": "latest", + "@emotion/styled": "latest", + "@mui/icons-material": "latest", + "@mui/material": "latest", + "react": "latest", + "react-dom": "latest", + "react-markdown": "^10.0.0", + "yaml": "^2.7.0" + }, + "devDependencies": { + "@types/react": "latest", + "@types/react-dom": "latest", + "@vitejs/plugin-react": "latest", + "typescript": "latest", + "vite": "latest", + "vite-plugin-singlefile": "^2.1.0" + } +} diff --git a/scripts/settings/public/vite.svg b/scripts/settings/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/scripts/settings/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/scripts/settings/src/App.tsx b/scripts/settings/src/App.tsx new file mode 100644 index 0000000..6e6e813 --- /dev/null +++ b/scripts/settings/src/App.tsx @@ -0,0 +1,364 @@ +import * as React from 'react'; +import { useEffect, useState } from 'react'; +import Container from '@mui/material/Container'; +import Typography from '@mui/material/Typography'; +import Box from '@mui/material/Box'; +import Link from '@mui/material/Link'; +import { modules, steps, configs, module_types } from './schema.json'; +import { + Checkbox, + Select, + MenuItem, + FormControl, + FormControlLabel, + InputLabel, + FormHelperText, + Stack, + TextField, + Card, + CardContent, + CardActions, + Button, + Dialog, + DialogTitle, + DialogContent, +} from '@mui/material'; +import Grid from '@mui/material/Grid2'; + +import Accordion from '@mui/material/Accordion'; +import AccordionDetails from '@mui/material/AccordionDetails'; +import AccordionSummary from '@mui/material/AccordionSummary'; +import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; +import ReactMarkdown from 'react-markdown'; +import { parseDocument, ParsedNode, Document } from 'yaml' +import { set } from 'yaml/dist/schema/yaml-1.1/set'; + +Object.defineProperty(String.prototype, 'capitalize', { + value: function() { + return this.charAt(0).toUpperCase() + this.slice(1); + }, + enumerable: false +}); + +function FileDrop({ setYamlFile }) { + + const [showError, setShowError] = useState(false); + const [label, setLabel] = useState("Drag and drop your orchestration.yaml file here, or click to select a file."); + + function openYAMLFile(event: any) { + let file = event.target.files[0]; + if (file.type !== 'application/x-yaml') { + setShowError(true); + setLabel("Invalid type, only YAML files are accepted.") + return; + } + let reader = new FileReader(); + reader.onload = function(e) { + let contents = e.target.result; + try { + let document = parseDocument(contents); + if (document.errors.length > 0) { + // not a valid yaml file + setShowError(true); + setLabel("Invalid file. Make sure your Orchestration is a valid YAML file with a 'steps' section in it.") + return; + } else { + setShowError(false); + setLabel("File loaded successfully.") + } + setYamlFile(document); + } catch (e) { + console.error(e); + } + } + reader.readAsText(file); + } + return ( + <> +
+ + + + {label} + +
+ + ); +} + + +function ModuleCheckbox({ module, toggleModule, enabledModules, configValues }: { module: object, toggleModule: any, enabledModules: any, configValues: any }) { + let name = module.name; + const [helpOpen, setHelpOpen] = useState(false); + const [configOpen, setConfigOpen] = useState(false); + if (name == 'metadata_enricher') { + console.log("hi"); + } + return ( + <> + + + } + label={module.display_name} /> + + + + {enabledModules.includes(name) && module.configs && name != 'cli_feeder' ? ( + + ) : null} + + + setHelpOpen(false)} + maxWidth="lg" + > + + {module.display_name} + + + + {module.manifest.description.split("\n").map((line: string) => line.trim()).join("\n")} + + + + {module.configs && name != 'cli_feeder' && } + + ) +} + + +function ConfigPanel({ module, open, setOpen, configValues }: { module: any, open: boolean, setOpen: any, configValues: any }) { + return ( + <> + setOpen(false)} + maxWidth="lg" + > + + {module.display_name} + + + + {Object.keys(module.configs).map((config_value: any) => { + let config_args = module.configs[config_value]; + let config_name = config_value.replace(/_/g," "); + return ( + + + { config_args.type === 'bool' ? + } label={config_name} /> + : + ( config_args.type === 'int' ? + + : + ( + config_args.choices !== undefined ? + <> + {config_name} + + + : + + ) + ) + } + {config_args.help} + + + ); + })} + + + + + ); +} + +function ModuleTypes({ stepType, toggleModule, enabledModules, configValues }: { stepType: string, toggleModule: any, enabledModules: any, configValues: any }) { + const [showError, setShowError] = useState(false); + + const _toggleModule = (event: any) => { + // make sure that 'feeder' and 'formatter' types only have one value + let name = event.target.id; + if (stepType === 'feeder' || stepType === 'formatter') { + let checked = event.target.checked; + // check how many modules of this type are enabled + let modules = steps[stepType].filter((m: string) => (m !== name && enabledModules.includes(m)) || (checked && m === name)); + if (modules.length > 1) { + setShowError(true); + } else { + setShowError(false); + } + } else { + setShowError(false); + } + toggleModule(event); + } + return ( + <> + + {stepType}s + + {showError ? Only one {stepType} can be enabled at a time. : null} + + {steps[stepType].map((name: string) => { + let m = modules[name]; + return ( + + + + ); + })} + + + ); +} + + +export default function App() { + const [yamlFile, setYamlFile] = useState(new Document()); + const [enabledModules, setEnabledModules] = useState<[]>([]); + const [configValues, setConfigValues] = useState<{}>( + Object.keys(modules).reduce((acc, module) => { + acc[module] = {}; + return acc; + }, {}) + ); + + const saveSettings = function(copy: boolean = false) { + // edit the yamlFile + + // generate the steps config + let stepsConfig = {} + module_types.forEach((stepType: string) => { + stepsConfig[stepType] = enabledModules.filter((m: string) => steps[stepType].includes(m)); + } + ); + + // create a yaml file from + const finalYaml = { + 'steps': stepsConfig + }; + + Object.keys(configValues).map((module: string) => { + let module_values = configValues[module]; + if (module_values) { + finalYaml[module] = module_values; + } + }); + let newFile = new Document(finalYaml); + if (copy) { + navigator.clipboard.writeText(String(newFile)).then(() => { + alert("Settings copied to clipboard."); + }); + } else { + // offer the file for download + const blob = new Blob([String(newFile)], { type: 'application/x-yaml' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = 'orchestration.yaml'; + a.click(); + } + } + + const toggleModule = function (event: any) { + let module = event.target.id; + let checked = event.target.checked + + if (checked) { + setEnabledModules([...enabledModules, module]); + } else { + setEnabledModules(enabledModules.filter((m: string) => m !== module)); + } + } + + useEffect(() => { + // load the configs, and set the default values if they exist + let newConfigValues = {}; + Object.keys(modules).map((module: string) => { + let m = modules[module]; + let configs = m.configs; + if (!configs) { + return; + } + newConfigValues[module] = {}; + Object.keys(configs).map((config: string) => { + let config_args = configs[config]; + if (config_args.default !== undefined) { + newConfigValues[module][config] = config_args.default; + } + }); + }) + setConfigValues(newConfigValues); + }, []); + + useEffect(() => { + if (!yamlFile || yamlFile.contents == null) { + return; + } + + let settings = yamlFile.toJS(); + // make a deep copy of settings + let newEnabledModules = Object.keys(settings['steps']).map((step: string) => { + return settings['steps'][step]; + }).flat(); + newEnabledModules = newEnabledModules.filter((m: string, i: number) => newEnabledModules.indexOf(m) === i); + setEnabledModules(newEnabledModules); + }, [yamlFile]); + + + + return ( + + + + Auto Archiver Settings + + + + 1. Select your
orchestration.yaml
settings file. +
+ +
+ + + 2. Choose the Modules you wish to enable/disable + + {Object.keys(steps).map((stepType: string) => { + return ( + + ); + })} + + + + 3. Configure your Enabled Modules + + + Next to each module you've enabled, you can click 'Configure' to set the module's settings. + + + + + 4. Save your settings + + + + +
+
+ ); +} diff --git a/scripts/settings/src/ProTip.tsx b/scripts/settings/src/ProTip.tsx new file mode 100644 index 0000000..217b5bf --- /dev/null +++ b/scripts/settings/src/ProTip.tsx @@ -0,0 +1,23 @@ +import * as React from 'react'; +import Link from '@mui/material/Link'; +import SvgIcon, { SvgIconProps } from '@mui/material/SvgIcon'; +import Typography from '@mui/material/Typography'; + +function LightBulbIcon(props: SvgIconProps) { + return ( + + + + ); +} + +export default function ProTip() { + return ( + + + {'Pro tip: See more '} + templates + {' in the Material UI documentation.'} + + ); +} diff --git a/scripts/settings/src/main.tsx b/scripts/settings/src/main.tsx new file mode 100644 index 0000000..ad50afc --- /dev/null +++ b/scripts/settings/src/main.tsx @@ -0,0 +1,15 @@ +import * as React from 'react'; +import * as ReactDOM from 'react-dom/client'; +import { ThemeProvider } from '@mui/material/styles'; +import { CssBaseline } from '@mui/material'; +import theme from './theme'; +import App from './App'; + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + + + + , +); diff --git a/scripts/settings/src/schema.json b/scripts/settings/src/schema.json new file mode 100644 index 0000000..e02067c --- /dev/null +++ b/scripts/settings/src/schema.json @@ -0,0 +1,2095 @@ +{ + "modules": { + "gsheet_feeder": { + "name": "gsheet_feeder", + "display_name": "Google Sheets Feeder", + "manifest": { + "name": "Google Sheets Feeder", + "author": "Bellingcat", + "type": [ + "feeder" + ], + "requires_setup": true, + "description": "\n GsheetsFeeder \n A Google Sheets-based feeder for the Auto Archiver.\n\n This reads data from Google Sheets and filters rows based on user-defined rules.\n The filtered rows are processed into `Metadata` objects.\n\n ### Features\n - Validates the sheet structure and filters rows based on input configurations.\n - Processes only worksheets allowed by the `allow_worksheets` and `block_worksheets` configurations.\n - Ensures only rows with valid URLs and unprocessed statuses are included for archival.\n - Supports organizing stored files into folder paths based on sheet and worksheet names.\n\n ### Setup\n - Requires a Google Service Account JSON file for authentication, which should be stored in `secrets/gsheets_service_account.json`.\n To set up a service account, follow the instructions [here](https://gspread.readthedocs.io/en/latest/oauth2.html).\n - Define the `sheet` or `sheet_id` configuration to specify the sheet to archive.\n - Customize the column names in your Google sheet using the `columns` configuration.\n ", + "dependencies": { + "python": [ + "loguru", + "gspread", + "slugify" + ] + }, + "entry_point": "gsheet_feeder::GsheetsFeeder", + "version": "1.0", + "configs": { + "sheet": { + "default": null, + "help": "name of the sheet to archive" + }, + "sheet_id": { + "default": null, + "help": "the id of the sheet to archive (alternative to 'sheet' config)" + }, + "header": { + "default": 1, + "help": "index of the header row (starts at 1)", + "type": "int" + }, + "service_account": { + "default": "secrets/service_account.json", + "help": "service account JSON file path. Learn how to create one: https://gspread.readthedocs.io/en/latest/oauth2.html", + "required": true + }, + "columns": { + "default": { + "url": "link", + "status": "archive status", + "folder": "destination folder", + "archive": "archive location", + "date": "archive date", + "thumbnail": "thumbnail", + "timestamp": "upload timestamp", + "title": "upload title", + "text": "text content", + "screenshot": "screenshot", + "hash": "hash", + "pdq_hash": "perceptual hashes", + "wacz": "wacz", + "replaywebpage": "replaywebpage" + }, + "help": "Custom names for the columns in your Google sheet. If you don't want to use the default column names, change them with this setting", + "type": "json_loader" + }, + "allow_worksheets": { + "default": [], + "help": "A list of worksheet names that should be processed (overrides worksheet_block), leave empty so all are allowed" + }, + "block_worksheets": { + "default": [], + "help": "A list of worksheet names for worksheets that should be explicitly blocked from being processed" + }, + "use_sheet_names_in_stored_paths": { + "default": true, + "help": "if True the stored files path will include 'workbook_name/worksheet_name/...'", + "type": "bool" + } + } + }, + "configs": { + "sheet": { + "default": null, + "help": "name of the sheet to archive" + }, + "sheet_id": { + "default": null, + "help": "the id of the sheet to archive (alternative to 'sheet' config)" + }, + "header": { + "default": 1, + "help": "index of the header row (starts at 1)", + "type": "int" + }, + "service_account": { + "default": "secrets/service_account.json", + "help": "service account JSON file path. Learn how to create one: https://gspread.readthedocs.io/en/latest/oauth2.html", + "required": true + }, + "columns": { + "default": { + "url": "link", + "status": "archive status", + "folder": "destination folder", + "archive": "archive location", + "date": "archive date", + "thumbnail": "thumbnail", + "timestamp": "upload timestamp", + "title": "upload title", + "text": "text content", + "screenshot": "screenshot", + "hash": "hash", + "pdq_hash": "perceptual hashes", + "wacz": "wacz", + "replaywebpage": "replaywebpage" + }, + "help": "Custom names for the columns in your Google sheet. If you don't want to use the default column names, change them with this setting", + "type": "json_loader" + }, + "allow_worksheets": { + "default": [], + "help": "A list of worksheet names that should be processed (overrides worksheet_block), leave empty so all are allowed" + }, + "block_worksheets": { + "default": [], + "help": "A list of worksheet names for worksheets that should be explicitly blocked from being processed" + }, + "use_sheet_names_in_stored_paths": { + "default": true, + "help": "if True the stored files path will include 'workbook_name/worksheet_name/...'", + "type": "bool" + } + } + }, + "atlos_feeder": { + "name": "atlos_feeder", + "display_name": "Atlos Feeder", + "manifest": { + "name": "Atlos Feeder", + "author": "Bellingcat", + "type": [ + "feeder" + ], + "requires_setup": true, + "description": "\n AtlosFeeder: A feeder module that integrates with the Atlos API to fetch source material URLs for archival.\n\n ### Features\n - Connects to the Atlos API to retrieve a list of source material URLs.\n - Filters source materials based on visibility, processing status, and metadata.\n - Converts filtered source materials into `Metadata` objects with the relevant `atlos_id` and URL.\n - Iterates through paginated results using a cursor for efficient API interaction.\n\n ### Notes\n - Requires an Atlos API endpoint and a valid API token for authentication.\n - Ensures only unprocessed, visible, and ready-to-archive URLs are returned.\n - Handles pagination transparently when retrieving data from the Atlos API.\n ", + "dependencies": { + "python": [ + "loguru", + "requests" + ] + }, + "entry_point": "", + "version": "1.0", + "configs": { + "api_token": { + "type": "str", + "required": true, + "help": "An Atlos API token. For more information, see https://docs.atlos.org/technical/api/" + }, + "atlos_url": { + "default": "https://platform.atlos.org", + "help": "The URL of your Atlos instance (e.g., https://platform.atlos.org), without a trailing slash.", + "type": "str" + } + } + }, + "configs": { + "api_token": { + "type": "str", + "required": true, + "help": "An Atlos API token. For more information, see https://docs.atlos.org/technical/api/" + }, + "atlos_url": { + "default": "https://platform.atlos.org", + "help": "The URL of your Atlos instance (e.g., https://platform.atlos.org), without a trailing slash.", + "type": "str" + } + } + }, + "csv_feeder": { + "name": "csv_feeder", + "display_name": "CSV Feeder", + "manifest": { + "name": "CSV Feeder", + "author": "Bellingcat", + "type": [ + "feeder" + ], + "requires_setup": true, + "description": "\n Reads URLs from CSV files and feeds them into the archiving process.\n\n ### Features\n - Supports reading URLs from multiple input files, specified as a comma-separated list.\n - Allows specifying the column number or name to extract URLs from.\n - Skips header rows if the first value is not a valid URL.\n\n ### Setup\n - Input files should be formatted with one URL per line, with or without a header row.\n - If you have a header row, you can specify the column number or name to read URLs from using the 'column' config option.\n ", + "dependencies": { + "python": [ + "loguru" + ], + "bin": [ + "" + ] + }, + "entry_point": "csv_feeder::CSVFeeder", + "version": "1.0", + "configs": { + "files": { + "default": null, + "help": "Path to the input file(s) to read the URLs from, comma separated. Input files should be formatted with one URL per line", + "required": true, + "type": "valid_file", + "nargs": "+" + }, + "column": { + "default": null, + "help": "Column number or name to read the URLs from, 0-indexed" + } + } + }, + "configs": { + "files": { + "default": null, + "help": "Path to the input file(s) to read the URLs from, comma separated. Input files should be formatted with one URL per line", + "required": true, + "type": "valid_file", + "nargs": "+" + }, + "column": { + "default": null, + "help": "Column number or name to read the URLs from, 0-indexed" + } + } + }, + "cli_feeder": { + "name": "cli_feeder", + "display_name": "Command Line Feeder", + "manifest": { + "name": "Command Line Feeder", + "author": "Bellingcat", + "type": [ + "feeder" + ], + "requires_setup": false, + "description": "\nThe Command Line Feeder is the default enabled feeder for the Auto Archiver. It allows you to pass URLs directly to the orchestrator from the command line \nwithout the need to specify any additional configuration or command line arguments:\n\n`auto-archiver --feeder cli_feeder -- \"https://example.com/1/,https://example.com/2/\"`\n\nYou can pass multiple URLs by separating them with a space. The URLs will be processed in the order they are provided.\n\n`auto-archiver --feeder cli_feeder -- https://example.com/1/ https://example.com/2/`\n", + "dependencies": {}, + "entry_point": "cli_feeder::CLIFeeder", + "version": "1.0", + "configs": { + "urls": { + "default": null, + "help": "URL(s) to archive, either a single URL or a list of urls, should not come from config.yaml" + } + } + }, + "configs": { + "urls": { + "default": null, + "help": "URL(s) to archive, either a single URL or a list of urls, should not come from config.yaml" + } + } + }, + "instagram_api_extractor": { + "name": "instagram_api_extractor", + "display_name": "Instagram API Extractor", + "manifest": { + "name": "Instagram API Extractor", + "author": "Bellingcat", + "type": [ + "extractor" + ], + "requires_setup": true, + "description": "\nArchives various types of Instagram content using the Instagrapi API.\n\nRequires setting up an Instagrapi API deployment and providing an access token and API endpoint.\n\n### Features\n- Connects to an Instagrapi API deployment to fetch Instagram profiles, posts, stories, highlights, reels, and tagged content.\n- Supports advanced configuration options, including:\n - Full profile download (all posts, stories, highlights, and tagged content).\n - Limiting the number of posts to fetch for large profiles.\n - Minimising JSON output to remove empty fields and redundant data.\n- Provides robust error handling and retries for API calls.\n- Ensures efficient media scraping, including handling nested or carousel media items.\n- Adds downloaded media and metadata to the result for further processing.\n\n### Notes\n- Requires a valid Instagrapi API token (`access_token`) and API endpoint (`api_endpoint`).\n- Full-profile downloads can be limited by setting `full_profile_max_posts`.\n- Designed to fetch content in batches for large profiles, minimising API load.\n", + "dependencies": { + "python": [ + "requests", + "loguru", + "retrying", + "tqdm" + ] + }, + "entry_point": "instagram_api_extractor::InstagramAPIExtractor", + "version": "1.0", + "configs": { + "access_token": { + "default": null, + "help": "a valid instagrapi-api token" + }, + "api_endpoint": { + "required": true, + "help": "API endpoint to use" + }, + "full_profile": { + "default": false, + "type": "bool", + "help": "if true, will download all posts, tagged posts, stories, and highlights for a profile, if false, will only download the profile pic and information." + }, + "full_profile_max_posts": { + "default": 0, + "type": "int", + "help": "Use to limit the number of posts to download when full_profile is true. 0 means no limit. limit is applied softly since posts are fetched in batch, once to: posts, tagged posts, and highlights" + }, + "minimize_json_output": { + "default": true, + "type": "bool", + "help": "if true, will remove empty values from the json output" + } + } + }, + "configs": { + "access_token": { + "default": null, + "help": "a valid instagrapi-api token" + }, + "api_endpoint": { + "required": true, + "help": "API endpoint to use" + }, + "full_profile": { + "default": false, + "type": "bool", + "help": "if true, will download all posts, tagged posts, stories, and highlights for a profile, if false, will only download the profile pic and information." + }, + "full_profile_max_posts": { + "default": 0, + "type": "int", + "help": "Use to limit the number of posts to download when full_profile is true. 0 means no limit. limit is applied softly since posts are fetched in batch, once to: posts, tagged posts, and highlights" + }, + "minimize_json_output": { + "default": true, + "type": "bool", + "help": "if true, will remove empty values from the json output" + } + } + }, + "instagram_tbot_extractor": { + "name": "instagram_tbot_extractor", + "display_name": "Instagram Telegram Bot Extractor", + "manifest": { + "name": "Instagram Telegram Bot Extractor", + "author": "Bellingcat", + "type": [ + "extractor" + ], + "requires_setup": true, + "description": "\nThe `InstagramTbotExtractor` module uses a Telegram bot (`instagram_load_bot`) to fetch and archive Instagram content,\nsuch as posts and stories. It leverages the Telethon library to interact with the Telegram API, sending Instagram URLs\nto the bot and downloading the resulting media and metadata. The downloaded content is stored as `Media` objects and\nreturned as part of a `Metadata` object.\n\n### Features\n- Supports archiving Instagram posts and stories through the Telegram bot.\n- Downloads and saves media files (e.g., images, videos) in a temporary directory.\n- Captures and returns metadata, including titles and descriptions, as a `Metadata` object.\n- Automatically manages Telegram session files for secure access.\n\n### Setup\n\nTo use the `InstagramTbotExtractor`, you need to provide the following configuration settings:\n- **API ID and Hash**: Telegram API credentials obtained from [my.telegram.org/apps](https://my.telegram.org/apps).\n- **Session File**: Optional path to store the Telegram session file for future use.\n- The session file is created automatically and should be unique for each instance.\n- You may need to enter your Telegram credentials (phone) and use the a 2FA code sent to you the first time you run the extractor.:\n```2025-01-30 00:43:49.348 | INFO | auto_archiver.modules.instagram_tbot_extractor.instagram_tbot_extractor:setup:36 - SETUP instagram_tbot_extractor checking login...\nPlease enter your phone (or bot token): +447123456789\nPlease enter the code you received: 00000\nSigned in successfully as E C; remember to not break the ToS or you will risk an account ban!\n```\n ", + "dependencies": { + "python": [ + "loguru", + "telethon" + ] + }, + "entry_point": "", + "version": "1.0", + "configs": { + "api_id": { + "default": null, + "help": "telegram API_ID value, go to https://my.telegram.org/apps" + }, + "api_hash": { + "default": null, + "help": "telegram API_HASH value, go to https://my.telegram.org/apps" + }, + "session_file": { + "default": "secrets/anon-insta", + "help": "optional, records the telegram login session for future usage, '.session' will be appended to the provided value." + }, + "timeout": { + "default": 45, + "type": "int", + "help": "timeout to fetch the instagram content in seconds." + } + } + }, + "configs": { + "api_id": { + "default": null, + "help": "telegram API_ID value, go to https://my.telegram.org/apps" + }, + "api_hash": { + "default": null, + "help": "telegram API_HASH value, go to https://my.telegram.org/apps" + }, + "session_file": { + "default": "secrets/anon-insta", + "help": "optional, records the telegram login session for future usage, '.session' will be appended to the provided value." + }, + "timeout": { + "default": 45, + "type": "int", + "help": "timeout to fetch the instagram content in seconds." + } + } + }, + "twitter_api_extractor": { + "name": "twitter_api_extractor", + "display_name": "Twitter API Extractor", + "manifest": { + "name": "Twitter API Extractor", + "author": "Bellingcat", + "type": [ + "extractor" + ], + "requires_setup": true, + "description": "\n The `TwitterApiExtractor` fetches tweets and associated media using the Twitter API. \n It supports multiple API configurations for extended rate limits and reliable access. \n Features include URL expansion, media downloads (e.g., images, videos), and structured output \n via `Metadata` and `Media` objects. Requires Twitter API credentials such as bearer tokens \n or consumer key/secret and access token/secret.\n \n ### Features\n - Fetches tweets and their metadata, including text, creation timestamp, and author information.\n - Downloads media attachments (e.g., images, videos) in high quality.\n - Supports multiple API configurations for improved rate limiting.\n - Expands shortened URLs (e.g., `t.co` links).\n - Outputs structured metadata and media using `Metadata` and `Media` objects.\n \n ### Setup\n To use the `TwitterApiExtractor`, you must provide valid Twitter API credentials via configuration:\n - **Bearer Token(s)**: A single token or a list for rate-limited API access.\n - **Consumer Key and Secret**: Required for user-authenticated API access.\n - **Access Token and Secret**: Complements the consumer key for enhanced API capabilities.\n \n Credentials can be obtained by creating a Twitter developer account at [Twitter Developer Platform](https://developer.twitter.com/en).\n ", + "dependencies": { + "python": [ + "requests", + "loguru", + "pytwitter", + "slugify" + ], + "bin": [ + "" + ] + }, + "entry_point": "", + "version": "1.0", + "configs": { + "bearer_token": { + "default": null, + "help": "[deprecated: see bearer_tokens] twitter API bearer_token which is enough for archiving, if not provided you will need consumer_key, consumer_secret, access_token, access_secret" + }, + "bearer_tokens": { + "default": [], + "help": " a list of twitter API bearer_token which is enough for archiving, if not provided you will need consumer_key, consumer_secret, access_token, access_secret, if provided you can still add those for better rate limits. CSV of bearer tokens if provided via the command line" + }, + "consumer_key": { + "default": null, + "help": "twitter API consumer_key" + }, + "consumer_secret": { + "default": null, + "help": "twitter API consumer_secret" + }, + "access_token": { + "default": null, + "help": "twitter API access_token" + }, + "access_secret": { + "default": null, + "help": "twitter API access_secret" + } + } + }, + "configs": { + "bearer_token": { + "default": null, + "help": "[deprecated: see bearer_tokens] twitter API bearer_token which is enough for archiving, if not provided you will need consumer_key, consumer_secret, access_token, access_secret" + }, + "bearer_tokens": { + "default": [], + "help": " a list of twitter API bearer_token which is enough for archiving, if not provided you will need consumer_key, consumer_secret, access_token, access_secret, if provided you can still add those for better rate limits. CSV of bearer tokens if provided via the command line" + }, + "consumer_key": { + "default": null, + "help": "twitter API consumer_key" + }, + "consumer_secret": { + "default": null, + "help": "twitter API consumer_secret" + }, + "access_token": { + "default": null, + "help": "twitter API access_token" + }, + "access_secret": { + "default": null, + "help": "twitter API access_secret" + } + } + }, + "instagram_extractor": { + "name": "instagram_extractor", + "display_name": "Instagram Extractor", + "manifest": { + "name": "Instagram Extractor", + "author": "Bellingcat", + "type": [ + "extractor" + ], + "requires_setup": true, + "description": "\n Uses the [Instaloader library](https://instaloader.github.io/as-module.html) to download content from Instagram. This class handles both individual posts\n and user profiles, downloading as much information as possible, including images, videos, text, stories,\n highlights, and tagged posts. \n Authentication is required via username/password or a session file.\n \n ", + "dependencies": { + "python": [ + "instaloader", + "loguru" + ] + }, + "entry_point": "", + "version": "1.0", + "configs": { + "username": { + "required": true, + "help": "a valid Instagram username" + }, + "password": { + "required": true, + "help": "the corresponding Instagram account password" + }, + "download_folder": { + "default": "instaloader", + "help": "name of a folder to temporarily download content to" + }, + "session_file": { + "default": "secrets/instaloader.session", + "help": "path to the instagram session which saves session credentials" + } + } + }, + "configs": { + "username": { + "required": true, + "help": "a valid Instagram username" + }, + "password": { + "required": true, + "help": "the corresponding Instagram account password" + }, + "download_folder": { + "default": "instaloader", + "help": "name of a folder to temporarily download content to" + }, + "session_file": { + "default": "secrets/instaloader.session", + "help": "path to the instagram session which saves session credentials" + } + } + }, + "telethon_extractor": { + "name": "telethon_extractor", + "display_name": "Telethon Extractor", + "manifest": { + "name": "Telethon Extractor", + "author": "Bellingcat", + "type": [ + "extractor" + ], + "requires_setup": true, + "description": "\nThe `TelethonExtractor` uses the Telethon library to archive posts and media from Telegram channels and groups. \nIt supports private and public channels, downloading grouped posts with media, and can join channels using invite links \nif provided in the configuration. \n\n### Features\n- Fetches posts and metadata from Telegram channels and groups, including private channels.\n- Downloads media attachments (e.g., images, videos, audio) from individual posts or grouped posts.\n- Handles channel invites to join channels dynamically during setup.\n- Utilizes Telethon's capabilities for reliable Telegram interactions.\n- Outputs structured metadata and media using `Metadata` and `Media` objects.\n\n### Setup\nTo use the `TelethonExtractor`, you must configure the following:\n- **API ID and API Hash**: Obtain these from [my.telegram.org](https://my.telegram.org/apps).\n- **Session File**: Optional, but records login sessions for future use (default: `secrets/anon.session`).\n- **Bot Token**: Optional, allows access to additional content (e.g., large videos) but limits private channel archiving.\n- **Channel Invites**: Optional, specify a JSON string of invite links to join channels during setup.\n\n### First Time Login\nThe first time you run, you will be prompted to do a authentication with the phone number associated, alternatively you can put your `anon.session` in the root.\n\n\n", + "dependencies": { + "python": [ + "telethon", + "loguru", + "tqdm" + ], + "bin": [ + "" + ] + }, + "entry_point": "", + "version": "1.0", + "configs": { + "api_id": { + "default": null, + "help": "telegram API_ID value, go to https://my.telegram.org/apps" + }, + "api_hash": { + "default": null, + "help": "telegram API_HASH value, go to https://my.telegram.org/apps" + }, + "bot_token": { + "default": null, + "help": "optional, but allows access to more content such as large videos, talk to @botfather" + }, + "session_file": { + "default": "secrets/anon", + "help": "optional, records the telegram login session for future usage, '.session' will be appended to the provided value." + }, + "join_channels": { + "default": true, + "help": "disables the initial setup with channel_invites config, useful if you have a lot and get stuck" + }, + "channel_invites": { + "default": {}, + "help": "(JSON string) private channel invite links (format: t.me/joinchat/HASH OR t.me/+HASH) and (optional but important to avoid hanging for minutes on startup) channel id (format: CHANNEL_ID taken from a post url like https://t.me/c/CHANNEL_ID/1), the telegram account will join any new channels on setup", + "type": "json_loader" + } + } + }, + "configs": { + "api_id": { + "default": null, + "help": "telegram API_ID value, go to https://my.telegram.org/apps" + }, + "api_hash": { + "default": null, + "help": "telegram API_HASH value, go to https://my.telegram.org/apps" + }, + "bot_token": { + "default": null, + "help": "optional, but allows access to more content such as large videos, talk to @botfather" + }, + "session_file": { + "default": "secrets/anon", + "help": "optional, records the telegram login session for future usage, '.session' will be appended to the provided value." + }, + "join_channels": { + "default": true, + "help": "disables the initial setup with channel_invites config, useful if you have a lot and get stuck" + }, + "channel_invites": { + "default": {}, + "help": "(JSON string) private channel invite links (format: t.me/joinchat/HASH OR t.me/+HASH) and (optional but important to avoid hanging for minutes on startup) channel id (format: CHANNEL_ID taken from a post url like https://t.me/c/CHANNEL_ID/1), the telegram account will join any new channels on setup", + "type": "json_loader" + } + } + }, + "vk_extractor": { + "name": "vk_extractor", + "display_name": "VKontakte Extractor", + "manifest": { + "name": "VKontakte Extractor", + "author": "Bellingcat", + "type": [ + "extractor" + ], + "requires_setup": true, + "description": "\nThe `VkExtractor` fetches posts, text, and images from VK (VKontakte) social media pages. \nThis archiver is specialized for `/wall` posts and uses the `VkScraper` library to extract \nand download content. Note that VK videos are handled separately by the `YTDownloader`.\n\n### Features\n- Extracts text, timestamps, and metadata from VK `/wall` posts.\n- Downloads associated images and attaches them to the resulting `Metadata` object.\n- Processes multiple segments of VK URLs that contain mixed content (e.g., wall, photo).\n- Outputs structured metadata and media using `Metadata` and `Media` objects.\n\n### Setup\nTo use the `VkArchiver`, you must provide valid VKontakte login credentials and session information:\n- **Username**: A valid VKontakte account username.\n- **Password**: The corresponding password for the VKontakte account.\n- **Session File**: Optional. Path to a session configuration file (`.json`) for persistent VK login.\n\nCredentials can be set in the configuration file or directly via environment variables. Ensure you \nhave access to the VKontakte API by creating an account at [VKontakte](https://vk.com/).\n", + "dependencies": { + "python": [ + "loguru", + "vk_url_scraper" + ] + }, + "entry_point": "", + "version": "1.0", + "configs": { + "username": { + "required": true, + "help": "valid VKontakte username" + }, + "password": { + "required": true, + "help": "valid VKontakte password" + }, + "session_file": { + "default": "secrets/vk_config.v2.json", + "help": "valid VKontakte password" + } + }, + "depends": [ + "core", + "utils" + ] + }, + "configs": { + "username": { + "required": true, + "help": "valid VKontakte username" + }, + "password": { + "required": true, + "help": "valid VKontakte password" + }, + "session_file": { + "default": "secrets/vk_config.v2.json", + "help": "valid VKontakte password" + } + } + }, + "generic_extractor": { + "name": "generic_extractor", + "display_name": "Generic Extractor", + "manifest": { + "name": "Generic Extractor", + "author": "Bellingcat", + "type": [ + "extractor" + ], + "requires_setup": false, + "description": "\nThis is the generic extractor used by auto-archiver, which uses `yt-dlp` under the hood.\n\nThis module is responsible for downloading and processing media content from platforms\nsupported by `yt-dlp`, such as YouTube, Facebook, and others. It provides functionality\nfor retrieving videos, subtitles, comments, and other metadata, and it integrates with\nthe broader archiving framework.\n\n### Features\n- Supports downloading videos and playlists.\n- Retrieves metadata like titles, descriptions, upload dates, and durations.\n- Downloads subtitles and comments when enabled.\n- Configurable options for handling live streams, proxies, and more.\n- Supports authentication of websites using the 'authentication' settings from your orchestration.\n\n### Dropins\n- For websites supported by `yt-dlp` that also contain posts in addition to videos\n (e.g. Facebook, Twitter, Bluesky), dropins can be created to extract post data and create \n metadata objects. Some dropins are included in this generic_archiver by default, but\ncustom dropins can be created to handle additional websites and passed to the archiver\nvia the command line using the `--dropins` option (TODO!).\n", + "dependencies": { + "python": [ + "yt_dlp", + "requests", + "loguru", + "slugify" + ] + }, + "entry_point": "", + "version": "0.1.0", + "configs": { + "subtitles": { + "default": true, + "help": "download subtitles if available", + "type": "bool" + }, + "comments": { + "default": false, + "help": "download all comments if available, may lead to large metadata", + "type": "bool" + }, + "livestreams": { + "default": false, + "help": "if set, will download live streams, otherwise will skip them; see --max-filesize for more control", + "type": "bool" + }, + "live_from_start": { + "default": false, + "help": "if set, will download live streams from their earliest available moment, otherwise starts now.", + "type": "bool" + }, + "proxy": { + "default": "", + "help": "http/socks (https seems to not work atm) proxy to use for the webdriver, eg https://proxy-user:password@proxy-ip:port" + }, + "end_means_success": { + "default": true, + "help": "if True, any archived content will mean a 'success', if False this archiver will not return a 'success' stage; this is useful for cases when the yt-dlp will archive a video but ignore other types of content like images or text only pages that the subsequent archivers can retrieve.", + "type": "bool" + }, + "allow_playlist": { + "default": false, + "help": "If True will also download playlists, set to False if the expectation is to download a single video.", + "type": "bool" + }, + "max_downloads": { + "default": "inf", + "help": "Use to limit the number of videos to download when a channel or long page is being extracted. 'inf' means no limit." + } + } + }, + "configs": { + "subtitles": { + "default": true, + "help": "download subtitles if available", + "type": "bool" + }, + "comments": { + "default": false, + "help": "download all comments if available, may lead to large metadata", + "type": "bool" + }, + "livestreams": { + "default": false, + "help": "if set, will download live streams, otherwise will skip them; see --max-filesize for more control", + "type": "bool" + }, + "live_from_start": { + "default": false, + "help": "if set, will download live streams from their earliest available moment, otherwise starts now.", + "type": "bool" + }, + "proxy": { + "default": "", + "help": "http/socks (https seems to not work atm) proxy to use for the webdriver, eg https://proxy-user:password@proxy-ip:port" + }, + "end_means_success": { + "default": true, + "help": "if True, any archived content will mean a 'success', if False this archiver will not return a 'success' stage; this is useful for cases when the yt-dlp will archive a video but ignore other types of content like images or text only pages that the subsequent archivers can retrieve.", + "type": "bool" + }, + "allow_playlist": { + "default": false, + "help": "If True will also download playlists, set to False if the expectation is to download a single video.", + "type": "bool" + }, + "max_downloads": { + "default": "inf", + "help": "Use to limit the number of videos to download when a channel or long page is being extracted. 'inf' means no limit." + } + } + }, + "telegram_extractor": { + "name": "telegram_extractor", + "display_name": "Telegram Extractor", + "manifest": { + "name": "Telegram Extractor", + "author": "Bellingcat", + "type": [ + "extractor" + ], + "requires_setup": false, + "description": " \n The `TelegramExtractor` retrieves publicly available media content from Telegram message links without requiring login credentials. \n It processes URLs to fetch images and videos embedded in Telegram messages, ensuring a structured output using `Metadata` \n and `Media` objects. Recommended for scenarios where login-based archiving is not viable, although `telethon_archiver` \n is advised for more comprehensive functionality, and higher quality media extraction.\n \n ### Features\n- Extracts images and videos from public Telegram message links (`t.me`).\n- Processes HTML content of messages to retrieve embedded media.\n- Sets structured metadata, including timestamps, content, and media details.\n- Does not require user authentication for Telegram.\n\n ", + "dependencies": { + "python": [ + "requests", + "bs4", + "loguru" + ] + }, + "entry_point": "", + "version": "1.0", + "configs": {} + }, + "configs": null + }, + "wayback_extractor_enricher": { + "name": "wayback_extractor_enricher", + "display_name": "Wayback Machine Enricher (and Extractor)", + "manifest": { + "name": "Wayback Machine Enricher (and Extractor)", + "author": "Bellingcat", + "type": [ + "enricher", + "extractor" + ], + "requires_setup": true, + "description": "\n Submits the current URL to the Wayback Machine for archiving and returns either a job ID or the completed archive URL.\n\n ### Features\n - Archives URLs using the Internet Archive's Wayback Machine API.\n - Supports conditional archiving based on the existence of prior archives within a specified time range.\n - Provides proxies for HTTP and HTTPS requests.\n - Fetches and confirms the archive URL or provides a job ID for later status checks.\n\n ### Notes\n - Requires a valid Wayback Machine API key and secret.\n - Handles rate-limiting by Wayback Machine and retries status checks with exponential backoff.\n \n ### Steps to Get an Wayback API Key:\n - Sign up for an account at [Internet Archive](https://archive.org/account/signup).\n - Log in to your account.\n - Navigte to your [account settings](https://archive.org/account).\n - or: https://archive.org/developers/tutorial-get-ia-credentials.html\n - Under Wayback Machine API Keys, generate a new key.\n - Note down your API key and secret, as they will be required for authentication.\n ", + "dependencies": { + "python": [ + "loguru", + "requests" + ] + }, + "entry_point": "wayback_extractor_enricher::WaybackExtractorEnricher", + "version": "1.0", + "configs": { + "timeout": { + "default": 15, + "help": "seconds to wait for successful archive confirmation from wayback, if more than this passes the result contains the job_id so the status can later be checked manually." + }, + "if_not_archived_within": { + "default": null, + "help": "only tell wayback to archive if no archive is available before the number of seconds specified, use None to ignore this option. For more information: https://docs.google.com/document/d/1Nsv52MvSjbLb2PCpHlat0gkzw0EvtSgpKHu4mk0MnrA" + }, + "key": { + "required": true, + "help": "wayback API key. to get credentials visit https://archive.org/account/s3.php" + }, + "secret": { + "required": true, + "help": "wayback API secret. to get credentials visit https://archive.org/account/s3.php" + }, + "proxy_http": { + "default": null, + "help": "http proxy to use for wayback requests, eg http://proxy-user:password@proxy-ip:port" + }, + "proxy_https": { + "default": null, + "help": "https proxy to use for wayback requests, eg https://proxy-user:password@proxy-ip:port" + } + } + }, + "configs": { + "timeout": { + "default": 15, + "help": "seconds to wait for successful archive confirmation from wayback, if more than this passes the result contains the job_id so the status can later be checked manually." + }, + "if_not_archived_within": { + "default": null, + "help": "only tell wayback to archive if no archive is available before the number of seconds specified, use None to ignore this option. For more information: https://docs.google.com/document/d/1Nsv52MvSjbLb2PCpHlat0gkzw0EvtSgpKHu4mk0MnrA" + }, + "key": { + "required": true, + "help": "wayback API key. to get credentials visit https://archive.org/account/s3.php" + }, + "secret": { + "required": true, + "help": "wayback API secret. to get credentials visit https://archive.org/account/s3.php" + }, + "proxy_http": { + "default": null, + "help": "http proxy to use for wayback requests, eg http://proxy-user:password@proxy-ip:port" + }, + "proxy_https": { + "default": null, + "help": "https proxy to use for wayback requests, eg https://proxy-user:password@proxy-ip:port" + } + } + }, + "wacz_extractor_enricher": { + "name": "wacz_extractor_enricher", + "display_name": "WACZ Enricher (and Extractor)", + "manifest": { + "name": "WACZ Enricher (and Extractor)", + "author": "Bellingcat", + "type": [ + "enricher", + "extractor" + ], + "requires_setup": true, + "description": "\n Creates .WACZ archives of web pages using the `browsertrix-crawler` tool, with options for media extraction and screenshot saving.\n [Browsertrix-crawler](https://crawler.docs.browsertrix.com/user-guide/) is a headless browser-based crawler that archives web pages in WACZ format.\n\n ### Features\n - Archives web pages into .WACZ format using Docker or direct invocation of `browsertrix-crawler`.\n - Supports custom profiles for archiving private or dynamic content.\n - Extracts media (images, videos, audio) and screenshots from the archive, optionally adding them to the enrichment pipeline.\n - Generates metadata from the archived page's content and structure (e.g., titles, text).\n\n ### Notes\n - Requires Docker for running `browsertrix-crawler` .\n - Configurable via parameters for timeout, media extraction, screenshots, and proxy settings.\n ", + "dependencies": { + "python": [ + "loguru", + "jsonlines", + "warcio" + ], + "bin": [ + "docker" + ] + }, + "entry_point": "wacz_extractor_enricher::WaczExtractorEnricher", + "version": "1.0", + "configs": { + "profile": { + "default": null, + "help": "browsertrix-profile (for profile generation see https://github.com/webrecorder/browsertrix-crawler#creating-and-using-browser-profiles)." + }, + "docker_commands": { + "default": null, + "help": "if a custom docker invocation is needed" + }, + "timeout": { + "default": 120, + "help": "timeout for WACZ generation in seconds", + "type": "int" + }, + "extract_media": { + "default": false, + "type": "bool", + "help": "If enabled all the images/videos/audio present in the WACZ archive will be extracted into separate Media and appear in the html report. The .wacz file will be kept untouched." + }, + "extract_screenshot": { + "default": true, + "type": "bool", + "help": "If enabled the screenshot captured by browsertrix will be extracted into separate Media and appear in the html report. The .wacz file will be kept untouched." + }, + "socks_proxy_host": { + "default": null, + "help": "SOCKS proxy host for browsertrix-crawler, use in combination with socks_proxy_port. eg: user:password@host" + }, + "socks_proxy_port": { + "default": null, + "type": "int", + "help": "SOCKS proxy port for browsertrix-crawler, use in combination with socks_proxy_host. eg 1234" + }, + "proxy_server": { + "default": null, + "help": "SOCKS server proxy URL, in development" + } + } + }, + "configs": { + "profile": { + "default": null, + "help": "browsertrix-profile (for profile generation see https://github.com/webrecorder/browsertrix-crawler#creating-and-using-browser-profiles)." + }, + "docker_commands": { + "default": null, + "help": "if a custom docker invocation is needed" + }, + "timeout": { + "default": 120, + "help": "timeout for WACZ generation in seconds", + "type": "int" + }, + "extract_media": { + "default": false, + "type": "bool", + "help": "If enabled all the images/videos/audio present in the WACZ archive will be extracted into separate Media and appear in the html report. The .wacz file will be kept untouched." + }, + "extract_screenshot": { + "default": true, + "type": "bool", + "help": "If enabled the screenshot captured by browsertrix will be extracted into separate Media and appear in the html report. The .wacz file will be kept untouched." + }, + "socks_proxy_host": { + "default": null, + "help": "SOCKS proxy host for browsertrix-crawler, use in combination with socks_proxy_port. eg: user:password@host" + }, + "socks_proxy_port": { + "default": null, + "type": "int", + "help": "SOCKS proxy port for browsertrix-crawler, use in combination with socks_proxy_host. eg 1234" + }, + "proxy_server": { + "default": null, + "help": "SOCKS server proxy URL, in development" + } + } + }, + "metadata_enricher": { + "name": "metadata_enricher", + "display_name": "Media Metadata Enricher", + "manifest": { + "name": "Media Metadata Enricher", + "author": "Bellingcat", + "type": [ + "enricher" + ], + "requires_setup": true, + "description": "\n Extracts metadata information from files using ExifTool.\n\n ### Features\n - Uses ExifTool to extract detailed metadata from media files.\n - Processes file-specific data like camera settings, geolocation, timestamps, and other embedded metadata.\n - Adds extracted metadata to the corresponding `Media` object within the `Metadata`.\n\n ### Notes\n - Requires ExifTool to be installed and accessible via the system's PATH.\n - Skips enrichment for files where metadata extraction fails.\n ", + "dependencies": { + "python": [ + "loguru" + ], + "bin": [ + "exiftool" + ] + }, + "entry_point": "", + "version": "1.0", + "configs": {} + }, + "configs": null + }, + "timestamping_enricher": { + "name": "timestamping_enricher", + "display_name": "Timestamping Enricher", + "manifest": { + "name": "Timestamping Enricher", + "author": "Bellingcat", + "type": [ + "enricher" + ], + "requires_setup": true, + "description": "\n Generates RFC3161-compliant timestamp tokens using Time Stamp Authorities (TSA) for archived files.\n\n ### Features\n - Creates timestamp tokens to prove the existence of files at a specific time, useful for legal and authenticity purposes.\n - Aggregates file hashes into a text file and timestamps the concatenated data.\n - Uses multiple Time Stamp Authorities (TSAs) to ensure reliability and redundancy.\n - Validates timestamping certificates against trusted Certificate Authorities (CAs) using the `certifi` trust store.\n\n ### Notes\n - Should be run after the `hash_enricher` to ensure file hashes are available.\n - Requires internet access to interact with the configured TSAs.\n ", + "dependencies": { + "python": [ + "loguru", + "slugify", + "tsp_client", + "asn1crypto", + "certvalidator", + "certifi" + ] + }, + "entry_point": "", + "version": "1.0", + "configs": { + "tsa_urls": { + "default": [ + "http://timestamp.digicert.com", + "http://timestamp.identrust.com", + "http://timestamp.globalsign.com/tsa/r6advanced1", + "http://tss.accv.es:8318/tsa" + ], + "help": "List of RFC3161 Time Stamp Authorities to use, separate with commas if passed via the command line." + } + } + }, + "configs": { + "tsa_urls": { + "default": [ + "http://timestamp.digicert.com", + "http://timestamp.identrust.com", + "http://timestamp.globalsign.com/tsa/r6advanced1", + "http://tss.accv.es:8318/tsa" + ], + "help": "List of RFC3161 Time Stamp Authorities to use, separate with commas if passed via the command line." + } + } + }, + "screenshot_enricher": { + "name": "screenshot_enricher", + "display_name": "Screenshot Enricher", + "manifest": { + "name": "Screenshot Enricher", + "author": "Bellingcat", + "type": [ + "enricher" + ], + "requires_setup": true, + "description": "\n Captures screenshots and optionally saves web pages as PDFs using a WebDriver.\n\n ### Features\n - Takes screenshots of web pages, with configurable width, height, and timeout settings.\n - Optionally saves pages as PDFs, with additional configuration for PDF printing options.\n - Bypasses URLs detected as authentication walls.\n - Integrates seamlessly with the metadata enrichment pipeline, adding screenshots and PDFs as media.\n\n ### Notes\n - Requires a WebDriver (e.g., ChromeDriver) installed and accessible via the system's PATH.\n ", + "dependencies": { + "python": [ + "loguru", + "selenium" + ] + }, + "entry_point": "", + "version": "1.0", + "configs": { + "width": { + "default": 1280, + "help": "width of the screenshots" + }, + "height": { + "default": 720, + "help": "height of the screenshots" + }, + "timeout": { + "default": 60, + "help": "timeout for taking the screenshot" + }, + "sleep_before_screenshot": { + "default": 4, + "help": "seconds to wait for the pages to load before taking screenshot" + }, + "http_proxy": { + "default": "", + "help": "http proxy to use for the webdriver, eg http://proxy-user:password@proxy-ip:port" + }, + "save_to_pdf": { + "default": false, + "help": "save the page as pdf along with the screenshot. PDF saving options can be adjusted with the 'print_options' parameter" + }, + "print_options": { + "default": {}, + "help": "options to pass to the pdf printer" + } + } + }, + "configs": { + "width": { + "default": 1280, + "help": "width of the screenshots" + }, + "height": { + "default": 720, + "help": "height of the screenshots" + }, + "timeout": { + "default": 60, + "help": "timeout for taking the screenshot" + }, + "sleep_before_screenshot": { + "default": 4, + "help": "seconds to wait for the pages to load before taking screenshot" + }, + "http_proxy": { + "default": "", + "help": "http proxy to use for the webdriver, eg http://proxy-user:password@proxy-ip:port" + }, + "save_to_pdf": { + "default": false, + "help": "save the page as pdf along with the screenshot. PDF saving options can be adjusted with the 'print_options' parameter" + }, + "print_options": { + "default": {}, + "help": "options to pass to the pdf printer" + } + } + }, + "whisper_enricher": { + "name": "whisper_enricher", + "display_name": "Whisper Enricher", + "manifest": { + "name": "Whisper Enricher", + "author": "Bellingcat", + "type": [ + "enricher" + ], + "requires_setup": true, + "description": "\n Integrates with a Whisper API service to transcribe, translate, or detect the language of audio and video files.\n\n ### Features\n - Submits audio or video files to a Whisper API deployment for processing.\n - Supports operations such as transcription, translation, and language detection.\n - Optionally generates SRT subtitle files for video content.\n - Integrates with S3-compatible storage systems to make files publicly accessible for processing.\n - Handles job submission, status checking, artifact retrieval, and cleanup.\n\n ### Notes\n - Requires a Whisper API endpoint and API key for authentication.\n - Only compatible with S3-compatible storage systems for media file accessibility.\n - ** This stores the media files in S3 prior to enriching them as Whisper requires public URLs to access the media files.\n - Handles multiple jobs and retries for failed or incomplete processing.\n ", + "dependencies": { + "python": [ + "s3_storage", + "loguru", + "requests" + ] + }, + "entry_point": "", + "version": "1.0", + "configs": { + "api_endpoint": { + "required": true, + "help": "WhisperApi api endpoint, eg: https://whisperbox-api.com/api/v1, a deployment of https://github.com/bellingcat/whisperbox-transcribe." + }, + "api_key": { + "required": true, + "help": "WhisperApi api key for authentication" + }, + "include_srt": { + "default": false, + "help": "Whether to include a subtitle SRT (SubRip Subtitle file) for the video (can be used in video players)." + }, + "timeout": { + "default": 90, + "help": "How many seconds to wait at most for a successful job completion." + }, + "action": { + "default": "translate", + "help": "which Whisper operation to execute", + "choices": [ + "transcribe", + "translate", + "language_detection" + ] + } + } + }, + "configs": { + "api_endpoint": { + "required": true, + "help": "WhisperApi api endpoint, eg: https://whisperbox-api.com/api/v1, a deployment of https://github.com/bellingcat/whisperbox-transcribe." + }, + "api_key": { + "required": true, + "help": "WhisperApi api key for authentication" + }, + "include_srt": { + "default": false, + "help": "Whether to include a subtitle SRT (SubRip Subtitle file) for the video (can be used in video players)." + }, + "timeout": { + "default": 90, + "help": "How many seconds to wait at most for a successful job completion." + }, + "action": { + "default": "translate", + "help": "which Whisper operation to execute", + "choices": [ + "transcribe", + "translate", + "language_detection" + ] + } + } + }, + "thumbnail_enricher": { + "name": "thumbnail_enricher", + "display_name": "Thumbnail Enricher", + "manifest": { + "name": "Thumbnail Enricher", + "author": "Bellingcat", + "type": [ + "enricher" + ], + "requires_setup": false, + "description": "\n Generates thumbnails for video files to provide visual previews.\n\n ### Features\n - Processes video files and generates evenly distributed thumbnails.\n - Calculates the number of thumbnails based on video duration, `thumbnails_per_minute`, and `max_thumbnails`.\n - Distributes thumbnails equally across the video's duration and stores them as media objects.\n - Adds metadata for each thumbnail, including timestamps and IDs.\n\n ### Notes\n - Requires `ffmpeg` to be installed and accessible via the system's PATH.\n - Handles videos without pre-existing duration metadata by probing with `ffmpeg`.\n - Skips enrichment for non-video media files.\n ", + "dependencies": { + "python": [ + "loguru", + "ffmpeg" + ], + "bin": [ + "ffmpeg" + ] + }, + "entry_point": "", + "version": "1.0", + "configs": { + "thumbnails_per_minute": { + "default": 60, + "type": "int", + "help": "how many thumbnails to generate per minute of video, can be limited by max_thumbnails" + }, + "max_thumbnails": { + "default": 16, + "type": "int", + "help": "limit the number of thumbnails to generate per video, 0 means no limit" + } + } + }, + "configs": { + "thumbnails_per_minute": { + "default": 60, + "type": "int", + "help": "how many thumbnails to generate per minute of video, can be limited by max_thumbnails" + }, + "max_thumbnails": { + "default": 16, + "type": "int", + "help": "limit the number of thumbnails to generate per video, 0 means no limit" + } + } + }, + "meta_enricher": { + "name": "meta_enricher", + "display_name": "Archive Metadata Enricher", + "manifest": { + "name": "Archive Metadata Enricher", + "author": "Bellingcat", + "type": [ + "enricher" + ], + "requires_setup": false, + "description": " \n Adds metadata information about the archive operations, Adds metadata about archive operations, including file sizes and archive duration./\n To be included at the end of all enrichments.\n \n ### Features\n- Calculates the total size of all archived media files, storing the result in human-readable and byte formats.\n- Computes the duration of the archival process, storing the elapsed time in seconds.\n- Ensures all enrichments are performed only if the `Metadata` object contains valid data.\n- Adds detailed metadata to provide insights into file sizes and archival performance.\n\n### Notes\n- Skips enrichment if no media or metadata is available in the `Metadata` object.\n- File sizes are calculated using the `os.stat` module, ensuring accurate byte-level reporting.\n", + "dependencies": { + "python": [ + "loguru" + ] + }, + "entry_point": "", + "version": "1.0", + "configs": {} + }, + "configs": null + }, + "pdq_hash_enricher": { + "name": "pdq_hash_enricher", + "display_name": "PDQ Hash Enricher", + "manifest": { + "name": "PDQ Hash Enricher", + "author": "Bellingcat", + "type": [ + "enricher" + ], + "requires_setup": false, + "description": "\n PDQ Hash Enricher for generating perceptual hashes of media files.\n\n ### Features\n - Calculates perceptual hashes for image files using the PDQ hashing algorithm.\n - Enables detection of duplicate or near-duplicate visual content.\n - Processes images stored in `Metadata` objects, adding computed hashes to the corresponding `Media` entries.\n - Skips non-image media or files unsuitable for hashing (e.g., corrupted or unsupported formats).\n\n ### Notes\n - Best used after enrichers like `thumbnail_enricher` or `screenshot_enricher` to ensure images are available.\n - Uses the `pdqhash` library to compute 256-bit perceptual hashes, which are stored as hexadecimal strings.\n ", + "dependencies": { + "python": [ + "loguru", + "pdqhash", + "numpy", + "PIL" + ] + }, + "entry_point": "", + "version": "1.0", + "configs": {} + }, + "configs": null + }, + "ssl_enricher": { + "name": "ssl_enricher", + "display_name": "SSL Certificate Enricher", + "manifest": { + "name": "SSL Certificate Enricher", + "author": "Bellingcat", + "type": [ + "enricher" + ], + "requires_setup": false, + "description": "\n Retrieves SSL certificate information for a domain and stores it as a file.\n\n ### Features\n - Fetches SSL certificates for domains using the HTTPS protocol.\n - Stores certificates in PEM format and adds them as media to the metadata.\n - Skips enrichment if no media has been archived, based on the `skip_when_nothing_archived` configuration.\n\n ### Notes\n - Requires the target URL to use the HTTPS scheme; other schemes are not supported.\n ", + "dependencies": { + "python": [ + "loguru", + "slugify" + ] + }, + "entry_point": "ssl_enricher::SSLEnricher", + "version": "1.0", + "configs": { + "skip_when_nothing_archived": { + "default": true, + "type": "bool", + "help": "if true, will skip enriching when no media is archived" + } + } + }, + "configs": { + "skip_when_nothing_archived": { + "default": true, + "type": "bool", + "help": "if true, will skip enriching when no media is archived" + } + } + }, + "hash_enricher": { + "name": "hash_enricher", + "display_name": "Hash Enricher", + "manifest": { + "name": "Hash Enricher", + "author": "Bellingcat", + "type": [ + "enricher" + ], + "requires_setup": false, + "description": "\nGenerates cryptographic hashes for media files to ensure data integrity and authenticity.\n\n### Features\n- Calculates cryptographic hashes (SHA-256 or SHA3-512) for media files stored in `Metadata` objects.\n- Ensures content authenticity, integrity validation, and duplicate identification.\n- Efficiently processes large files by reading file bytes in configurable chunk sizes.\n- Supports dynamic configuration of hash algorithms and chunk sizes.\n- Updates media metadata with the computed hash value in the format `:`.\n\n### Notes\n- Default hash algorithm is SHA-256, but SHA3-512 is also supported.\n- Chunk size defaults to 16 MB but can be adjusted based on memory requirements.\n- Useful for workflows requiring hash-based content validation or deduplication.\n", + "dependencies": { + "python": [ + "loguru" + ] + }, + "entry_point": "", + "version": "1.0", + "configs": { + "algorithm": { + "default": "SHA-256", + "help": "hash algorithm to use", + "choices": [ + "SHA-256", + "SHA3-512" + ] + }, + "chunksize": { + "default": 16000000, + "help": "number of bytes to use when reading files in chunks (if this value is too large you will run out of RAM), default is 16MB", + "type": "int" + } + } + }, + "configs": { + "algorithm": { + "default": "SHA-256", + "help": "hash algorithm to use", + "choices": [ + "SHA-256", + "SHA3-512" + ] + }, + "chunksize": { + "default": 16000000, + "help": "number of bytes to use when reading files in chunks (if this value is too large you will run out of RAM), default is 16MB", + "type": "int" + } + } + }, + "atlos_db": { + "name": "atlos_db", + "display_name": "Atlos Database", + "manifest": { + "name": "Atlos Database", + "author": "Bellingcat", + "type": [ + "database" + ], + "requires_setup": true, + "description": "\nHandles integration with the Atlos platform for managing archival results.\n\n### Features\n- Outputs archival results to the Atlos API for storage and tracking.\n- Updates failure status with error details when archiving fails.\n- Processes and formats metadata, including ISO formatting for datetime fields.\n- Skips processing for items without an Atlos ID.\n\n### Setup\nRequired configs:\n- atlos_url: Base URL for the Atlos API.\n- api_token: Authentication token for API access.\n", + "dependencies": { + "python": [ + "loguru", + "" + ], + "bin": [ + "" + ] + }, + "entry_point": "atlos_db::AtlosDb", + "version": "1.0", + "configs": { + "api_token": { + "default": null, + "help": "An Atlos API token. For more information, see https://docs.atlos.org/technical/api/", + "required": true, + "type": "str" + }, + "atlos_url": { + "default": "https://platform.atlos.org", + "help": "The URL of your Atlos instance (e.g., https://platform.atlos.org), without a trailing slash.", + "type": "str" + } + } + }, + "configs": { + "api_token": { + "default": null, + "help": "An Atlos API token. For more information, see https://docs.atlos.org/technical/api/", + "required": true, + "type": "str" + }, + "atlos_url": { + "default": "https://platform.atlos.org", + "help": "The URL of your Atlos instance (e.g., https://platform.atlos.org), without a trailing slash.", + "type": "str" + } + } + }, + "api_db": { + "name": "api_db", + "display_name": "Auto Archiver API Database", + "manifest": { + "name": "Auto Archiver API Database", + "author": "Bellingcat", + "type": [ + "database" + ], + "requires_setup": true, + "description": "\n Provides integration with the Auto Archiver API for querying and storing archival data.\n\n### Features\n- **API Integration**: Supports querying for existing archives and submitting results.\n- **Duplicate Prevention**: Avoids redundant archiving when `use_api_cache` is disabled.\n- **Configurable**: Supports settings like API endpoint, authentication token, tags, and permissions.\n- **Tagging and Metadata**: Adds tags and manages metadata for archives.\n- **Optional Storage**: Archives results conditionally based on configuration.\n\n### Setup\nRequires access to an Auto Archiver API instance and a valid API token.\n ", + "dependencies": { + "python": [ + "requests", + "loguru" + ] + }, + "entry_point": "api_db::AAApiDb", + "version": "1.0", + "configs": { + "api_endpoint": { + "required": true, + "help": "API endpoint where calls are made to" + }, + "api_token": { + "default": null, + "help": "API Bearer token." + }, + "public": { + "default": false, + "type": "bool", + "help": "whether the URL should be publicly available via the API" + }, + "author_id": { + "default": null, + "help": "which email to assign as author" + }, + "group_id": { + "default": null, + "help": "which group of users have access to the archive in case public=false as author" + }, + "use_api_cache": { + "default": true, + "type": "bool", + "help": "if False then the API database will be queried prior to any archiving operations and stop if the link has already been archived" + }, + "store_results": { + "default": true, + "type": "bool", + "help": "when set, will send the results to the API database." + }, + "tags": { + "default": [], + "help": "what tags to add to the archived URL" + } + } + }, + "configs": { + "api_endpoint": { + "required": true, + "help": "API endpoint where calls are made to" + }, + "api_token": { + "default": null, + "help": "API Bearer token." + }, + "public": { + "default": false, + "type": "bool", + "help": "whether the URL should be publicly available via the API" + }, + "author_id": { + "default": null, + "help": "which email to assign as author" + }, + "group_id": { + "default": null, + "help": "which group of users have access to the archive in case public=false as author" + }, + "use_api_cache": { + "default": true, + "type": "bool", + "help": "if False then the API database will be queried prior to any archiving operations and stop if the link has already been archived" + }, + "store_results": { + "default": true, + "type": "bool", + "help": "when set, will send the results to the API database." + }, + "tags": { + "default": [], + "help": "what tags to add to the archived URL" + } + } + }, + "gsheet_db": { + "name": "gsheet_db", + "display_name": "Google Sheets Database", + "manifest": { + "name": "Google Sheets Database", + "author": "Bellingcat", + "type": [ + "database" + ], + "requires_setup": true, + "description": "\n GsheetsDatabase:\n Handles integration with Google Sheets for tracking archival tasks.\n\n### Features\n- Updates a Google Sheet with the status of the archived URLs, including in progress, success or failure, and method used.\n- Saves metadata such as title, text, timestamp, hashes, screenshots, and media URLs to designated columns.\n- Formats media-specific metadata, such as thumbnails and PDQ hashes for the sheet.\n- Skips redundant updates for empty or invalid data fields.\n\n### Notes\n- Currently works only with metadata provided by GsheetFeeder. \n- Requires configuration of a linked Google Sheet and appropriate API credentials.\n ", + "dependencies": { + "python": [ + "loguru", + "gspread", + "slugify" + ] + }, + "entry_point": "gsheet_db::GsheetsDb", + "version": "1.0", + "configs": { + "allow_worksheets": { + "default": [], + "help": "(CSV) only worksheets whose name is included in allow are included (overrides worksheet_block), leave empty so all are allowed" + }, + "block_worksheets": { + "default": [], + "help": "(CSV) explicitly block some worksheets from being processed" + }, + "use_sheet_names_in_stored_paths": { + "default": true, + "type": "bool", + "help": "if True the stored files path will include 'workbook_name/worksheet_name/...'" + } + } + }, + "configs": { + "allow_worksheets": { + "default": [], + "help": "(CSV) only worksheets whose name is included in allow are included (overrides worksheet_block), leave empty so all are allowed" + }, + "block_worksheets": { + "default": [], + "help": "(CSV) explicitly block some worksheets from being processed" + }, + "use_sheet_names_in_stored_paths": { + "default": true, + "type": "bool", + "help": "if True the stored files path will include 'workbook_name/worksheet_name/...'" + } + } + }, + "console_db": { + "name": "console_db", + "display_name": "Console Database", + "manifest": { + "name": "Console Database", + "author": "Bellingcat", + "type": [ + "database" + ], + "requires_setup": false, + "description": "\nProvides a simple database implementation that outputs archival results and status updates to the console.\n\n### Features\n- Logs the status of archival tasks directly to the console, including:\n - started\n - failed (with error details)\n - aborted\n - done (with optional caching status)\n- Useful for debugging or lightweight setups where no external database is required.\n\n### Setup\nNo additional configuration is required.\n", + "dependencies": { + "python": [ + "loguru" + ] + }, + "entry_point": "", + "version": "1.0", + "configs": {} + }, + "configs": null + }, + "csv_db": { + "name": "csv_db", + "display_name": "CSV Database", + "manifest": { + "name": "CSV Database", + "author": "Bellingcat", + "type": [ + "database" + ], + "requires_setup": false, + "description": "\nHandles exporting archival results to a CSV file.\n\n### Features\n- Saves archival metadata as rows in a CSV file.\n- Automatically creates the CSV file with a header if it does not exist.\n- Appends new metadata entries to the existing file.\n\n### Setup\nRequired config:\n- csv_file: Path to the CSV file where results will be stored (default: \"db.csv\").\n", + "dependencies": { + "python": [ + "loguru" + ] + }, + "entry_point": "csv_db::CSVDb", + "version": "1.0", + "configs": { + "csv_file": { + "default": "db.csv", + "help": "CSV file name to save metadata to" + } + } + }, + "configs": { + "csv_file": { + "default": "db.csv", + "help": "CSV file name to save metadata to" + } + } + }, + "gdrive_storage": { + "name": "gdrive_storage", + "display_name": "Google Drive Storage", + "manifest": { + "name": "Google Drive Storage", + "author": "Dave Mateer", + "type": [ + "storage" + ], + "requires_setup": true, + "description": "\n \n GDriveStorage: A storage module for saving archived content to Google Drive.\n\n Source Documentation: https://davemateer.com/2022/04/28/google-drive-with-python\n\n ### Features\n - Saves media files to Google Drive, organizing them into folders based on the provided path structure.\n - Supports OAuth token-based authentication or service account credentials for API access.\n - Automatically creates folders in Google Drive if they don't exist.\n - Retrieves CDN URLs for stored files, enabling easy sharing and access.\n\n ### Notes\n - Requires setup with either a Google OAuth token or a service account JSON file.\n - Files are uploaded to the specified `root_folder_id` and organized by the `media.key` structure.\n - Automatically handles Google Drive API token refreshes for long-running jobs.\n \n ## Overview\nThis module integrates Google Drive as a storage backend, enabling automatic folder creation and file uploads. It supports authentication via **service accounts** (recommended for automation) or **OAuth tokens** (for user-based authentication).\n\n## Features\n- Saves files to Google Drive, organizing them into structured folders.\n- Supports both **service account** and **OAuth token** authentication.\n- Automatically creates folders if they don't exist.\n- Generates public URLs for easy file sharing.\n\n## Setup Guide\n1. **Enable Google Drive API**\n - Create a Google Cloud project at [Google Cloud Console](https://console.cloud.google.com/)\n - Enable the **Google Drive API**.\n\n2. **Set Up a Google Drive Folder**\n - Create a folder in **Google Drive** and copy its **folder ID** from the URL.\n - Add the **folder ID** to your configuration (`orchestration.yaml`):\n ```yaml\n root_folder_id: \"FOLDER_ID\"\n ```\n\n3. **Authentication Options**\n - **Option 1: Service Account (Recommended)**\n - Create a **service account** in Google Cloud IAM.\n - Download the JSON key file and save it as:\n ```\n secrets/service_account.json\n ```\n - **Share your Drive folder** with the service account\u2019s `client_email` (found in the JSON file).\n \n - **Option 2: OAuth Token (User Authentication)**\n - Create OAuth **Desktop App credentials** in Google Cloud.\n - Save the credentials as:\n ```\n secrets/oauth_credentials.json\n ```\n - Generate an OAuth token by running:\n ```sh\n python scripts/create_update_gdrive_oauth_token.py -c secrets/oauth_credentials.json\n ```\n\n \n Notes on the OAuth token:\n Tokens are refreshed after 1 hour however keep working for 7 days (tbc)\n so as long as the job doesn't last for 7 days then this method of refreshing only once per run will work\n see this link for details on the token:\n https://davemateer.com/2022/04/28/google-drive-with-python#tokens\n \n \n", + "dependencies": { + "python": [ + "loguru", + "googleapiclient", + "google" + ] + }, + "entry_point": "gdrive_storage::GDriveStorage", + "version": "1.0", + "configs": { + "path_generator": { + "default": "url", + "help": "how to store the file in terms of directory structure: 'flat' sets to root; 'url' creates a directory based on the provided URL; 'random' creates a random directory.", + "choices": [ + "flat", + "url", + "random" + ] + }, + "filename_generator": { + "default": "static", + "help": "how to name stored files: 'random' creates a random string; 'static' uses a replicable strategy such as a hash.", + "choices": [ + "random", + "static" + ] + }, + "root_folder_id": { + "required": true, + "help": "root google drive folder ID to use as storage, found in URL: 'https://drive.google.com/drive/folders/FOLDER_ID'" + }, + "oauth_token": { + "default": null, + "help": "JSON filename with Google Drive OAuth token: check auto-archiver repository scripts folder for create_update_gdrive_oauth_token.py. NOTE: storage used will count towards owner of GDrive folder, therefore it is best to use oauth_token_filename over service_account." + }, + "service_account": { + "default": "secrets/service_account.json", + "help": "service account JSON file path, same as used for Google Sheets. NOTE: storage used will count towards the developer account." + } + } + }, + "configs": { + "path_generator": { + "default": "url", + "help": "how to store the file in terms of directory structure: 'flat' sets to root; 'url' creates a directory based on the provided URL; 'random' creates a random directory.", + "choices": [ + "flat", + "url", + "random" + ] + }, + "filename_generator": { + "default": "static", + "help": "how to name stored files: 'random' creates a random string; 'static' uses a replicable strategy such as a hash.", + "choices": [ + "random", + "static" + ] + }, + "root_folder_id": { + "required": true, + "help": "root google drive folder ID to use as storage, found in URL: 'https://drive.google.com/drive/folders/FOLDER_ID'" + }, + "oauth_token": { + "default": null, + "help": "JSON filename with Google Drive OAuth token: check auto-archiver repository scripts folder for create_update_gdrive_oauth_token.py. NOTE: storage used will count towards owner of GDrive folder, therefore it is best to use oauth_token_filename over service_account." + }, + "service_account": { + "default": "secrets/service_account.json", + "help": "service account JSON file path, same as used for Google Sheets. NOTE: storage used will count towards the developer account." + } + } + }, + "atlos_storage": { + "name": "atlos_storage", + "display_name": "Atlos Storage", + "manifest": { + "name": "Atlos Storage", + "author": "Bellingcat", + "type": [ + "storage" + ], + "requires_setup": true, + "description": "\n Stores media files in a [Atlos](https://www.atlos.org/).\n\n ### Features\n - Saves media files to Atlos, organizing them into folders based on the provided path structure.\n\n ### Notes\n - Requires setup with Atlos credentials.\n - Files are uploaded to the specified `root_folder_id` and organized by the `media.key` structure.\n ", + "dependencies": { + "python": [ + "loguru", + "boto3" + ], + "bin": [] + }, + "entry_point": "", + "version": "1.0", + "configs": { + "api_token": { + "default": null, + "help": "An Atlos API token. For more information, see https://docs.atlos.org/technical/api/", + "required": true, + "type": "str" + }, + "atlos_url": { + "default": "https://platform.atlos.org", + "help": "The URL of your Atlos instance (e.g., https://platform.atlos.org), without a trailing slash.", + "type": "str" + } + } + }, + "configs": { + "api_token": { + "default": null, + "help": "An Atlos API token. For more information, see https://docs.atlos.org/technical/api/", + "required": true, + "type": "str" + }, + "atlos_url": { + "default": "https://platform.atlos.org", + "help": "The URL of your Atlos instance (e.g., https://platform.atlos.org), without a trailing slash.", + "type": "str" + } + } + }, + "s3_storage": { + "name": "s3_storage", + "display_name": "S3 Storage", + "manifest": { + "name": "S3 Storage", + "author": "Bellingcat", + "type": [ + "storage" + ], + "requires_setup": true, + "description": "\n S3Storage: A storage module for saving media files to an S3-compatible object storage.\n\n ### Features\n - Uploads media files to an S3 bucket with customizable configurations.\n - Supports `random_no_duplicate` mode to avoid duplicate uploads by checking existing files based on SHA-256 hashes.\n - Automatically generates unique paths for files when duplicates are found.\n - Configurable endpoint and CDN URL for different S3-compatible providers.\n - Supports both private and public file storage, with public files being readable online.\n\n ### Notes\n - Requires S3 credentials (API key and secret) and a bucket name to function.\n - The `random_no_duplicate` option ensures no duplicate uploads by leveraging hash-based folder structures.\n - Uses `boto3` for interaction with the S3 API.\n - Depends on the `HashEnricher` module for hash calculation.\n ", + "dependencies": { + "python": [ + "hash_enricher", + "boto3", + "loguru" + ] + }, + "entry_point": "", + "version": "1.0", + "configs": { + "path_generator": { + "default": "flat", + "help": "how to store the file in terms of directory structure: 'flat' sets to root; 'url' creates a directory based on the provided URL; 'random' creates a random directory.", + "choices": [ + "flat", + "url", + "random" + ] + }, + "filename_generator": { + "default": "static", + "help": "how to name stored files: 'random' creates a random string; 'static' uses a replicable strategy such as a hash.", + "choices": [ + "random", + "static" + ] + }, + "bucket": { + "default": null, + "help": "S3 bucket name" + }, + "region": { + "default": null, + "help": "S3 region name" + }, + "key": { + "default": null, + "help": "S3 API key" + }, + "secret": { + "default": null, + "help": "S3 API secret" + }, + "random_no_duplicate": { + "default": false, + "type": "bool", + "help": "if set, it will override `path_generator`, `filename_generator` and `folder`. It will check if the file already exists and if so it will not upload it again. Creates a new root folder path `no-dups/`" + }, + "endpoint_url": { + "default": "https://{region}.digitaloceanspaces.com", + "help": "S3 bucket endpoint, {region} are inserted at runtime" + }, + "cdn_url": { + "default": "https://{bucket}.{region}.cdn.digitaloceanspaces.com/{key}", + "help": "S3 CDN url, {bucket}, {region} and {key} are inserted at runtime" + }, + "private": { + "default": false, + "type": "bool", + "help": "if true S3 files will not be readable online" + } + } + }, + "configs": { + "path_generator": { + "default": "flat", + "help": "how to store the file in terms of directory structure: 'flat' sets to root; 'url' creates a directory based on the provided URL; 'random' creates a random directory.", + "choices": [ + "flat", + "url", + "random" + ] + }, + "filename_generator": { + "default": "static", + "help": "how to name stored files: 'random' creates a random string; 'static' uses a replicable strategy such as a hash.", + "choices": [ + "random", + "static" + ] + }, + "bucket": { + "default": null, + "help": "S3 bucket name" + }, + "region": { + "default": null, + "help": "S3 region name" + }, + "key": { + "default": null, + "help": "S3 API key" + }, + "secret": { + "default": null, + "help": "S3 API secret" + }, + "random_no_duplicate": { + "default": false, + "type": "bool", + "help": "if set, it will override `path_generator`, `filename_generator` and `folder`. It will check if the file already exists and if so it will not upload it again. Creates a new root folder path `no-dups/`" + }, + "endpoint_url": { + "default": "https://{region}.digitaloceanspaces.com", + "help": "S3 bucket endpoint, {region} are inserted at runtime" + }, + "cdn_url": { + "default": "https://{bucket}.{region}.cdn.digitaloceanspaces.com/{key}", + "help": "S3 CDN url, {bucket}, {region} and {key} are inserted at runtime" + }, + "private": { + "default": false, + "type": "bool", + "help": "if true S3 files will not be readable online" + } + } + }, + "local_storage": { + "name": "local_storage", + "display_name": "Local Storage", + "manifest": { + "name": "Local Storage", + "author": "Bellingcat", + "type": [ + "storage" + ], + "requires_setup": false, + "description": "\n LocalStorage: A storage module for saving archived content locally on the filesystem.\n\n ### Features\n - Saves archived media files to a specified folder on the local filesystem.\n - Maintains file metadata during storage using `shutil.copy2`.\n - Supports both absolute and relative paths for stored files, configurable via `save_absolute`.\n - Automatically creates directories as needed for storing files.\n\n ### Notes\n - Default storage folder is `./archived`, but this can be changed via the `save_to` configuration.\n - The `save_absolute` option can reveal the file structure in output formats; use with caution.\n ", + "dependencies": { + "python": [ + "loguru" + ] + }, + "entry_point": "", + "version": "1.0", + "configs": { + "path_generator": { + "default": "flat", + "help": "how to store the file in terms of directory structure: 'flat' sets to root; 'url' creates a directory based on the provided URL; 'random' creates a random directory.", + "choices": [ + "flat", + "url", + "random" + ] + }, + "filename_generator": { + "default": "static", + "help": "how to name stored files: 'random' creates a random string; 'static' uses a replicable strategy such as a hash.", + "choices": [ + "random", + "static" + ] + }, + "save_to": { + "default": "./local_archive", + "help": "folder where to save archived content" + }, + "save_absolute": { + "default": false, + "help": "whether the path to the stored file is absolute or relative in the output result inc. formatters (WARN: leaks the file structure)" + } + } + }, + "configs": { + "path_generator": { + "default": "flat", + "help": "how to store the file in terms of directory structure: 'flat' sets to root; 'url' creates a directory based on the provided URL; 'random' creates a random directory.", + "choices": [ + "flat", + "url", + "random" + ] + }, + "filename_generator": { + "default": "static", + "help": "how to name stored files: 'random' creates a random string; 'static' uses a replicable strategy such as a hash.", + "choices": [ + "random", + "static" + ] + }, + "save_to": { + "default": "./local_archive", + "help": "folder where to save archived content" + }, + "save_absolute": { + "default": false, + "help": "whether the path to the stored file is absolute or relative in the output result inc. formatters (WARN: leaks the file structure)" + } + } + }, + "mute_formatter": { + "name": "mute_formatter", + "display_name": "Mute Formatter", + "manifest": { + "name": "Mute Formatter", + "author": "Bellingcat", + "type": [ + "formatter" + ], + "requires_setup": true, + "description": " Default formatter.\n ", + "dependencies": {}, + "entry_point": "", + "version": "1.0", + "configs": {} + }, + "configs": null + }, + "html_formatter": { + "name": "html_formatter", + "display_name": "HTML Formatter", + "manifest": { + "name": "HTML Formatter", + "author": "Bellingcat", + "type": [ + "formatter" + ], + "requires_setup": false, + "description": " ", + "dependencies": { + "python": [ + "hash_enricher", + "loguru", + "jinja2" + ], + "bin": [ + "" + ] + }, + "entry_point": "", + "version": "1.0", + "configs": { + "detect_thumbnails": { + "default": true, + "help": "if true will group by thumbnails generated by thumbnail enricher by id 'thumbnail_00'", + "type": "bool" + } + } + }, + "configs": { + "detect_thumbnails": { + "default": true, + "help": "if true will group by thumbnails generated by thumbnail enricher by id 'thumbnail_00'", + "type": "bool" + } + } + } + }, + "steps": { + "feeder": [ + "cli_feeder", + "gsheet_feeder", + "atlos_feeder", + "csv_feeder" + ], + "extractor": [ + "wayback_extractor_enricher", + "wacz_extractor_enricher", + "instagram_api_extractor", + "instagram_tbot_extractor", + "generic_extractor", + "twitter_api_extractor", + "instagram_extractor", + "telethon_extractor", + "vk_extractor", + "telegram_extractor" + ], + "enricher": [ + "wayback_extractor_enricher", + "wacz_extractor_enricher", + "metadata_enricher", + "timestamping_enricher", + "thumbnail_enricher", + "screenshot_enricher", + "meta_enricher", + "pdq_hash_enricher", + "whisper_enricher", + "ssl_enricher", + "hash_enricher" + ], + "database": [ + "console_db", + "atlos_db", + "api_db", + "csv_db", + "gsheet_db" + ], + "storage": [ + "local_storage", + "gdrive_storage", + "atlos_storage", + "s3_storage" + ], + "formatter": [ + "html_formatter", + "mute_formatter" + ] + }, + "configs": [ + "gsheet_feeder", + "atlos_feeder", + "csv_feeder", + "cli_feeder", + "instagram_api_extractor", + "instagram_tbot_extractor", + "twitter_api_extractor", + "instagram_extractor", + "telethon_extractor", + "vk_extractor", + "generic_extractor", + "wayback_extractor_enricher", + "wacz_extractor_enricher", + "timestamping_enricher", + "screenshot_enricher", + "whisper_enricher", + "thumbnail_enricher", + "ssl_enricher", + "hash_enricher", + "atlos_db", + "api_db", + "gsheet_db", + "csv_db", + "gdrive_storage", + "atlos_storage", + "s3_storage", + "local_storage", + "html_formatter" + ], + "module_types": [ + "feeder", + "extractor", + "enricher", + "database", + "storage", + "formatter" + ] +} \ No newline at end of file diff --git a/scripts/settings/src/theme.tsx b/scripts/settings/src/theme.tsx new file mode 100644 index 0000000..7ba9892 --- /dev/null +++ b/scripts/settings/src/theme.tsx @@ -0,0 +1,20 @@ +import { createTheme } from '@mui/material/styles'; +import { red } from '@mui/material/colors'; + +// A custom theme for this app +const theme = createTheme({ + cssVariables: true, + palette: { + primary: { + main: '#556cd6', + }, + secondary: { + main: '#19857b', + }, + error: { + main: red.A400, + }, + }, +}); + +export default theme; diff --git a/scripts/settings/src/vite-env.d.ts b/scripts/settings/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/scripts/settings/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/scripts/settings/tsconfig.json b/scripts/settings/tsconfig.json new file mode 100644 index 0000000..3d0a51a --- /dev/null +++ b/scripts/settings/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "Node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx" + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/scripts/settings/tsconfig.node.json b/scripts/settings/tsconfig.node.json new file mode 100644 index 0000000..9d31e2a --- /dev/null +++ b/scripts/settings/tsconfig.node.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "composite": true, + "module": "ESNext", + "moduleResolution": "Node", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/scripts/settings/vite.config.ts b/scripts/settings/vite.config.ts new file mode 100644 index 0000000..1787623 --- /dev/null +++ b/scripts/settings/vite.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import { viteSingleFile } from "vite-plugin-singlefile" + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react(), viteSingleFile()], +}); diff --git a/src/auto_archiver/modules/csv_db/__manifest__.py b/src/auto_archiver/modules/csv_db/__manifest__.py index 507ce14..d9733b2 100644 --- a/src/auto_archiver/modules/csv_db/__manifest__.py +++ b/src/auto_archiver/modules/csv_db/__manifest__.py @@ -6,7 +6,7 @@ }, 'entry_point': 'csv_db::CSVDb', "configs": { - "csv_file": {"default": "db.csv", "help": "CSV file name"} + "csv_file": {"default": "db.csv", "help": "CSV file name to save metadata to"}, }, "description": """ Handles exporting archival results to a CSV file. diff --git a/src/auto_archiver/modules/html_formatter/__manifest__.py b/src/auto_archiver/modules/html_formatter/__manifest__.py index ec19cf8..6e51c7a 100644 --- a/src/auto_archiver/modules/html_formatter/__manifest__.py +++ b/src/auto_archiver/modules/html_formatter/__manifest__.py @@ -7,7 +7,9 @@ "bin": [""] }, "configs": { - "detect_thumbnails": {"default": True, "help": "if true will group by thumbnails generated by thumbnail enricher by id 'thumbnail_00'"} + "detect_thumbnails": {"default": True, + "help": "if true will group by thumbnails generated by thumbnail enricher by id 'thumbnail_00'", + "type": "bool"}, }, "description": """ """, } diff --git a/src/auto_archiver/modules/ssl_enricher/__manifest__.py b/src/auto_archiver/modules/ssl_enricher/__manifest__.py index 9028f14..097cd21 100644 --- a/src/auto_archiver/modules/ssl_enricher/__manifest__.py +++ b/src/auto_archiver/modules/ssl_enricher/__manifest__.py @@ -7,7 +7,9 @@ }, 'entry_point': 'ssl_enricher::SSLEnricher', "configs": { - "skip_when_nothing_archived": {"default": True, "help": "if true, will skip enriching when no media is archived"}, + "skip_when_nothing_archived": {"default": True, + "type": 'bool', + "help": "if true, will skip enriching when no media is archived"}, }, "description": """ Retrieves SSL certificate information for a domain and stores it as a file. diff --git a/src/auto_archiver/modules/wacz_extractor_enricher/__manifest__.py b/src/auto_archiver/modules/wacz_extractor_enricher/__manifest__.py index b8d6201..a78a3a9 100644 --- a/src/auto_archiver/modules/wacz_extractor_enricher/__manifest__.py +++ b/src/auto_archiver/modules/wacz_extractor_enricher/__manifest__.py @@ -17,11 +17,17 @@ "configs": { "profile": {"default": None, "help": "browsertrix-profile (for profile generation see https://github.com/webrecorder/browsertrix-crawler#creating-and-using-browser-profiles)."}, "docker_commands": {"default": None, "help":"if a custom docker invocation is needed"}, - "timeout": {"default": 120, "help": "timeout for WACZ generation in seconds"}, - "extract_media": {"default": False, "help": "If enabled all the images/videos/audio present in the WACZ archive will be extracted into separate Media and appear in the html report. The .wacz file will be kept untouched."}, - "extract_screenshot": {"default": True, "help": "If enabled the screenshot captured by browsertrix will be extracted into separate Media and appear in the html report. The .wacz file will be kept untouched."}, + "timeout": {"default": 120, "help": "timeout for WACZ generation in seconds", "type": "int"}, + "extract_media": {"default": False, + "type": 'bool', + "help": "If enabled all the images/videos/audio present in the WACZ archive will be extracted into separate Media and appear in the html report. The .wacz file will be kept untouched." + }, + "extract_screenshot": {"default": True, + "type": 'bool', + "help": "If enabled the screenshot captured by browsertrix will be extracted into separate Media and appear in the html report. The .wacz file will be kept untouched." + }, "socks_proxy_host": {"default": None, "help": "SOCKS proxy host for browsertrix-crawler, use in combination with socks_proxy_port. eg: user:password@host"}, - "socks_proxy_port": {"default": None, "help": "SOCKS proxy port for browsertrix-crawler, use in combination with socks_proxy_host. eg 1234"}, + "socks_proxy_port": {"default": None, "type":"int", "help": "SOCKS proxy port for browsertrix-crawler, use in combination with socks_proxy_host. eg 1234"}, "proxy_server": {"default": None, "help": "SOCKS server proxy URL, in development"}, }, "description": """ From f58f110436a65a37a8cfacadf8bc4bafa3b18817 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Wed, 26 Feb 2025 17:59:13 +0000 Subject: [PATCH 093/160] Check at least 1 URL provided for new cli_feeder module rewrite --- src/auto_archiver/modules/cli_feeder/cli_feeder.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/auto_archiver/modules/cli_feeder/cli_feeder.py b/src/auto_archiver/modules/cli_feeder/cli_feeder.py index 1f1fe26..20ca6ae 100644 --- a/src/auto_archiver/modules/cli_feeder/cli_feeder.py +++ b/src/auto_archiver/modules/cli_feeder/cli_feeder.py @@ -5,6 +5,11 @@ from auto_archiver.core.metadata import Metadata class CLIFeeder(Feeder): + def setup(self) -> None: + self.urls = self.config['urls'] + if not self.urls: + raise ValueError("No URLs provided. Please provide at least one URL via the command line, or set up an alternative feeder. Use --help for more information.") + def __iter__(self) -> Metadata: urls = self.config['urls'] for url in urls: From 4280791f0776955a4eaa2a656215f7d00761ec32 Mon Sep 17 00:00:00 2001 From: erinhmclark Date: Thu, 27 Feb 2025 11:25:58 +0000 Subject: [PATCH 094/160] Fix mocking in test_wayback_enricher.py. --- tests/enrichers/test_wayback_enricher.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/enrichers/test_wayback_enricher.py b/tests/enrichers/test_wayback_enricher.py index 88f4662..5406e39 100644 --- a/tests/enrichers/test_wayback_enricher.py +++ b/tests/enrichers/test_wayback_enricher.py @@ -16,7 +16,7 @@ def mock_is_auth_wall(mocker): def mock_post_success(mocker): """Fixture to mock POST requests with a successful response.""" def _mock_post(json_data: dict = None, status_code: int = 200): - json_data = json_data or {"job_id": "job123"} + json_data = {"job_id": "job123"} if json_data is None else json_data resp = mocker.Mock(status_code=status_code) resp.json.return_value = json_data return mocker.patch("requests.post", return_value=resp) From efe9fdf915d7c5c16c0fb9c266595e55923cc2ae Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Thu, 27 Feb 2025 13:02:50 +0000 Subject: [PATCH 095/160] Tidy ups to config editor page --- scripts/settings/package-lock.json | 174 ++++++++---- scripts/settings/package.json | 6 +- scripts/settings/src/App.tsx | 267 +++++++----------- scripts/settings/src/StepCard.tsx | 200 +++++++++++++ scripts/settings/src/schema.json | 2 + .../modules/local_storage/__manifest__.py | 4 +- 6 files changed, 434 insertions(+), 219 deletions(-) create mode 100644 scripts/settings/src/StepCard.tsx diff --git a/scripts/settings/package-lock.json b/scripts/settings/package-lock.json index 6ef055b..cd40c14 100644 --- a/scripts/settings/package-lock.json +++ b/scripts/settings/package-lock.json @@ -8,12 +8,14 @@ "name": "material-ui-vite-ts", "version": "5.0.0", "dependencies": { + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/sortable": "^10.0.0", "@emotion/react": "latest", "@emotion/styled": "latest", "@mui/icons-material": "latest", "@mui/material": "latest", - "react": "latest", - "react-dom": "latest", + "react": "19.0.0", + "react-dom": "19.0.0", "react-markdown": "^10.0.0", "yaml": "^2.7.0" }, @@ -322,6 +324,59 @@ "node": ">=6.9.0" } }, + "node_modules/@dnd-kit/accessibility": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz", + "integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/core": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", + "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", + "license": "MIT", + "dependencies": { + "@dnd-kit/accessibility": "^3.1.1", + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/sortable": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-10.0.0.tgz", + "integrity": "sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==", + "license": "MIT", + "dependencies": { + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@dnd-kit/core": "^6.3.0", + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/utilities": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz", + "integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/@emotion/babel-plugin": { "version": "11.13.5", "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", @@ -942,9 +997,9 @@ } }, "node_modules/@mui/core-downloads-tracker": { - "version": "6.4.5", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.4.5.tgz", - "integrity": "sha512-zoXvHU1YuoodgMlPS+epP084Pqv9V+Vg+5IGv9n/7IIFVQ2nkTngYHYxElCq8pdTTbDcgji+nNh0lxri2abWgA==", + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.4.6.tgz", + "integrity": "sha512-rho5Q4IscbrVmK9rCrLTJmjLjfH6m/NcqKr/mchvck0EIXlyYUB9+Z0oVmkt/+Mben43LMRYBH8q/Uzxj/c4Vw==", "license": "MIT", "funding": { "type": "opencollective", @@ -952,9 +1007,9 @@ } }, "node_modules/@mui/icons-material": { - "version": "6.4.5", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.4.5.tgz", - "integrity": "sha512-4A//t8Nrc+4u4pbVhGarIFU98zpuB5AV9hTNzgXx1ySZJ1tWtx+i/1SbQ8PtGJxWeXlljhwimZJNPQ3x0CiIFw==", + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.4.6.tgz", + "integrity": "sha512-rGJBvIQQbQAlyKYljHQ8wAQS/K2/uYwvemcpygnAmCizmCI4zSF9HQPuiG8Ql4YLZ6V/uKjA3WHIYmF/8sV+pQ==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0" @@ -967,7 +1022,7 @@ "url": "https://opencollective.com/mui-org" }, "peerDependencies": { - "@mui/material": "^6.4.5", + "@mui/material": "^6.4.6", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, @@ -978,16 +1033,16 @@ } }, "node_modules/@mui/material": { - "version": "6.4.5", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.4.5.tgz", - "integrity": "sha512-5eyEgSXocIeV1JkXs8mYyJXU0aFyXZIWI5kq2g/mCnIgJe594lkOBNAKnCIaGVfQTu2T6TTEHF8/hHIqpiIRGA==", + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.4.6.tgz", + "integrity": "sha512-6UyAju+DBOdMogfYmLiT3Nu7RgliorimNBny1pN/acOjc+THNFVE7hlxLyn3RDONoZJNDi/8vO4AQQr6dLAXqA==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", - "@mui/core-downloads-tracker": "^6.4.5", - "@mui/system": "^6.4.3", + "@mui/core-downloads-tracker": "^6.4.6", + "@mui/system": "^6.4.6", "@mui/types": "^7.2.21", - "@mui/utils": "^6.4.3", + "@mui/utils": "^6.4.6", "@popperjs/core": "^2.11.8", "@types/react-transition-group": "^4.4.12", "clsx": "^2.1.1", @@ -1006,7 +1061,7 @@ "peerDependencies": { "@emotion/react": "^11.5.0", "@emotion/styled": "^11.3.0", - "@mui/material-pigment-css": "^6.4.3", + "@mui/material-pigment-css": "^6.4.6", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" @@ -1027,13 +1082,13 @@ } }, "node_modules/@mui/private-theming": { - "version": "6.4.3", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.4.3.tgz", - "integrity": "sha512-7x9HaNwDCeoERc4BoEWLieuzKzXu5ZrhRnEM6AUcRXUScQLvF1NFkTlP59+IJfTbEMgcGg1wWHApyoqcksrBpQ==", + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.4.6.tgz", + "integrity": "sha512-T5FxdPzCELuOrhpA2g4Pi6241HAxRwZudzAuL9vBvniuB5YU82HCmrARw32AuCiyTfWzbrYGGpZ4zyeqqp9RvQ==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", - "@mui/utils": "^6.4.3", + "@mui/utils": "^6.4.6", "prop-types": "^15.8.1" }, "engines": { @@ -1054,9 +1109,9 @@ } }, "node_modules/@mui/styled-engine": { - "version": "6.4.3", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.4.3.tgz", - "integrity": "sha512-OC402VfK+ra2+f12Gef8maY7Y9n7B6CZcoQ9u7mIkh/7PKwW/xH81xwX+yW+Ak1zBT3HYcVjh2X82k5cKMFGoQ==", + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.4.6.tgz", + "integrity": "sha512-vSWYc9ZLX46be5gP+FCzWVn5rvDr4cXC5JBZwSIkYk9xbC7GeV+0kCvB8Q6XLFQJy+a62bbqtmdwS4Ghi9NBlQ==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", @@ -1088,16 +1143,16 @@ } }, "node_modules/@mui/system": { - "version": "6.4.3", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.4.3.tgz", - "integrity": "sha512-Q0iDwnH3+xoxQ0pqVbt8hFdzhq1g2XzzR4Y5pVcICTNtoCLJmpJS3vI4y/OIM1FHFmpfmiEC2IRIq7YcZ8nsmg==", + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.4.6.tgz", + "integrity": "sha512-FQjWwPec7pMTtB/jw5f9eyLynKFZ6/Ej9vhm5kGdtmts1z5b7Vyn3Rz6kasfYm1j2TfrfGnSXRvvtwVWxjpz6g==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", - "@mui/private-theming": "^6.4.3", - "@mui/styled-engine": "^6.4.3", + "@mui/private-theming": "^6.4.6", + "@mui/styled-engine": "^6.4.6", "@mui/types": "^7.2.21", - "@mui/utils": "^6.4.3", + "@mui/utils": "^6.4.6", "clsx": "^2.1.1", "csstype": "^3.1.3", "prop-types": "^15.8.1" @@ -1142,9 +1197,9 @@ } }, "node_modules/@mui/utils": { - "version": "6.4.3", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.4.3.tgz", - "integrity": "sha512-jxHRHh3BqVXE9ABxDm+Tc3wlBooYz/4XPa0+4AI+iF38rV1/+btJmSUgG4shDtSWVs/I97aDn5jBCt6SF2Uq2A==", + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.4.6.tgz", + "integrity": "sha512-43nZeE1pJF2anGafNydUcYFPtHwAqiBiauRtaMvurdrZI3YrUjHkAu43RBsxef7OFtJMXGiHFvq43kb7lig0sA==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", @@ -1642,6 +1697,7 @@ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, + "license": "MIT", "dependencies": { "fill-range": "^7.1.1" }, @@ -1881,9 +1937,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.105", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.105.tgz", - "integrity": "sha512-ccp7LocdXx3yBhwiG0qTQ7XFrK48Ua2pxIxBdJO8cbddp/MvbBtPFzvnTchtyHQTsgqqczO8cdmAIbpMa0u2+g==", + "version": "1.5.107", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.107.tgz", + "integrity": "sha512-dJr1o6yCntRkXElnhsHh1bAV19bo/hKyFf7tCcWgpXbuFIF0Lakjgqv5LRfSDaNzAII8Fnxg2tqgHkgCvxdbxw==", "dev": true, "license": "ISC" }, @@ -1980,6 +2036,7 @@ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, + "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -2205,6 +2262,7 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -2896,6 +2954,7 @@ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, + "license": "MIT", "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" @@ -3026,6 +3085,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, + "license": "MIT", "engines": { "node": ">=8.6" }, @@ -3090,28 +3150,24 @@ } }, "node_modules/react": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", - "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", + "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==", "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - }, "engines": { "node": ">=0.10.0" } }, "node_modules/react-dom": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", - "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz", + "integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==", "license": "MIT", "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.2" + "scheduler": "^0.25.0" }, "peerDependencies": { - "react": "^18.3.1" + "react": "^19.0.0" } }, "node_modules/react-is": { @@ -3281,13 +3337,10 @@ } }, "node_modules/scheduler": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", - "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - } + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz", + "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==", + "license": "MIT" }, "node_modules/semver": { "version": "6.3.1", @@ -3374,6 +3427,7 @@ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, + "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -3401,6 +3455,12 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/typescript": { "version": "5.7.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", @@ -3503,9 +3563,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz", - "integrity": "sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", "dev": true, "funding": [ { diff --git a/scripts/settings/package.json b/scripts/settings/package.json index 6608693..aec7706 100644 --- a/scripts/settings/package.json +++ b/scripts/settings/package.json @@ -9,12 +9,14 @@ "preview": "vite preview" }, "dependencies": { + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/sortable": "^10.0.0", "@emotion/react": "latest", "@emotion/styled": "latest", "@mui/icons-material": "latest", "@mui/material": "latest", - "react": "latest", - "react-dom": "latest", + "react": "19.0.0", + "react-dom": "19.0.0", "react-markdown": "^10.0.0", "yaml": "^2.7.0" }, diff --git a/scripts/settings/src/App.tsx b/scripts/settings/src/App.tsx index 6e6e813..126836c 100644 --- a/scripts/settings/src/App.tsx +++ b/scripts/settings/src/App.tsx @@ -3,42 +3,34 @@ import { useEffect, useState } from 'react'; import Container from '@mui/material/Container'; import Typography from '@mui/material/Typography'; import Box from '@mui/material/Box'; -import Link from '@mui/material/Link'; -import { modules, steps, configs, module_types } from './schema.json'; + +// +import { + DndContext, + closestCenter, + KeyboardSensor, + PointerSensor, + useSensor, + useSensors, + DragOverlay +} from "@dnd-kit/core"; +import { + arrayMove, + SortableContext, + sortableKeyboardCoordinates, + rectSortingStrategy +} from "@dnd-kit/sortable"; + + +import { modules, steps, module_types } from './schema.json'; import { - Checkbox, - Select, - MenuItem, - FormControl, - FormControlLabel, - InputLabel, - FormHelperText, Stack, - TextField, - Card, - CardContent, - CardActions, Button, - Dialog, - DialogTitle, - DialogContent, } from '@mui/material'; import Grid from '@mui/material/Grid2'; -import Accordion from '@mui/material/Accordion'; -import AccordionDetails from '@mui/material/AccordionDetails'; -import AccordionSummary from '@mui/material/AccordionSummary'; -import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; -import ReactMarkdown from 'react-markdown'; -import { parseDocument, ParsedNode, Document } from 'yaml' -import { set } from 'yaml/dist/schema/yaml-1.1/set'; - -Object.defineProperty(String.prototype, 'capitalize', { - value: function() { - return this.charAt(0).toUpperCase() + this.slice(1); - }, - enumerable: false -}); +import { parseDocument, Document } from 'yaml' +import StepCard from './StepCard'; function FileDrop({ setYamlFile }) { @@ -86,117 +78,21 @@ function FileDrop({ setYamlFile }) { ); } - -function ModuleCheckbox({ module, toggleModule, enabledModules, configValues }: { module: object, toggleModule: any, enabledModules: any, configValues: any }) { - let name = module.name; - const [helpOpen, setHelpOpen] = useState(false); - const [configOpen, setConfigOpen] = useState(false); - if (name == 'metadata_enricher') { - console.log("hi"); - } - return ( - <> - - - } - label={module.display_name} /> - - - - {enabledModules.includes(name) && module.configs && name != 'cli_feeder' ? ( - - ) : null} - - - setHelpOpen(false)} - maxWidth="lg" - > - - {module.display_name} - - - - {module.manifest.description.split("\n").map((line: string) => line.trim()).join("\n")} - - - - {module.configs && name != 'cli_feeder' && } - - ) -} - - -function ConfigPanel({ module, open, setOpen, configValues }: { module: any, open: boolean, setOpen: any, configValues: any }) { - return ( - <> - setOpen(false)} - maxWidth="lg" - > - - {module.display_name} - - - - {Object.keys(module.configs).map((config_value: any) => { - let config_args = module.configs[config_value]; - let config_name = config_value.replace(/_/g," "); - return ( - - - { config_args.type === 'bool' ? - } label={config_name} /> - : - ( config_args.type === 'int' ? - - : - ( - config_args.choices !== undefined ? - <> - {config_name} - - - : - - ) - ) - } - {config_args.help} - - - ); - })} - - - - - ); -} - -function ModuleTypes({ stepType, toggleModule, enabledModules, configValues }: { stepType: string, toggleModule: any, enabledModules: any, configValues: any }) { +function ModuleTypes({ stepType, setEnabledModules, enabledModules, configValues }: { stepType: string, setEnabledModules: any, enabledModules: any, configValues: any }) { const [showError, setShowError] = useState(false); + const [activeId, setActiveId] = useState(null); + const [items, setItems] = useState(enabledModules[stepType].map(([name, enabled]: [string, boolean]) => name)); - const _toggleModule = (event: any) => { + const toggleModule = (event: any) => { // make sure that 'feeder' and 'formatter' types only have one value let name = event.target.id; + let checked = event.target.checked; if (stepType === 'feeder' || stepType === 'formatter') { - let checked = event.target.checked; // check how many modules of this type are enabled - let modules = steps[stepType].filter((m: string) => (m !== name && enabledModules.includes(m)) || (checked && m === name)); - if (modules.length > 1) { + const checkedModules = enabledModules[stepType].filter(([m, enabled]: [string, boolean]) => { + return (m !== name && enabled) || (checked && m === name) + }); + if (checkedModules.length > 1) { setShowError(true); } else { setShowError(false); @@ -204,24 +100,85 @@ function ModuleTypes({ stepType, toggleModule, enabledModules, configValues }: { } else { setShowError(false); } - toggleModule(event); + let newEnabledModules = { ...enabledModules }; + newEnabledModules[stepType] = enabledModules[stepType].map(([m, enabled]: [string, boolean]) => { + return (m === name) ? [m, checked] : [m, enabled]; + } + ); + setEnabledModules(newEnabledModules); } + + const sensors = useSensors( + useSensor(PointerSensor), + useSensor(KeyboardSensor, { + coordinateGetter: sortableKeyboardCoordinates + }) + ); + + const handleDragStart = (event) => { + setActiveId(event.active.id); + }; + + const handleDragEnd = (event) => { + setActiveId(null); + const { active, over } = event; + + if (active.id !== over.id) { + const oldIndex = items.indexOf(active.id); + const newIndex = items.indexOf(over.id); + + let newArray = arrayMove(items, oldIndex, newIndex); + // set it also on steps + let newEnabledModules = { ...enabledModules }; + newEnabledModules[stepType] = enabledModules[stepType].sort((a, b) => { + return newArray.indexOf(a[0]) - newArray.indexOf(b[0]); + }) + setEnabledModules(newEnabledModules); + setItems(newArray); + } + }; return ( <> + {stepType}s + + Select the {stepType}s you wish to enable. You can drag and drop them to reorder them. + + {showError ? Only one {stepType} can be enabled at a time. : null} + + - {steps[stepType].map((name: string) => { + + {items.map((name: string) => { let m = modules[name]; return ( - - - + ); })} + + {activeId ? ( +
+ + ) : null} +
+
+
); } @@ -229,7 +186,7 @@ function ModuleTypes({ stepType, toggleModule, enabledModules, configValues }: { export default function App() { const [yamlFile, setYamlFile] = useState(new Document()); - const [enabledModules, setEnabledModules] = useState<[]>([]); + const [enabledModules, setEnabledModules] = useState<{}>(Object.fromEntries(module_types.map(type => [type, steps[type].map((name: string) => [name, false])]))); const [configValues, setConfigValues] = useState<{}>( Object.keys(modules).reduce((acc, module) => { acc[module] = {}; @@ -241,15 +198,14 @@ export default function App() { // edit the yamlFile // generate the steps config - let stepsConfig = {} - module_types.forEach((stepType: string) => { - stepsConfig[stepType] = enabledModules.filter((m: string) => steps[stepType].includes(m)); - } - ); + let stepsConfig = enabledModules; // create a yaml file from const finalYaml = { - 'steps': stepsConfig + 'steps': Object.keys(stepsConfig).reduce((acc, stepType) => { + acc[stepType] = stepsConfig[stepType].filter(([name, enabled]: [string, boolean]) => enabled).map(([name, enabled]: [string, boolean]) => name); + return acc; + }, {}) }; Object.keys(configValues).map((module: string) => { @@ -274,17 +230,6 @@ export default function App() { } } - const toggleModule = function (event: any) { - let module = event.target.id; - let checked = event.target.checked - - if (checked) { - setEnabledModules([...enabledModules, module]); - } else { - setEnabledModules(enabledModules.filter((m: string) => m !== module)); - } - } - useEffect(() => { // load the configs, and set the default values if they exist let newConfigValues = {}; @@ -339,7 +284,9 @@ export default function App() { {Object.keys(steps).map((stepType: string) => { return ( - + + + ); })} @@ -355,8 +302,10 @@ export default function App() { 4. Save your settings + + diff --git a/scripts/settings/src/StepCard.tsx b/scripts/settings/src/StepCard.tsx new file mode 100644 index 0000000..d401d25 --- /dev/null +++ b/scripts/settings/src/StepCard.tsx @@ -0,0 +1,200 @@ +import { useState } from "react"; +import { useSortable } from "@dnd-kit/sortable"; +import ReactMarkdown from 'react-markdown'; + +import { CSS } from "@dnd-kit/utilities"; + +import { + Card, + CardContent, + CardActions, + CardHeader, + Button, + Dialog, + DialogTitle, + DialogContent, + Box, + IconButton, + Checkbox, + Select, + MenuItem, + FormControl, + FormControlLabel, + InputLabel, + FormHelperText, + TextField, + Stack, + Typography, + } from '@mui/material'; +import Grid from '@mui/material/Grid2'; +import DragIndicatorIcon from '@mui/icons-material/DragIndicator'; +import { set } from "yaml/dist/schema/yaml-1.1/set"; + + +Object.defineProperty(String.prototype, 'capitalize', { + value: function() { + return this.charAt(0).toUpperCase() + this.slice(1); + }, + enumerable: false +}); + +const StepCard = ({ + type, + module, + toggleModule, + enabledModules, + configValues +}: { + type: string, + module: object, + toggleModule: any, + enabledModules: any, + configValues: any +}) => { + const { + attributes, + listeners, + setNodeRef, + transform, + transition, + isDragging + } = useSortable({ id: module.name }); + + + const style = { + transform: CSS.Transform.toString(transform), + transition, + zIndex: isDragging ? "100" : "auto", + opacity: isDragging ? 0.3 : 1 + }; + + let name = module.name; + const [helpOpen, setHelpOpen] = useState(false); + const [configOpen, setConfigOpen] = useState(false); + const enabled = enabledModules[type].find((m: any) => m[0] === name)[1]; + + return ( + + + } + label={module.display_name} /> + } + action ={ + + + + } + /> + + + {enabled && module.configs && name != 'cli_feeder' ? ( + + ) : null} + + + + setHelpOpen(false)} + maxWidth="lg" + > + + {module.display_name} + + + + {module.manifest.description.split("\n").map((line: string) => line.trim()).join("\n")} + + + + {module.configs && name != 'cli_feeder' && } + + ) + } + + +function ConfigPanel({ module, open, setOpen, configValues }: { module: any, open: boolean, setOpen: any, configValues: any }) { + + function setConfigValue(config: any, value: any) { + configValues[module.name][config] = value; + } + return ( + <> + setOpen(false)} + maxWidth="lg" + > + + {module.display_name} + + + + {Object.keys(module.configs).map((config_value: any) => { + const config_args = module.configs[config_value]; + const config_name = config_value.replace(/_/g," "); + const config_display_name = config_name.capitalize(); + const value = configValues[module.name][config_value] || config_args.default; + return ( + + {config_display_name} + + { config_args.type === 'bool' ? + { + setConfigValue(config_value, e.target.checked); + }} + /> + : + ( + config_args.choices !== undefined ? + + : + ( config_args.type === 'json_loader' ? + { + try { + val = JSON.parse(e.target.value); + setConfigValue(config_value, val); + } catch (e) { + console.log(e); + } + } + } type='text' /> + : + { + setConfigValue(config_value, e.target.value); + }} /> + ) + ) + } + {config_args.help} + + + ); + })} + + + + + ); + } + +export default StepCard; \ No newline at end of file diff --git a/scripts/settings/src/schema.json b/scripts/settings/src/schema.json index e02067c..e4f470d 100644 --- a/scripts/settings/src/schema.json +++ b/scripts/settings/src/schema.json @@ -1914,6 +1914,7 @@ }, "save_absolute": { "default": false, + "type": "bool", "help": "whether the path to the stored file is absolute or relative in the output result inc. formatters (WARN: leaks the file structure)" } } @@ -1942,6 +1943,7 @@ }, "save_absolute": { "default": false, + "type": "bool", "help": "whether the path to the stored file is absolute or relative in the output result inc. formatters (WARN: leaks the file structure)" } } diff --git a/src/auto_archiver/modules/local_storage/__manifest__.py b/src/auto_archiver/modules/local_storage/__manifest__.py index 6d9cf53..4f6c2df 100644 --- a/src/auto_archiver/modules/local_storage/__manifest__.py +++ b/src/auto_archiver/modules/local_storage/__manifest__.py @@ -17,7 +17,9 @@ "choices": ["random", "static"], }, "save_to": {"default": "./local_archive", "help": "folder where to save archived content"}, - "save_absolute": {"default": False, "help": "whether the path to the stored file is absolute or relative in the output result inc. formatters (WARN: leaks the file structure)"}, + "save_absolute": {"default": False, + "type": "bool", + "help": "whether the path to the stored file is absolute or relative in the output result inc. formatters (WARN: leaks the file structure)"}, }, "description": """ LocalStorage: A storage module for saving archived content locally on the filesystem. From 1e92c03b1d0216e4522f4072d1815a59f8d8f825 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Thu, 27 Feb 2025 15:21:11 +0000 Subject: [PATCH 096/160] Tweaks to settings page + more declarations in manifests --- scripts/generate_settings_page.py | 2 +- scripts/settings/src/App.tsx | 52 +++++++++++++------ scripts/settings/src/StepCard.tsx | 20 ++++--- scripts/settings/src/schema.json | 30 ++++++++--- .../screenshot_enricher/__manifest__.py | 24 ++++++--- .../__manifest__.py | 1 + 6 files changed, 91 insertions(+), 38 deletions(-) diff --git a/scripts/generate_settings_page.py b/scripts/generate_settings_page.py index 632d782..cb3d452 100644 --- a/scripts/generate_settings_page.py +++ b/scripts/generate_settings_page.py @@ -31,7 +31,7 @@ output_schame = { 'configs': module.configs or None } ) for module in all_modules_ordered_by_type), - 'steps': dict((module_type, [module.name for module in modules_by_type[module_type]]) for module_type in MODULE_TYPES), + 'steps': dict((f"{module_type}s", [module.name for module in modules_by_type[module_type]]) for module_type in MODULE_TYPES), 'configs': [m.name for m in all_modules_ordered_by_type if m.configs], 'module_types': MODULE_TYPES, } diff --git a/scripts/settings/src/App.tsx b/scripts/settings/src/App.tsx index 126836c..1b12ba9 100644 --- a/scripts/settings/src/App.tsx +++ b/scripts/settings/src/App.tsx @@ -81,7 +81,12 @@ function FileDrop({ setYamlFile }) { function ModuleTypes({ stepType, setEnabledModules, enabledModules, configValues }: { stepType: string, setEnabledModules: any, enabledModules: any, configValues: any }) { const [showError, setShowError] = useState(false); const [activeId, setActiveId] = useState(null); - const [items, setItems] = useState(enabledModules[stepType].map(([name, enabled]: [string, boolean]) => name)); + const [items, setItems] = useState([]); + + useEffect(() => { + setItems(enabledModules[stepType].map(([name, enabled]: [string, boolean]) => name)); + } + , [enabledModules]); const toggleModule = (event: any) => { // make sure that 'feeder' and 'formatter' types only have one value @@ -100,11 +105,12 @@ function ModuleTypes({ stepType, setEnabledModules, enabledModules, configValues } else { setShowError(false); } - let newEnabledModules = { ...enabledModules }; - newEnabledModules[stepType] = enabledModules[stepType].map(([m, enabled]: [string, boolean]) => { - return (m === name) ? [m, checked] : [m, enabled]; - } - ); + let newEnabledModules = Object.fromEntries(Object.keys(enabledModules).map((type : string) => { + return [type, enabledModules[type].map(([m, enabled]: [string, boolean]) => { + return (m === name) ? [m, checked] : [m, enabled]; + })]; + } + )); setEnabledModules(newEnabledModules); } @@ -134,17 +140,16 @@ function ModuleTypes({ stepType, setEnabledModules, enabledModules, configValues return newArray.indexOf(a[0]) - newArray.indexOf(b[0]); }) setEnabledModules(newEnabledModules); - setItems(newArray); } }; return ( <> - {stepType}s + {stepType} - Select the {stepType}s you wish to enable. You can drag and drop them to reorder them. + Select the {stepType} you wish to enable. You can drag and drop them to reorder them. {showError ? Only one {stepType} can be enabled at a time. : null} @@ -186,7 +191,7 @@ function ModuleTypes({ stepType, setEnabledModules, enabledModules, configValues export default function App() { const [yamlFile, setYamlFile] = useState(new Document()); - const [enabledModules, setEnabledModules] = useState<{}>(Object.fromEntries(module_types.map(type => [type, steps[type].map((name: string) => [name, false])]))); + const [enabledModules, setEnabledModules] = useState<{}>(Object.fromEntries(Object.keys(steps).map(type => [type, steps[type].map((name: string) => [name, false])]))); const [configValues, setConfigValues] = useState<{}>( Object.keys(modules).reduce((acc, module) => { acc[module] = {}; @@ -202,7 +207,7 @@ export default function App() { // create a yaml file from const finalYaml = { - 'steps': Object.keys(stepsConfig).reduce((acc, stepType) => { + 'steps': Object.keys(steps).reduce((acc, stepType) => { acc[stepType] = stepsConfig[stepType].filter(([name, enabled]: [string, boolean]) => enabled).map(([name, enabled]: [string, boolean]) => name); return acc; }, {}) @@ -257,10 +262,27 @@ export default function App() { let settings = yamlFile.toJS(); // make a deep copy of settings - let newEnabledModules = Object.keys(settings['steps']).map((step: string) => { - return settings['steps'][step]; - }).flat(); - newEnabledModules = newEnabledModules.filter((m: string, i: number) => newEnabledModules.indexOf(m) === i); + let stepSettings = settings['steps']; + let newEnabledModules = Object.fromEntries(Object.keys(steps).map((type: string) => { + return [type, steps[type].map((name: string) => { + return [name, stepSettings[type].indexOf(name) !== -1]; + }).sort((a, b) => { + let aIndex = stepSettings[type].indexOf(a[0]); + let bIndex = stepSettings[type].indexOf(b[0]); + if (aIndex === -1 && bIndex === -1) { + return a - b; + } + if (bIndex === -1) { + return -1; + } + if (aIndex === -1) { + return 1; + } + return aIndex - bIndex; + })]; + }).sort((a, b) => { + return module_types.indexOf(a[0]) - module_types.indexOf(b[0]); + })); setEnabledModules(newEnabledModules); }, [yamlFile]); diff --git a/scripts/settings/src/StepCard.tsx b/scripts/settings/src/StepCard.tsx index d401d25..870926a 100644 --- a/scripts/settings/src/StepCard.tsx +++ b/scripts/settings/src/StepCard.tsx @@ -20,7 +20,7 @@ import { MenuItem, FormControl, FormControlLabel, - InputLabel, + Textarea, FormHelperText, TextField, Stack, @@ -141,13 +141,15 @@ function ConfigPanel({ module, open, setOpen, configValues }: { module: any, ope const value = configValues[module.name][config_value] || config_args.default; return ( - {config_display_name} + {config_display_name} - { config_args.type === 'bool' ? + { config_args.type === 'bool' ? + { setConfigValue(config_value, e.target.checked); }} + />} label={config_args.help} /> : ( @@ -167,16 +169,16 @@ function ConfigPanel({ module, open, setOpen, configValues }: { module: any, ope : ( config_args.type === 'json_loader' ? - { try { - val = JSON.parse(e.target.value); + let val = JSON.parse(e.target.value); setConfigValue(config_value, val); } catch (e) { console.log(e); } } - } type='text' /> + } /> : { @@ -185,8 +187,10 @@ function ConfigPanel({ module, open, setOpen, configValues }: { module: any, ope ) ) } - {config_args.help} - + {config_args.type !== 'bool' && ( + {config_args.help} + )} + ); })} diff --git a/scripts/settings/src/schema.json b/scripts/settings/src/schema.json index e4f470d..87ec55b 100644 --- a/scripts/settings/src/schema.json +++ b/scripts/settings/src/schema.json @@ -799,6 +799,7 @@ "configs": { "timeout": { "default": 15, + "type": "int", "help": "seconds to wait for successful archive confirmation from wayback, if more than this passes the result contains the job_id so the status can later be checked manually." }, "if_not_archived_within": { @@ -826,6 +827,7 @@ "configs": { "timeout": { "default": 15, + "type": "int", "help": "seconds to wait for successful archive confirmation from wayback, if more than this passes the result contains the job_id so the status can later be checked manually." }, "if_not_archived_within": { @@ -1046,18 +1048,22 @@ "configs": { "width": { "default": 1280, + "type": "int", "help": "width of the screenshots" }, "height": { "default": 720, + "type": "int", "help": "height of the screenshots" }, "timeout": { "default": 60, + "type": "int", "help": "timeout for taking the screenshot" }, "sleep_before_screenshot": { "default": 4, + "type": "int", "help": "seconds to wait for the pages to load before taking screenshot" }, "http_proxy": { @@ -1066,29 +1072,35 @@ }, "save_to_pdf": { "default": false, + "type": "bool", "help": "save the page as pdf along with the screenshot. PDF saving options can be adjusted with the 'print_options' parameter" }, "print_options": { "default": {}, - "help": "options to pass to the pdf printer" + "help": "options to pass to the pdf printer, in JSON format. See https://www.selenium.dev/documentation/webdriver/interactions/print_page/ for more information", + "type": "json_loader" } } }, "configs": { "width": { "default": 1280, + "type": "int", "help": "width of the screenshots" }, "height": { "default": 720, + "type": "int", "help": "height of the screenshots" }, "timeout": { "default": 60, + "type": "int", "help": "timeout for taking the screenshot" }, "sleep_before_screenshot": { "default": 4, + "type": "int", "help": "seconds to wait for the pages to load before taking screenshot" }, "http_proxy": { @@ -1097,11 +1109,13 @@ }, "save_to_pdf": { "default": false, + "type": "bool", "help": "save the page as pdf along with the screenshot. PDF saving options can be adjusted with the 'print_options' parameter" }, "print_options": { "default": {}, - "help": "options to pass to the pdf printer" + "help": "options to pass to the pdf printer, in JSON format. See https://www.selenium.dev/documentation/webdriver/interactions/print_page/ for more information", + "type": "json_loader" } } }, @@ -2007,13 +2021,13 @@ } }, "steps": { - "feeder": [ + "feeders": [ "cli_feeder", "gsheet_feeder", "atlos_feeder", "csv_feeder" ], - "extractor": [ + "extractors": [ "wayback_extractor_enricher", "wacz_extractor_enricher", "instagram_api_extractor", @@ -2025,7 +2039,7 @@ "vk_extractor", "telegram_extractor" ], - "enricher": [ + "enrichers": [ "wayback_extractor_enricher", "wacz_extractor_enricher", "metadata_enricher", @@ -2038,20 +2052,20 @@ "ssl_enricher", "hash_enricher" ], - "database": [ + "databases": [ "console_db", "atlos_db", "api_db", "csv_db", "gsheet_db" ], - "storage": [ + "storages": [ "local_storage", "gdrive_storage", "atlos_storage", "s3_storage" ], - "formatter": [ + "formatters": [ "html_formatter", "mute_formatter" ] diff --git a/src/auto_archiver/modules/screenshot_enricher/__manifest__.py b/src/auto_archiver/modules/screenshot_enricher/__manifest__.py index 9829844..c6a196c 100644 --- a/src/auto_archiver/modules/screenshot_enricher/__manifest__.py +++ b/src/auto_archiver/modules/screenshot_enricher/__manifest__.py @@ -6,13 +6,25 @@ "python": ["loguru", "selenium"], }, "configs": { - "width": {"default": 1280, "help": "width of the screenshots"}, - "height": {"default": 720, "help": "height of the screenshots"}, - "timeout": {"default": 60, "help": "timeout for taking the screenshot"}, - "sleep_before_screenshot": {"default": 4, "help": "seconds to wait for the pages to load before taking screenshot"}, + "width": {"default": 1280, + "type": "int", + "help": "width of the screenshots"}, + "height": {"default": 720, + "type": "int", + "help": "height of the screenshots"}, + "timeout": {"default": 60, + "type": "int", + "help": "timeout for taking the screenshot"}, + "sleep_before_screenshot": {"default": 4, + "type": "int", + "help": "seconds to wait for the pages to load before taking screenshot"}, "http_proxy": {"default": "", "help": "http proxy to use for the webdriver, eg http://proxy-user:password@proxy-ip:port"}, - "save_to_pdf": {"default": False, "help": "save the page as pdf along with the screenshot. PDF saving options can be adjusted with the 'print_options' parameter"}, - "print_options": {"default": {}, "help": "options to pass to the pdf printer"} + "save_to_pdf": {"default": False, + "type": "bool", + "help": "save the page as pdf along with the screenshot. PDF saving options can be adjusted with the 'print_options' parameter"}, + "print_options": {"default": {}, + "help": "options to pass to the pdf printer, in JSON format. See https://www.selenium.dev/documentation/webdriver/interactions/print_page/ for more information", + "type": "json_loader"}, }, "description": """ Captures screenshots and optionally saves web pages as PDFs using a WebDriver. diff --git a/src/auto_archiver/modules/wayback_extractor_enricher/__manifest__.py b/src/auto_archiver/modules/wayback_extractor_enricher/__manifest__.py index 38a5610..62a7e8a 100644 --- a/src/auto_archiver/modules/wayback_extractor_enricher/__manifest__.py +++ b/src/auto_archiver/modules/wayback_extractor_enricher/__manifest__.py @@ -9,6 +9,7 @@ "configs": { "timeout": { "default": 15, + "type": "int", "help": "seconds to wait for successful archive confirmation from wayback, if more than this passes the result contains the job_id so the status can later be checked manually.", }, "if_not_archived_within": { From 2ec44f41703588af42755fddb8f254d2ad22cc83 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Thu, 27 Feb 2025 15:42:37 +0000 Subject: [PATCH 097/160] Documentation on building the settings page --- .../development/developer_guidelines.md | 1 + docs/source/development/release.md | 5 +++ docs/source/development/settings_page.md | 20 +++++++++++ ...gs_page.py => generate_settings_schema.py} | 4 ++- scripts/settings/package.json | 2 +- scripts/settings/src/App.tsx | 33 ++++++++++++------- 6 files changed, 52 insertions(+), 13 deletions(-) create mode 100644 docs/source/development/settings_page.md rename scripts/{generate_settings_page.py => generate_settings_schema.py} (91%) diff --git a/docs/source/development/developer_guidelines.md b/docs/source/development/developer_guidelines.md index e72193a..0014d8f 100644 --- a/docs/source/development/developer_guidelines.md +++ b/docs/source/development/developer_guidelines.md @@ -31,4 +31,5 @@ docker_development testing docs release +settings_page ``` \ No newline at end of file diff --git a/docs/source/development/release.md b/docs/source/development/release.md index 403dcb9..694af78 100644 --- a/docs/source/development/release.md +++ b/docs/source/development/release.md @@ -13,3 +13,8 @@ manual release to docker hub * `docker image tag auto-archiver bellingcat/auto-archiver:latest` * `docker push bellingcat/auto-archiver` + + +### Building the Settings Page + +The Settings page is built as part of the python-publish workflow and packaged within the app. \ No newline at end of file diff --git a/docs/source/development/settings_page.md b/docs/source/development/settings_page.md new file mode 100644 index 0000000..29c722a --- /dev/null +++ b/docs/source/development/settings_page.md @@ -0,0 +1,20 @@ +# Settings Page + +The settings page (viewable here TODO: add link), is an easy-to-use UI for users to edit their auto-archiver settings. + +The single-file app is built using React and vite. To get started developing the package, follow these steps: + +1. Make sure you have Node v22 installed. + +```{note} Tip: if you don't have node installed: + +Use `nvm` to manage your node installations. Use: +`curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash` to install `nvm` and then `nvm i 22` to install Node v22 +``` + +2. Generate the `schema.json` file for the currently installed modules using `python scripts/generate_settings_schema.py` +3. Go to the settings folder `cd scripts/settings/` and build your environment with `npm i` +4. Run a development version of the page with `npm run dev` +5. Build a release version of the page with `npm run build` + +A release version creates a single-file app called `dist/index.html` \ No newline at end of file diff --git a/scripts/generate_settings_page.py b/scripts/generate_settings_schema.py similarity index 91% rename from scripts/generate_settings_page.py rename to scripts/generate_settings_schema.py index cb3d452..e04e404 100644 --- a/scripts/generate_settings_page.py +++ b/scripts/generate_settings_schema.py @@ -1,4 +1,5 @@ import json +import os from auto_archiver.core.module import ModuleFactory from auto_archiver.core.consts import MODULE_TYPES @@ -36,6 +37,7 @@ output_schame = { 'module_types': MODULE_TYPES, } -output_file = 'schema.json' +current_file_dir = os.path.dirname(os.path.abspath(__file__)) +output_file = os.path.join(current_file_dir, 'settings/src/schema.json') with open(output_file, 'w') as file: json.dump(output_schame, file, indent=4, cls=SchemaEncoder) \ No newline at end of file diff --git a/scripts/settings/package.json b/scripts/settings/package.json index aec7706..fc7bb7b 100644 --- a/scripts/settings/package.json +++ b/scripts/settings/package.json @@ -5,7 +5,7 @@ "type": "module", "scripts": { "dev": "vite", - "build": "tsc && vite build", + "build": "vite build", "preview": "vite preview" }, "dependencies": { diff --git a/scripts/settings/src/App.tsx b/scripts/settings/src/App.tsx index 1b12ba9..ab1caeb 100644 --- a/scripts/settings/src/App.tsx +++ b/scripts/settings/src/App.tsx @@ -21,6 +21,8 @@ import { rectSortingStrategy } from "@dnd-kit/sortable"; +import type { DragStartEvent, DragEndEvent, UniqueIdentifier } from "@dnd-kit/core"; + import { modules, steps, module_types } from './schema.json'; import { @@ -32,7 +34,16 @@ import Grid from '@mui/material/Grid2'; import { parseDocument, Document } from 'yaml' import StepCard from './StepCard'; -function FileDrop({ setYamlFile }) { +// create a Typescript interface for module +interface Module { + name: string; + description: string; + configs: object; + manifest: object; +} + + +function FileDrop({ setYamlFile }: { setYamlFile: React.Dispatch> }) { const [showError, setShowError] = useState(false); const [label, setLabel] = useState("Drag and drop your orchestration.yaml file here, or click to select a file."); @@ -46,9 +57,9 @@ function FileDrop({ setYamlFile }) { } let reader = new FileReader(); reader.onload = function(e) { - let contents = e.target.result; + let contents = e.target ? e.target.result : ''; try { - let document = parseDocument(contents); + let document = parseDocument(contents as string); if (document.errors.length > 0) { // not a valid yaml file setShowError(true); @@ -79,8 +90,8 @@ function FileDrop({ setYamlFile }) { } function ModuleTypes({ stepType, setEnabledModules, enabledModules, configValues }: { stepType: string, setEnabledModules: any, enabledModules: any, configValues: any }) { - const [showError, setShowError] = useState(false); - const [activeId, setActiveId] = useState(null); + const [showError, setShowError] = useState(false); + const [activeId, setActiveId] = useState(); const [items, setItems] = useState([]); useEffect(() => { @@ -121,17 +132,17 @@ function ModuleTypes({ stepType, setEnabledModules, enabledModules, configValues }) ); - const handleDragStart = (event) => { + const handleDragStart = (event: DragStartEvent) => { setActiveId(event.active.id); }; - const handleDragEnd = (event) => { - setActiveId(null); + const handleDragEnd = (event: DragEndEvent) => { + setActiveId(undefined); const { active, over } = event; - if (active.id !== over.id) { - const oldIndex = items.indexOf(active.id); - const newIndex = items.indexOf(over.id); + if (active.id !== over?.id) { + const oldIndex = items.indexOf(active.id as string); + const newIndex = items.indexOf(over?.id as string); let newArray = arrayMove(items, oldIndex, newIndex); // set it also on steps From 15da907e811ba3f26a641bfd24abf0ba6aa8565c Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Thu, 27 Feb 2025 15:58:30 +0000 Subject: [PATCH 098/160] Add a bit of typescripting --- scripts/settings/src/App.tsx | 21 ++++++++++----------- scripts/settings/src/StepCard.tsx | 7 ++----- scripts/settings/src/types.d.ts | 18 ++++++++++++++++++ 3 files changed, 30 insertions(+), 16 deletions(-) create mode 100644 scripts/settings/src/types.d.ts diff --git a/scripts/settings/src/App.tsx b/scripts/settings/src/App.tsx index ab1caeb..1c3f0e3 100644 --- a/scripts/settings/src/App.tsx +++ b/scripts/settings/src/App.tsx @@ -24,6 +24,8 @@ import { import type { DragStartEvent, DragEndEvent, UniqueIdentifier } from "@dnd-kit/core"; +import { Module } from './types'; + import { modules, steps, module_types } from './schema.json'; import { Stack, @@ -34,14 +36,6 @@ import Grid from '@mui/material/Grid2'; import { parseDocument, Document } from 'yaml' import StepCard from './StepCard'; -// create a Typescript interface for module -interface Module { - name: string; - description: string; - configs: object; - manifest: object; -} - function FileDrop({ setYamlFile }: { setYamlFile: React.Dispatch> }) { @@ -174,7 +168,7 @@ function ModuleTypes({ stepType, setEnabledModules, enabledModules, configValues {items.map((name: string) => { - let m = modules[name]; + let m: Module = modules[name]; return ( ); @@ -203,7 +197,12 @@ function ModuleTypes({ stepType, setEnabledModules, enabledModules, configValues export default function App() { const [yamlFile, setYamlFile] = useState(new Document()); const [enabledModules, setEnabledModules] = useState<{}>(Object.fromEntries(Object.keys(steps).map(type => [type, steps[type].map((name: string) => [name, false])]))); - const [configValues, setConfigValues] = useState<{}>( + const [configValues, setConfigValues] = useState<{ + [key: string]: { + [key: string + ]: any + } + }>( Object.keys(modules).reduce((acc, module) => { acc[module] = {}; return acc; @@ -218,7 +217,7 @@ export default function App() { // create a yaml file from const finalYaml = { - 'steps': Object.keys(steps).reduce((acc, stepType) => { + 'steps': Object.keys(steps).reduce((acc, stepType: string) => { acc[stepType] = stepsConfig[stepType].filter(([name, enabled]: [string, boolean]) => enabled).map(([name, enabled]: [string, boolean]) => name); return acc; }, {}) diff --git a/scripts/settings/src/StepCard.tsx b/scripts/settings/src/StepCard.tsx index 870926a..f326a50 100644 --- a/scripts/settings/src/StepCard.tsx +++ b/scripts/settings/src/StepCard.tsx @@ -6,7 +6,6 @@ import { CSS } from "@dnd-kit/utilities"; import { Card, - CardContent, CardActions, CardHeader, Button, @@ -20,7 +19,6 @@ import { MenuItem, FormControl, FormControlLabel, - Textarea, FormHelperText, TextField, Stack, @@ -28,8 +26,7 @@ import { } from '@mui/material'; import Grid from '@mui/material/Grid2'; import DragIndicatorIcon from '@mui/icons-material/DragIndicator'; -import { set } from "yaml/dist/schema/yaml-1.1/set"; - +import { Module } from "./types"; Object.defineProperty(String.prototype, 'capitalize', { value: function() { @@ -46,7 +43,7 @@ const StepCard = ({ configValues }: { type: string, - module: object, + module: Module, toggleModule: any, enabledModules: any, configValues: any diff --git a/scripts/settings/src/types.d.ts b/scripts/settings/src/types.d.ts new file mode 100644 index 0000000..64cba2f --- /dev/null +++ b/scripts/settings/src/types.d.ts @@ -0,0 +1,18 @@ +export interface Config { + name: string; + description: string; + type: string?; + default: any; +} + +interface Manifest { + description: string; +} + +export interface Module { + name: string; + description: string; + configs: { [key: string]: Config }; + manifest: Manifest; + display_name: string; +} \ No newline at end of file From 1141c00e9ab8fc0d4b0c09b2e0e208531b8e6220 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Thu, 27 Feb 2025 21:23:38 +0000 Subject: [PATCH 099/160] Remove unused files, set up for RTD --- .readthedocs.yaml | 4 + docs/source/installation/config_editor.md | 5 + docs/source/installation/settings.html | 372 ++++++++++++++++++++++ docs/source/installation/setup.md | 1 + scripts/generate_settings_schema.py | 4 +- scripts/settings/index.html | 1 - scripts/settings/public/vite.svg | 1 - scripts/settings/src/App.tsx | 244 +++++++------- scripts/settings/src/ProTip.tsx | 23 -- scripts/settings/src/StepCard.tsx | 277 ++++++++-------- scripts/settings/src/types.d.ts | 5 +- scripts/settings/src/vite-env.d.ts | 1 - 12 files changed, 655 insertions(+), 283 deletions(-) create mode 100644 docs/source/installation/config_editor.md create mode 100644 docs/source/installation/settings.html delete mode 100644 scripts/settings/public/vite.svg delete mode 100644 scripts/settings/src/ProTip.tsx delete mode 100644 scripts/settings/src/vite-env.d.ts diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 434f805..39504d8 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -9,6 +9,7 @@ build: os: ubuntu-22.04 tools: python: "3.10" + node: "22" jobs: post_install: - pip install poetry @@ -17,6 +18,9 @@ build: # See https://github.com/readthedocs/readthedocs.org/pull/11152/ - VIRTUAL_ENV=$READTHEDOCS_VIRTUALENV_PATH poetry install --with docs + # install node dependencies and build the settings + - cd scripts/settings && npm install && npm run build && cp dist/index.html ../../docs/source/installation/settings_base.html && cd ../.. + sphinx: configuration: docs/source/conf.py diff --git a/docs/source/installation/config_editor.md b/docs/source/installation/config_editor.md new file mode 100644 index 0000000..a23ebce --- /dev/null +++ b/docs/source/installation/config_editor.md @@ -0,0 +1,5 @@ +# Configuration Editor + +```{raw} html +:file: settings.html +``` \ No newline at end of file diff --git a/docs/source/installation/settings.html b/docs/source/installation/settings.html new file mode 100644 index 0000000..973a1e0 --- /dev/null +++ b/docs/source/installation/settings.html @@ -0,0 +1,372 @@ + + + + + + + + + + Auto Archiver Settings + + + +
+ + diff --git a/docs/source/installation/setup.md b/docs/source/installation/setup.md index 8d1a6f5..e5c96a6 100644 --- a/docs/source/installation/setup.md +++ b/docs/source/installation/setup.md @@ -6,6 +6,7 @@ installation.md configurations.md +config_editor.md authentication.md requirements.md config_cheatsheet.md diff --git a/scripts/generate_settings_schema.py b/scripts/generate_settings_schema.py index e04e404..92853cd 100644 --- a/scripts/generate_settings_schema.py +++ b/scripts/generate_settings_schema.py @@ -23,7 +23,7 @@ for module in available_modules: all_modules_ordered_by_type = sorted(available_modules, key=lambda x: (MODULE_TYPES.index(x.type[0]), not x.requires_setup)) -output_schame = { +output_schema = { 'modules': dict((module.name, { 'name': module.name, @@ -40,4 +40,4 @@ output_schame = { current_file_dir = os.path.dirname(os.path.abspath(__file__)) output_file = os.path.join(current_file_dir, 'settings/src/schema.json') with open(output_file, 'w') as file: - json.dump(output_schame, file, indent=4, cls=SchemaEncoder) \ No newline at end of file + json.dump(output_schema, file, indent=4, cls=SchemaEncoder) \ No newline at end of file diff --git a/scripts/settings/index.html b/scripts/settings/index.html index d2e8d75..b0cfa48 100644 --- a/scripts/settings/index.html +++ b/scripts/settings/index.html @@ -2,7 +2,6 @@ - diff --git a/scripts/settings/public/vite.svg b/scripts/settings/public/vite.svg deleted file mode 100644 index e7b8dfb..0000000 --- a/scripts/settings/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/scripts/settings/src/App.tsx b/scripts/settings/src/App.tsx index 1c3f0e3..26d306b 100644 --- a/scripts/settings/src/App.tsx +++ b/scripts/settings/src/App.tsx @@ -27,7 +27,7 @@ import type { DragStartEvent, DragEndEvent, UniqueIdentifier } from "@dnd-kit/co import { Module } from './types'; import { modules, steps, module_types } from './schema.json'; -import { +import { Stack, Button, } from '@mui/material'; @@ -50,7 +50,7 @@ function FileDrop({ setYamlFile }: { setYamlFile: React.Dispatch -
+
- - - {label} - -
+ + + {label} + +
); } @@ -91,13 +91,13 @@ function ModuleTypes({ stepType, setEnabledModules, enabledModules, configValues useEffect(() => { setItems(enabledModules[stepType].map(([name, enabled]: [string, boolean]) => name)); } - , [enabledModules]); + , [enabledModules]); const toggleModule = (event: any) => { // make sure that 'feeder' and 'formatter' types only have one value let name = event.target.id; let checked = event.target.checked; - if (stepType === 'feeder' || stepType === 'formatter') { + if (stepType === 'feeders' || stepType === 'formatters') { // check how many modules of this type are enabled const checkedModules = enabledModules[stepType].filter(([m, enabled]: [string, boolean]) => { return (m !== name && enabled) || (checked && m === name) @@ -110,11 +110,11 @@ function ModuleTypes({ stepType, setEnabledModules, enabledModules, configValues } else { setShowError(false); } - let newEnabledModules = Object.fromEntries(Object.keys(enabledModules).map((type : string) => { + let newEnabledModules = Object.fromEntries(Object.keys(enabledModules).map((type: string) => { return [type, enabledModules[type].map(([m, enabled]: [string, boolean]) => { return (m === name) ? [m, checked] : [m, enabled]; })]; - } + } )); setEnabledModules(newEnabledModules); } @@ -135,61 +135,62 @@ function ModuleTypes({ stepType, setEnabledModules, enabledModules, configValues const { active, over } = event; if (active.id !== over?.id) { - const oldIndex = items.indexOf(active.id as string); - const newIndex = items.indexOf(over?.id as string); + const oldIndex = items.indexOf(active.id as string); + const newIndex = items.indexOf(over?.id as string); - let newArray = arrayMove(items, oldIndex, newIndex); - // set it also on steps - let newEnabledModules = { ...enabledModules }; - newEnabledModules[stepType] = enabledModules[stepType].sort((a, b) => { - return newArray.indexOf(a[0]) - newArray.indexOf(b[0]); - }) - setEnabledModules(newEnabledModules); + let newArray = arrayMove(items, oldIndex, newIndex); + // set it also on steps + let newEnabledModules = { ...enabledModules }; + newEnabledModules[stepType] = enabledModules[stepType].sort((a, b) => { + return newArray.indexOf(a[0]) - newArray.indexOf(b[0]); + }) + setEnabledModules(newEnabledModules); } }; return ( <> - - - {stepType} - - - Select the {stepType} you wish to enable. You can drag and drop them to reorder them. - + + + {stepType} + + + Select the {stepType} you wish to enable. You can drag and move to reorder. + Learn more about {stepType} here. + - {showError ? Only one {stepType} can be enabled at a time. : null} + {showError ? Only one {stepType.slice(0,-1)} can be enabled at a time. : null} - - - {items.map((name: string) => { - let m: Module = modules[name]; - return ( - - ); - })} - - {activeId ? ( -
+ sensors={sensors} + collisionDetection={closestCenter} + onDragEnd={handleDragEnd} + onDragStart={handleDragStart} + > + + + {items.map((name: string) => { + let m: Module = modules[name]; + return ( + + ); + })} + + {activeId ? ( +
- ) : null} -
-
-
+ ) : null} +
+
+
- + ); } @@ -209,41 +210,41 @@ export default function App() { }, {}) ); - const saveSettings = function(copy: boolean = false) { + const saveSettings = function (copy: boolean = false) { // edit the yamlFile // generate the steps config let stepsConfig = enabledModules; - // create a yaml file from - const finalYaml = { - 'steps': Object.keys(steps).reduce((acc, stepType: string) => { - acc[stepType] = stepsConfig[stepType].filter(([name, enabled]: [string, boolean]) => enabled).map(([name, enabled]: [string, boolean]) => name); - return acc; - }, {}) - }; + // create a yaml file from + const finalYaml = { + 'steps': Object.keys(steps).reduce((acc, stepType: string) => { + acc[stepType] = stepsConfig[stepType].filter(([name, enabled]: [string, boolean]) => enabled).map(([name, enabled]: [string, boolean]) => name); + return acc; + }, {}) + }; - Object.keys(configValues).map((module: string) => { - let module_values = configValues[module]; - if (module_values) { - finalYaml[module] = module_values; - } - }); - let newFile = new Document(finalYaml); - if (copy) { - navigator.clipboard.writeText(String(newFile)).then(() => { - alert("Settings copied to clipboard."); - }); - } else { - // offer the file for download - const blob = new Blob([String(newFile)], { type: 'application/x-yaml' }); - const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = 'orchestration.yaml'; - a.click(); + Object.keys(configValues).map((module: string) => { + let module_values = configValues[module]; + if (module_values) { + finalYaml[module] = module_values; } + }); + let newFile = new Document(finalYaml); + if (copy) { + navigator.clipboard.writeText(String(newFile)).then(() => { + alert("Settings copied to clipboard."); + }); + } else { + // offer the file for download + const blob = new Blob([String(newFile)], { type: 'application/x-yaml' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = 'orchestration.yaml'; + a.click(); } + } useEffect(() => { // load the configs, and set the default values if they exist @@ -262,7 +263,7 @@ export default function App() { } }); }) - setConfigValues(newConfigValues); + setConfigValues(newConfigValues); }, []); useEffect(() => { @@ -274,25 +275,25 @@ export default function App() { // make a deep copy of settings let stepSettings = settings['steps']; let newEnabledModules = Object.fromEntries(Object.keys(steps).map((type: string) => { - return [type, steps[type].map((name: string) => { - return [name, stepSettings[type].indexOf(name) !== -1]; - }).sort((a, b) => { - let aIndex = stepSettings[type].indexOf(a[0]); - let bIndex = stepSettings[type].indexOf(b[0]); - if (aIndex === -1 && bIndex === -1) { - return a - b; - } - if (bIndex === -1) { - return -1; - } - if (aIndex === -1) { - return 1; - } - return aIndex - bIndex; - })]; + return [type, steps[type].map((name: string) => { + return [name, stepSettings[type].indexOf(name) !== -1]; }).sort((a, b) => { - return module_types.indexOf(a[0]) - module_types.indexOf(b[0]); - })); + let aIndex = stepSettings[type].indexOf(a[0]); + let bIndex = stepSettings[type].indexOf(b[0]); + if (aIndex === -1 && bIndex === -1) { + return a - b; + } + if (bIndex === -1) { + return -1; + } + if (aIndex === -1) { + return 1; + } + return aIndex - bIndex; + })]; + }).sort((a, b) => { + return module_types.indexOf(a[0]) - module_types.indexOf(b[0]); + })); setEnabledModules(newEnabledModules); }, [yamlFile]); @@ -301,44 +302,41 @@ export default function App() { return ( - - Auto Archiver Settings - - - 1. Select your
orchestration.yaml
settings file. -
- + + 1. Select your orchestration.yaml settings file. + +
- - 2. Choose the Modules you wish to enable/disable - + + 2. Choose the Modules you wish to enable/disable + {Object.keys(steps).map((stepType: string) => { return ( - + ); })} - - 3. Configure your Enabled Modules - - - Next to each module you've enabled, you can click 'Configure' to set the module's settings. - + + 3. Configure your Enabled Modules + + + Next to each module you've enabled, you can click 'Configure' to set the module's settings. + 4. Save your settings - - + + - +
); diff --git a/scripts/settings/src/ProTip.tsx b/scripts/settings/src/ProTip.tsx deleted file mode 100644 index 217b5bf..0000000 --- a/scripts/settings/src/ProTip.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import * as React from 'react'; -import Link from '@mui/material/Link'; -import SvgIcon, { SvgIconProps } from '@mui/material/SvgIcon'; -import Typography from '@mui/material/Typography'; - -function LightBulbIcon(props: SvgIconProps) { - return ( - - - - ); -} - -export default function ProTip() { - return ( - - - {'Pro tip: See more '} - templates - {' in the Material UI documentation.'} - - ); -} diff --git a/scripts/settings/src/StepCard.tsx b/scripts/settings/src/StepCard.tsx index f326a50..fe69359 100644 --- a/scripts/settings/src/StepCard.tsx +++ b/scripts/settings/src/StepCard.tsx @@ -4,7 +4,7 @@ import ReactMarkdown from 'react-markdown'; import { CSS } from "@dnd-kit/utilities"; -import { +import { Card, CardActions, CardHeader, @@ -23,17 +23,22 @@ import { TextField, Stack, Typography, - } from '@mui/material'; +} from '@mui/material'; import Grid from '@mui/material/Grid2'; import DragIndicatorIcon from '@mui/icons-material/DragIndicator'; -import { Module } from "./types"; +import HelpIconOutlined from '@mui/icons-material/HelpOutline'; +import { Module, Config } from "./types"; -Object.defineProperty(String.prototype, 'capitalize', { - value: function() { + +// adds 'capitalize' method to String prototype +declare global { + interface String { + capitalize(): string; + } +} +String.prototype.capitalize = function (this: string) { return this.charAt(0).toUpperCase() + this.slice(1); - }, - enumerable: false -}); +}; const StepCard = ({ type, @@ -46,7 +51,7 @@ const StepCard = ({ module: Module, toggleModule: any, enabledModules: any, - configValues: any + configValues: any }) => { const { attributes, @@ -55,147 +60,157 @@ const StepCard = ({ transform, transition, isDragging - } = useSortable({ id: module.name }); + } = useSortable({ id: module.name }); - const style = { + const style = { transform: CSS.Transform.toString(transform), transition, zIndex: isDragging ? "100" : "auto", opacity: isDragging ? 0.3 : 1 - }; - + }; + let name = module.name; const [helpOpen, setHelpOpen] = useState(false); const [configOpen, setConfigOpen] = useState(false); const enabled = enabledModules[type].find((m: any) => m[0] === name)[1]; - - return ( - - - } - label={module.display_name} /> - } - action ={ - - - - } - /> - - - {enabled && module.configs && name != 'cli_feeder' ? ( - - ) : null} - - - setHelpOpen(false)} - maxWidth="lg" - > - - {module.display_name} - - - - {module.manifest.description.split("\n").map((line: string) => line.trim()).join("\n")} - - - - {module.configs && name != 'cli_feeder' && } - + return ( + + + } + label={module.display_name} /> + } + /> + + + + setHelpOpen(true)}> + + + {enabled && module.configs && name != 'cli_feeder' ? ( + + ) : null} + + + + + + + + setHelpOpen(false)} + maxWidth="lg" + > + + {module.display_name} + + + + {module.manifest.description.split("\n").map((line: string) => line.trim()).join("\n")} + + + + {module.configs && name != 'cli_feeder' && } + ) - } - - -function ConfigPanel({ module, open, setOpen, configValues }: { module: any, open: boolean, setOpen: any, configValues: any }) { +} +function ConfigField({ config_value, module, configValues }: { config_value: any, module: Module, configValues: any }) { function setConfigValue(config: any, value: any) { - configValues[module.name][config] = value; + configValues[module.name][config] = value; } + const config_args: Config = module.configs[config_value]; + const config_name: string = config_value.replace(/_/g, " "); + const config_display_name = config_name.capitalize(); + const value = configValues[module.name][config_value] || config_args.default; return ( - <> - setOpen(false)} - maxWidth="lg" - > - - {module.display_name} - - - - {Object.keys(module.configs).map((config_value: any) => { - const config_args = module.configs[config_value]; - const config_name = config_value.replace(/_/g," "); - const config_display_name = config_name.capitalize(); - const value = configValues[module.name][config_value] || config_args.default; - return ( - - {config_display_name} - - { config_args.type === 'bool' ? - { - setConfigValue(config_value, e.target.checked); - }} - />} label={config_args.help} - /> + + {config_display_name} {config_args.required && (`(required)`)} + + {config_args.type === 'bool' ? + { + setConfigValue(config_value, e.target.checked); + }} + />} label={config_args.help} + /> : ( - config_args.choices !== undefined ? - { + setConfigValue(config_value, e.target.value); + }} + > + {config_args.choices.map((choice: any) => { + return ( + {choice} + ); + })} + + : + (config_args.type === 'json_loader' ? + { + try { + let val = JSON.parse(e.target.value); + setConfigValue(config_value, val); + } catch (e) { + console.log(e); + } + } + } /> + : + { + setConfigValue(config_value, e.target.value); + }} + required={config_args.required} + /> + ) + ) + } + {config_args.type !== 'bool' && ( + {config_args.help.capitalize()} + )} + + + ) +} + +function ConfigPanel({ module, open, setOpen, configValues }: { module: Module, open: boolean, setOpen: any, configValues: any }) { + + return ( + <> + setOpen(false)} + maxWidth="lg" + > + + {module.display_name} + + + + {Object.keys(module.configs).map((config_value: any) => { + return ( + + ); })} - - : - ( config_args.type === 'json_loader' ? - { - try { - let val = JSON.parse(e.target.value); - setConfigValue(config_value, val); - } catch (e) { - console.log(e); - } - } - } /> - : - { - setConfigValue(config_value, e.target.value); - }} /> - ) - ) - } - {config_args.type !== 'bool' && ( - {config_args.help} - )} - - - ); - })} - - - - + + + + ); - } +} export default StepCard; \ No newline at end of file diff --git a/scripts/settings/src/types.d.ts b/scripts/settings/src/types.d.ts index 64cba2f..fdf80fc 100644 --- a/scripts/settings/src/types.d.ts +++ b/scripts/settings/src/types.d.ts @@ -3,6 +3,9 @@ export interface Config { description: string; type: string?; default: any; + help: string; + choices: string[]; + required: boolean; } interface Manifest { @@ -15,4 +18,4 @@ export interface Module { configs: { [key: string]: Config }; manifest: Manifest; display_name: string; -} \ No newline at end of file +} diff --git a/scripts/settings/src/vite-env.d.ts b/scripts/settings/src/vite-env.d.ts deleted file mode 100644 index 11f02fe..0000000 --- a/scripts/settings/src/vite-env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// From 4ee1e75aa22b054b942cd1a7d394d7fdf3ad3877 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Thu, 27 Feb 2025 21:24:34 +0000 Subject: [PATCH 100/160] Fix readthedocs config file --- .readthedocs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 39504d8..003a1ae 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -9,7 +9,7 @@ build: os: ubuntu-22.04 tools: python: "3.10" - node: "22" + nodejs: "22" jobs: post_install: - pip install poetry From 65a9885d86cc7af6389508686a214e1a5a2abc78 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Thu, 27 Feb 2025 21:33:04 +0000 Subject: [PATCH 101/160] A few more manifest types --- src/auto_archiver/modules/gsheet_feeder/__manifest__.py | 4 +++- src/auto_archiver/modules/local_storage/__manifest__.py | 2 +- .../modules/telethon_extractor/__manifest__.py | 4 +++- .../modules/wacz_extractor_enricher/__manifest__.py | 4 +++- .../modules/whisper_enricher/__manifest__.py | 8 ++++++-- 5 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/auto_archiver/modules/gsheet_feeder/__manifest__.py b/src/auto_archiver/modules/gsheet_feeder/__manifest__.py index 130b9f6..1d8f244 100644 --- a/src/auto_archiver/modules/gsheet_feeder/__manifest__.py +++ b/src/auto_archiver/modules/gsheet_feeder/__manifest__.py @@ -12,7 +12,9 @@ "default": None, "help": "the id of the sheet to archive (alternative to 'sheet' config)", }, - "header": {"default": 1, "help": "index of the header row (starts at 1)", "type": "int"}, + "header": {"default": 1, + "type": "int", + "help": "index of the header row (starts at 1)", "type": "int"}, "service_account": { "default": "secrets/service_account.json", "help": "service account JSON file path. Learn how to create one: https://gspread.readthedocs.io/en/latest/oauth2.html", diff --git a/src/auto_archiver/modules/local_storage/__manifest__.py b/src/auto_archiver/modules/local_storage/__manifest__.py index 4f6c2df..8ad6381 100644 --- a/src/auto_archiver/modules/local_storage/__manifest__.py +++ b/src/auto_archiver/modules/local_storage/__manifest__.py @@ -18,7 +18,7 @@ }, "save_to": {"default": "./local_archive", "help": "folder where to save archived content"}, "save_absolute": {"default": False, - "type": "bool", + "type": "bool", "help": "whether the path to the stored file is absolute or relative in the output result inc. formatters (WARN: leaks the file structure)"}, }, "description": """ diff --git a/src/auto_archiver/modules/telethon_extractor/__manifest__.py b/src/auto_archiver/modules/telethon_extractor/__manifest__.py index 458428b..5e58203 100644 --- a/src/auto_archiver/modules/telethon_extractor/__manifest__.py +++ b/src/auto_archiver/modules/telethon_extractor/__manifest__.py @@ -14,7 +14,9 @@ "api_hash": {"default": None, "help": "telegram API_HASH value, go to https://my.telegram.org/apps"}, "bot_token": {"default": None, "help": "optional, but allows access to more content such as large videos, talk to @botfather"}, "session_file": {"default": "secrets/anon", "help": "optional, records the telegram login session for future usage, '.session' will be appended to the provided value."}, - "join_channels": {"default": True, "help": "disables the initial setup with channel_invites config, useful if you have a lot and get stuck"}, + "join_channels": {"default": True, + "type": "bool", + "help": "disables the initial setup with channel_invites config, useful if you have a lot and get stuck"}, "channel_invites": { "default": {}, "help": "(JSON string) private channel invite links (format: t.me/joinchat/HASH OR t.me/+HASH) and (optional but important to avoid hanging for minutes on startup) channel id (format: CHANNEL_ID taken from a post url like https://t.me/c/CHANNEL_ID/1), the telegram account will join any new channels on setup", diff --git a/src/auto_archiver/modules/wacz_extractor_enricher/__manifest__.py b/src/auto_archiver/modules/wacz_extractor_enricher/__manifest__.py index a78a3a9..9b373b9 100644 --- a/src/auto_archiver/modules/wacz_extractor_enricher/__manifest__.py +++ b/src/auto_archiver/modules/wacz_extractor_enricher/__manifest__.py @@ -17,7 +17,9 @@ "configs": { "profile": {"default": None, "help": "browsertrix-profile (for profile generation see https://github.com/webrecorder/browsertrix-crawler#creating-and-using-browser-profiles)."}, "docker_commands": {"default": None, "help":"if a custom docker invocation is needed"}, - "timeout": {"default": 120, "help": "timeout for WACZ generation in seconds", "type": "int"}, + "timeout": {"default": 120, + "type": "int", + "help": "timeout for WACZ generation in seconds", "type": "int"}, "extract_media": {"default": False, "type": 'bool', "help": "If enabled all the images/videos/audio present in the WACZ archive will be extracted into separate Media and appear in the html report. The .wacz file will be kept untouched." diff --git a/src/auto_archiver/modules/whisper_enricher/__manifest__.py b/src/auto_archiver/modules/whisper_enricher/__manifest__.py index 98e743e..0e09d03 100644 --- a/src/auto_archiver/modules/whisper_enricher/__manifest__.py +++ b/src/auto_archiver/modules/whisper_enricher/__manifest__.py @@ -10,8 +10,12 @@ "help": "WhisperApi api endpoint, eg: https://whisperbox-api.com/api/v1, a deployment of https://github.com/bellingcat/whisperbox-transcribe."}, "api_key": {"required": True, "help": "WhisperApi api key for authentication"}, - "include_srt": {"default": False, "help": "Whether to include a subtitle SRT (SubRip Subtitle file) for the video (can be used in video players)."}, - "timeout": {"default": 90, "help": "How many seconds to wait at most for a successful job completion."}, + "include_srt": {"default": False, + "type": "bool", + "help": "Whether to include a subtitle SRT (SubRip Subtitle file) for the video (can be used in video players)."}, + "timeout": {"default": 90, + "type": "int", + "help": "How many seconds to wait at most for a successful job completion."}, "action": {"default": "translate", "help": "which Whisper operation to execute", "choices": ["transcribe", "translate", "language_detection"]}, From 3eb4ab41b86305855c308ee93501f409765b3f42 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Thu, 27 Feb 2025 21:37:37 +0000 Subject: [PATCH 102/160] Also generate the schema on each run --- .readthedocs.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 003a1ae..6e9d174 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -18,6 +18,8 @@ build: # See https://github.com/readthedocs/readthedocs.org/pull/11152/ - VIRTUAL_ENV=$READTHEDOCS_VIRTUALENV_PATH poetry install --with docs + # generate the config editor page. Schema then HTML + - poetry run python scripts/generate_settings_schema.py # install node dependencies and build the settings - cd scripts/settings && npm install && npm run build && cp dist/index.html ../../docs/source/installation/settings_base.html && cd ../.. From 54a2a19dd726b1844b468fe516c59a491270630e Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Thu, 27 Feb 2025 21:42:21 +0000 Subject: [PATCH 103/160] Also build auto-archiver --- .readthedocs.yaml | 2 +- scripts/settings/src/schema.json | 22 ++++++++++++++-------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 6e9d174..4efddd9 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -19,7 +19,7 @@ build: - VIRTUAL_ENV=$READTHEDOCS_VIRTUALENV_PATH poetry install --with docs # generate the config editor page. Schema then HTML - - poetry run python scripts/generate_settings_schema.py + - VIRTUAL_ENV=$READTHEDOCS_VIRTUALENV_PATH poetry run python scripts/generate_settings_schema.py # install node dependencies and build the settings - cd scripts/settings && npm install && npm run build && cp dist/index.html ../../docs/source/installation/settings_base.html && cd ../.. diff --git a/scripts/settings/src/schema.json b/scripts/settings/src/schema.json index 87ec55b..8376c9f 100644 --- a/scripts/settings/src/schema.json +++ b/scripts/settings/src/schema.json @@ -31,8 +31,8 @@ }, "header": { "default": 1, - "help": "index of the header row (starts at 1)", - "type": "int" + "type": "int", + "help": "index of the header row (starts at 1)" }, "service_account": { "default": "secrets/service_account.json", @@ -85,8 +85,8 @@ }, "header": { "default": 1, - "help": "index of the header row (starts at 1)", - "type": "int" + "type": "int", + "help": "index of the header row (starts at 1)" }, "service_account": { "default": "secrets/service_account.json", @@ -559,6 +559,7 @@ }, "join_channels": { "default": true, + "type": "bool", "help": "disables the initial setup with channel_invites config, useful if you have a lot and get stuck" }, "channel_invites": { @@ -587,6 +588,7 @@ }, "join_channels": { "default": true, + "type": "bool", "help": "disables the initial setup with channel_invites config, useful if you have a lot and get stuck" }, "channel_invites": { @@ -887,8 +889,8 @@ }, "timeout": { "default": 120, - "help": "timeout for WACZ generation in seconds", - "type": "int" + "type": "int", + "help": "timeout for WACZ generation in seconds" }, "extract_media": { "default": false, @@ -926,8 +928,8 @@ }, "timeout": { "default": 120, - "help": "timeout for WACZ generation in seconds", - "type": "int" + "type": "int", + "help": "timeout for WACZ generation in seconds" }, "extract_media": { "default": false, @@ -1150,10 +1152,12 @@ }, "include_srt": { "default": false, + "type": "bool", "help": "Whether to include a subtitle SRT (SubRip Subtitle file) for the video (can be used in video players)." }, "timeout": { "default": 90, + "type": "int", "help": "How many seconds to wait at most for a successful job completion." }, "action": { @@ -1178,10 +1182,12 @@ }, "include_srt": { "default": false, + "type": "bool", "help": "Whether to include a subtitle SRT (SubRip Subtitle file) for the video (can be used in video players)." }, "timeout": { "default": 90, + "type": "int", "help": "How many seconds to wait at most for a successful job completion." }, "action": { From 7620a671d1711ce6f35a411b9aa78ba1f37bead0 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Thu, 27 Feb 2025 22:02:44 +0000 Subject: [PATCH 104/160] Overwrite settings_base file --- .readthedocs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 4efddd9..6dc9fe5 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -21,7 +21,7 @@ build: # generate the config editor page. Schema then HTML - VIRTUAL_ENV=$READTHEDOCS_VIRTUALENV_PATH poetry run python scripts/generate_settings_schema.py # install node dependencies and build the settings - - cd scripts/settings && npm install && npm run build && cp dist/index.html ../../docs/source/installation/settings_base.html && cd ../.. + - cd scripts/settings && npm install && npm run build && yes | cp dist/index.html ../../docs/source/installation/settings_base.html && cd ../.. sphinx: From 6ba79049d9e90bea70e233575c027a233c82afb3 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Thu, 27 Feb 2025 22:16:33 +0000 Subject: [PATCH 105/160] Capitalize help text --- scripts/settings/src/StepCard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/settings/src/StepCard.tsx b/scripts/settings/src/StepCard.tsx index fe69359..f55458f 100644 --- a/scripts/settings/src/StepCard.tsx +++ b/scripts/settings/src/StepCard.tsx @@ -139,7 +139,7 @@ function ConfigField({ config_value, module, configValues }: { config_value: any onChange={(e) => { setConfigValue(config_value, e.target.checked); }} - />} label={config_args.help} + />} label={config_args.help.capitalize()} /> : ( From cc14e5cb9fa4e66a7d08e4b5fce99d67127a12eb Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Mon, 3 Mar 2025 09:06:40 +0000 Subject: [PATCH 106/160] Remove extra html/head tag from page - now it's embedded in RTD --- docs/source/installation/settings.html | 24 ++++-------------------- scripts/settings/index.html | 18 +----------------- scripts/settings/src/App.tsx | 2 +- 3 files changed, 6 insertions(+), 38 deletions(-) diff --git a/docs/source/installation/settings.html b/docs/source/installation/settings.html index 973a1e0..832938d 100644 --- a/docs/source/installation/settings.html +++ b/docs/source/installation/settings.html @@ -1,17 +1,4 @@ - - - - - - - - - - Auto Archiver Settings - - - +`)})})]}),t.configs&&v!="cli_feeder"&&$.jsx(Y3,{module:t,open:S,setOpen:k,configValues:o})]})};function K3({config_value:e,module:t,configValues:n}){function i(d,p){n[t.name][d]=p}const o=t.configs[e],u=e.replace(/_/g," ").capitalize(),f=n[t.name][e]||o.default;return $.jsxs(Pr,{children:[$.jsxs(dn,{variant:"body1",style:{fontWeight:"bold"},children:[u," ",o.required&&"(required)"," "]}),$.jsxs(tx,{size:"small",children:[o.type==="bool"?$.jsx(nx,{control:$.jsx(Yw,{defaultChecked:f,size:"small",id:`${t}.${e}`,onChange:d=>{i(e,d.target.checked)}}),label:o.help.capitalize()}):o.choices!==void 0?$.jsx(ag,{size:"small",id:`${t}.${e}`,defaultValue:f,onChange:d=>{i(e,d.target.value)},children:o.choices.map(d=>$.jsx(VN,{value:d,children:d},`${t}.${e}.${d}`))}):o.type==="json_loader"?$.jsx(zS,{multiline:!0,size:"small",id:`${t}.${e}`,defaultValue:JSON.stringify(f,null,2),rows:6,onChange:d=>{try{let p=JSON.parse(d.target.value);i(e,p)}catch(p){console.log(p)}}}):$.jsx(zS,{size:"small",id:`${t}.${e}`,defaultValue:f,type:o.type==="int"?"number":"text",onChange:d=>{i(e,d.target.value)},required:o.required}),o.type!=="bool"&&$.jsx(rx,{children:o.help.capitalize()})]})]})}function Y3({module:e,open:t,setOpen:n,configValues:i}){return $.jsx($.Fragment,{children:$.jsxs(Zw,{open:t,onClose:()=>n(!1),maxWidth:"lg",children:[$.jsx(ex,{children:e.display_name}),$.jsx(Jw,{children:$.jsx(cx,{direction:"column",spacing:1,children:Object.keys(e.configs).map(o=>$.jsx(K3,{config_value:o,module:e,configValues:i},o))})})]})})}function X3({setYamlFile:e}){const[t,n]=T.useState(!1),[i,o]=T.useState("Drag and drop your orchestration.yaml file here, or click to select a file.");function l(u){let f=u.target.files[0];if(f.type!=="application/x-yaml"){n(!0),o("Invalid type, only YAML files are accepted.");return}let d=new FileReader;d.onload=function(p){let m=p.target?p.target.result:"";try{let g=Cz(m);if(g.errors.length>0){n(!0),o("Invalid file. Make sure your Orchestration is a valid YAML file with a 'steps' section in it.");return}else n(!1),o("File loaded successfully.");e(g)}catch(g){console.error(g)}},d.readAsText(f)}return $.jsx($.Fragment,{children:$.jsxs("div",{style:{width:"100%",border:"dashed",textAlign:"center",borderWidth:"1px",padding:"20px"},children:[$.jsx("input",{name:"file",type:"file",accept:".yaml",onChange:l}),$.jsx(dn,{style:{marginTop:"20px"},variant:"body1",color:t?"error":"",children:i})]})})}function W3({stepType:e,setEnabledModules:t,enabledModules:n,configValues:i}){const[o,l]=T.useState(!1),[u,f]=T.useState(),[d,p]=T.useState([]);T.useEffect(()=>{p(n[e].map(([w,S])=>w))},[n]);const m=w=>{let S=w.target.id,k=w.target.checked;(e==="feeders"||e==="formatters")&&n[e].filter(([_,O])=>_!==S&&O||k&&_===S).length>1?l(!0):l(!1);let A=Object.fromEntries(Object.keys(n).map(N=>[N,n[N].map(([_,O])=>_===S?[_,k]:[_,O])]));t(A)},g=DD(IS(fg),IS(ug,{coordinateGetter:c5})),v=w=>{f(w.active.id)},b=w=>{f(void 0);const{active:S,over:k}=w;if(S.id!==(k==null?void 0:k.id)){const A=d.indexOf(S.id),N=d.indexOf(k==null?void 0:k.id);let _=pg(d,A,N),O={...n};O[e]=n[e].sort((R,M)=>_.indexOf(R[0])-_.indexOf(M[0])),t(O)}};return $.jsxs($.Fragment,{children:[$.jsxs(Pr,{sx:{my:4},children:[$.jsx(dn,{id:e,variant:"h6",style:{textTransform:"capitalize"},children:e}),$.jsxs(dn,{variant:"body1",children:["Select the ",e," you wish to enable. You can drag and move to reorder. Learn more about ",e," ",$.jsx("a",{href:`https://auto-archiver.readthedocs.io/en/latest/modules/${e.slice(0,-1)}.html`,target:"_blank",children:"here"}),"."]})]}),o?$.jsxs(dn,{variant:"body1",color:"error",children:["Only one ",e.slice(0,-1)," can be enabled at a time."]}):null,$.jsx(ML,{sensors:g,collisionDetection:BD,onDragEnd:b,onDragStart:v,children:$.jsx(ix,{container:!0,spacing:1,children:$.jsxs(e5,{items:d,strategy:hg,children:[d.map(w=>{let S=Dc[w];return $.jsx(G3,{type:e,module:S,toggleModule:m,enabledModules:n,configValues:i},w)}),$.jsx(WL,{children:u?$.jsx("div",{style:{width:"100%",height:"100%",backgroundColor:"grey",opacity:.1}}):null})]})},e)})]})}function Q3(){const[e,t]=T.useState(new qo),[n,i]=T.useState(Object.fromEntries(Object.keys(xo).map(f=>[f,xo[f].map(d=>[d,!1])]))),[o,l]=T.useState(Object.keys(Dc).reduce((f,d)=>(f[d]={},f),{})),u=function(f=!1){let d=n;const p={steps:Object.keys(xo).reduce((g,v)=>(g[v]=d[v].filter(([b,w])=>w).map(([b,w])=>b),g),{})};Object.keys(o).map(g=>{let v=o[g];v&&(p[g]=v)});let m=new qo(p);if(f)navigator.clipboard.writeText(String(m)).then(()=>{alert("Settings copied to clipboard.")});else{const g=new Blob([String(m)],{type:"application/x-yaml"}),v=URL.createObjectURL(g),b=document.createElement("a");b.href=v,b.download="orchestration.yaml",b.click()}};return T.useEffect(()=>{let f={};Object.keys(Dc).map(d=>{let m=Dc[d].configs;m&&(f[d]={},Object.keys(m).map(g=>{let v=m[g];v.default!==void 0&&(f[d][g]=v.default)}))}),l(f)},[]),T.useEffect(()=>{if(!e||e.contents==null)return;let d=e.toJS().steps,p=Object.fromEntries(Object.keys(xo).map(m=>[m,xo[m].map(g=>[g,d[m].indexOf(g)!==-1]).sort((g,v)=>{let b=d[m].indexOf(g[0]),w=d[m].indexOf(v[0]);return b===-1&&w===-1?g-v:w===-1?-1:b===-1?1:b-w})]).sort((m,g)=>QS.indexOf(m[0])-QS.indexOf(g[0])));i(p)},[e]),$.jsx(hM,{maxWidth:"lg",children:$.jsxs(Pr,{sx:{my:4},children:[$.jsxs(Pr,{sx:{my:4},children:[$.jsx(dn,{variant:"h5",children:"1. Select your orchestration.yaml settings file."}),$.jsx(X3,{setYamlFile:t})]}),$.jsxs(Pr,{sx:{my:4},children:[$.jsx(dn,{variant:"h5",children:"2. Choose the Modules you wish to enable/disable"}),Object.keys(xo).map(f=>$.jsx(Pr,{sx:{my:4},children:$.jsx(W3,{stepType:f,setEnabledModules:i,enabledModules:n,configValues:o})},f))]}),$.jsxs(Pr,{sx:{my:4},children:[$.jsx(dn,{variant:"h5",children:"3. Configure your Enabled Modules"}),$.jsx(dn,{variant:"body1",children:"Next to each module you've enabled, you can click 'Configure' to set the module's settings."})]}),$.jsxs(Pr,{sx:{my:4},children:[$.jsx(dn,{variant:"h5",children:"4. Save your settings"}),$.jsxs(cx,{direction:"row",spacing:2,sx:{my:2},children:[$.jsx(rm,{variant:"contained",color:"primary",onClick:()=>u(!0),children:"Copy Settings to Clipboard"}),$.jsx(rm,{variant:"contained",color:"primary",onClick:()=>u(),children:"Save Settings to File"})]})]})]})})}wT.createRoot(document.getElementById("root")).render($.jsx(T.StrictMode,{children:$.jsxs(bR,{theme:bD,children:[$.jsx(vM,{}),$.jsx(Q3,{})]})})); +
- - diff --git a/scripts/settings/index.html b/scripts/settings/index.html index b0cfa48..22ff169 100644 --- a/scripts/settings/index.html +++ b/scripts/settings/index.html @@ -1,19 +1,3 @@ - - - - - - - - - - Auto Archiver Settings - - +
- - diff --git a/scripts/settings/src/App.tsx b/scripts/settings/src/App.tsx index 26d306b..bc5ee21 100644 --- a/scripts/settings/src/App.tsx +++ b/scripts/settings/src/App.tsx @@ -154,7 +154,7 @@ function ModuleTypes({ stepType, setEnabledModules, enabledModules, configValues {stepType} - Select the {stepType} you wish to enable. You can drag and move to reorder. + Select the {stepType} you wish to enable. Drag to re-order. Learn more about {stepType} here. From 9845804277d0af624eac65d2eec9907672c0deba Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Mon, 3 Mar 2025 09:18:19 +0000 Subject: [PATCH 107/160] Fix up TODO plus add comments on integration into RTD page --- docs/source/development/settings_page.md | 19 +++++++++++++++---- scripts/settings/index.html | 20 ++++++++++++++++++++ 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/docs/source/development/settings_page.md b/docs/source/development/settings_page.md index 29c722a..41271b9 100644 --- a/docs/source/development/settings_page.md +++ b/docs/source/development/settings_page.md @@ -1,6 +1,6 @@ -# Settings Page +# Configuration Editor -The settings page (viewable here TODO: add link), is an easy-to-use UI for users to edit their auto-archiver settings. +The [configuration editor](../installation/config_editor.md), is an easy-to-use UI for users to edit their auto-archiver settings. The single-file app is built using React and vite. To get started developing the package, follow these steps: @@ -14,7 +14,18 @@ Use `nvm` to manage your node installations. Use: 2. Generate the `schema.json` file for the currently installed modules using `python scripts/generate_settings_schema.py` 3. Go to the settings folder `cd scripts/settings/` and build your environment with `npm i` -4. Run a development version of the page with `npm run dev` +4. Run a development version of the page with `npm run dev` and then open localhost:5173. 5. Build a release version of the page with `npm run build` -A release version creates a single-file app called `dist/index.html` \ No newline at end of file +A release version creates a single-file app called `dist/index.html`. This file should be copied to `docs/source/installation/settings_base.html` so that it can be integrated into the sphinx docs. + +```{note} + +The single-file app dist/index.html does not include any `` or `` tags as it is designed to be built into a RTD docs page. Edit `index.html` in the settings folder if you wish to modify the built page. +``` + +## Readthedocs Integration + +The configuration editor is built as part of the RTD deployment (see `.readthedocs.yaml` file). This command is run every time RTD is built: + +`cd scripts/settings && npm install && npm run build && yes | cp dist/index.html ../../docs/source/installation/settings_base.html && cd ../..` \ No newline at end of file diff --git a/scripts/settings/index.html b/scripts/settings/index.html index 22ff169..2f7639d 100644 --- a/scripts/settings/index.html +++ b/scripts/settings/index.html @@ -1,3 +1,23 @@ + + +
+ + From a88a37d0a5ada1e6343ec8fcd29cd40cd0745c59 Mon Sep 17 00:00:00 2001 From: Patrick Robertson Date: Mon, 3 Mar 2025 11:56:23 +0000 Subject: [PATCH 108/160] Hook in to RTD theme to set react theme --- docs/source/installation/settings.html | 48438 ++++++++++++++++++++++- scripts/settings/index.html | 14 +- scripts/settings/src/App.tsx | 2 +- scripts/settings/src/StepCard.tsx | 1 + scripts/settings/src/main.tsx | 39 +- scripts/settings/src/theme.tsx | 20 - scripts/settings/vite.config.ts | 4 + 7 files changed, 48282 insertions(+), 236 deletions(-) delete mode 100644 scripts/settings/src/theme.tsx diff --git a/docs/source/installation/settings.html b/docs/source/installation/settings.html index 832938d..b1bf1ff 100644 --- a/docs/source/installation/settings.html +++ b/docs/source/installation/settings.html @@ -1,4 +1,81 @@ - +`; +const rotateAnimation = typeof circularRotateKeyframe !== "string" ? css` + animation: ${circularRotateKeyframe} 1.4s linear infinite; + ` : null; +const dashAnimation = typeof circularDashKeyframe !== "string" ? css` + animation: ${circularDashKeyframe} 1.4s ease-in-out infinite; + ` : null; +const useUtilityClasses$u = (ownerState) => { + const { + classes, + variant, + color: color2, + disableShrink + } = ownerState; + const slots = { + root: ["root", variant, `color${capitalize(color2)}`], + svg: ["svg"], + circle: ["circle", `circle${capitalize(variant)}`, disableShrink && "circleDisableShrink"] + }; + return composeClasses(slots, getCircularProgressUtilityClass, classes); +}; +const CircularProgressRoot = styled("span", { + name: "MuiCircularProgress", + slot: "Root", + overridesResolver: (props, styles2) => { + const { + ownerState + } = props; + return [styles2.root, styles2[ownerState.variant], styles2[`color${capitalize(ownerState.color)}`]]; + } +})(memoTheme(({ + theme +}) => ({ + display: "inline-block", + variants: [{ + props: { + variant: "determinate" + }, + style: { + transition: theme.transitions.create("transform") + } + }, { + props: { + variant: "indeterminate" + }, + style: rotateAnimation || { + animation: `${circularRotateKeyframe} 1.4s linear infinite` + } + }, ...Object.entries(theme.palette).filter(createSimplePaletteValueFilter()).map(([color2]) => ({ + props: { + color: color2 + }, + style: { + color: (theme.vars || theme).palette[color2].main + } + }))] +}))); +const CircularProgressSVG = styled("svg", { + name: "MuiCircularProgress", + slot: "Svg", + overridesResolver: (props, styles2) => styles2.svg +})({ + display: "block" + // Keeps the progress centered +}); +const CircularProgressCircle = styled("circle", { + name: "MuiCircularProgress", + slot: "Circle", + overridesResolver: (props, styles2) => { + const { + ownerState + } = props; + return [styles2.circle, styles2[`circle${capitalize(ownerState.variant)}`], ownerState.disableShrink && styles2.circleDisableShrink]; + } +})(memoTheme(({ + theme +}) => ({ + stroke: "currentColor", + variants: [{ + props: { + variant: "determinate" + }, + style: { + transition: theme.transitions.create("stroke-dashoffset") + } + }, { + props: { + variant: "indeterminate" + }, + style: { + // Some default value that looks fine waiting for the animation to kicks in. + strokeDasharray: "80px, 200px", + strokeDashoffset: 0 + // Add the unit to fix a Edge 16 and below bug. + } + }, { + props: ({ + ownerState + }) => ownerState.variant === "indeterminate" && !ownerState.disableShrink, + style: dashAnimation || { + // At runtime for Pigment CSS, `bufferAnimation` will be null and the generated keyframe will be used. + animation: `${circularDashKeyframe} 1.4s ease-in-out infinite` + } + }] +}))); +const CircularProgress = /* @__PURE__ */ reactExports.forwardRef(function CircularProgress2(inProps, ref) { + const props = useDefaultProps({ + props: inProps, + name: "MuiCircularProgress" + }); + const { + className, + color: color2 = "primary", + disableShrink = false, + size = 40, + style: style2, + thickness = 3.6, + value = 0, + variant = "indeterminate", + ...other + } = props; + const ownerState = { + ...props, + color: color2, + disableShrink, + size, + thickness, + value, + variant + }; + const classes = useUtilityClasses$u(ownerState); + const circleStyle = {}; + const rootStyle = {}; + const rootProps = {}; + if (variant === "determinate") { + const circumference = 2 * Math.PI * ((SIZE - thickness) / 2); + circleStyle.strokeDasharray = circumference.toFixed(3); + rootProps["aria-valuenow"] = Math.round(value); + circleStyle.strokeDashoffset = `${((100 - value) / 100 * circumference).toFixed(3)}px`; + rootStyle.transform = "rotate(-90deg)"; + } + return /* @__PURE__ */ jsxRuntimeExports.jsx(CircularProgressRoot, { + className: clsx(classes.root, className), + style: { + width: size, + height: size, + ...rootStyle, + ...style2 + }, + ownerState, + ref, + role: "progressbar", + ...rootProps, + ...other, + children: /* @__PURE__ */ jsxRuntimeExports.jsx(CircularProgressSVG, { + className: classes.svg, + ownerState, + viewBox: `${SIZE / 2} ${SIZE / 2} ${SIZE} ${SIZE}`, + children: /* @__PURE__ */ jsxRuntimeExports.jsx(CircularProgressCircle, { + className: classes.circle, + style: circleStyle, + ownerState, + cx: SIZE, + cy: SIZE, + r: (SIZE - thickness) / 2, + fill: "none", + strokeWidth: thickness + }) + }) + }); +}); +function getIconButtonUtilityClass(slot) { + return generateUtilityClass("MuiIconButton", slot); +} +const iconButtonClasses = generateUtilityClasses("MuiIconButton", ["root", "disabled", "colorInherit", "colorPrimary", "colorSecondary", "colorError", "colorInfo", "colorSuccess", "colorWarning", "edgeStart", "edgeEnd", "sizeSmall", "sizeMedium", "sizeLarge", "loading", "loadingIndicator", "loadingWrapper"]); +const useUtilityClasses$t = (ownerState) => { + const { + classes, + disabled, + color: color2, + edge, + size, + loading + } = ownerState; + const slots = { + root: ["root", loading && "loading", disabled && "disabled", color2 !== "default" && `color${capitalize(color2)}`, edge && `edge${capitalize(edge)}`, `size${capitalize(size)}`], + loadingIndicator: ["loadingIndicator"], + loadingWrapper: ["loadingWrapper"] + }; + return composeClasses(slots, getIconButtonUtilityClass, classes); +}; +const IconButtonRoot = styled(ButtonBase, { + name: "MuiIconButton", + slot: "Root", + overridesResolver: (props, styles2) => { + const { + ownerState + } = props; + return [styles2.root, ownerState.loading && styles2.loading, ownerState.color !== "default" && styles2[`color${capitalize(ownerState.color)}`], ownerState.edge && styles2[`edge${capitalize(ownerState.edge)}`], styles2[`size${capitalize(ownerState.size)}`]]; + } +})(memoTheme(({ + theme +}) => ({ + textAlign: "center", + flex: "0 0 auto", + fontSize: theme.typography.pxToRem(24), + padding: 8, + borderRadius: "50%", + color: (theme.vars || theme).palette.action.active, + transition: theme.transitions.create("background-color", { + duration: theme.transitions.duration.shortest + }), + variants: [{ + props: (props) => !props.disableRipple, + style: { + "--IconButton-hoverBg": theme.vars ? `rgba(${theme.vars.palette.action.activeChannel} / ${theme.vars.palette.action.hoverOpacity})` : alpha(theme.palette.action.active, theme.palette.action.hoverOpacity), + "&:hover": { + backgroundColor: "var(--IconButton-hoverBg)", + // Reset on touch devices, it doesn't add specificity + "@media (hover: none)": { + backgroundColor: "transparent" + } + } + } + }, { + props: { + edge: "start" + }, + style: { + marginLeft: -12 + } + }, { + props: { + edge: "start", + size: "small" + }, + style: { + marginLeft: -3 + } + }, { + props: { + edge: "end" + }, + style: { + marginRight: -12 + } + }, { + props: { + edge: "end", + size: "small" + }, + style: { + marginRight: -3 + } + }] +})), memoTheme(({ + theme +}) => ({ + variants: [{ + props: { + color: "inherit" + }, + style: { + color: "inherit" + } + }, ...Object.entries(theme.palette).filter(createSimplePaletteValueFilter()).map(([color2]) => ({ + props: { + color: color2 + }, + style: { + color: (theme.vars || theme).palette[color2].main + } + })), ...Object.entries(theme.palette).filter(createSimplePaletteValueFilter()).map(([color2]) => ({ + props: { + color: color2 + }, + style: { + "--IconButton-hoverBg": theme.vars ? `rgba(${(theme.vars || theme).palette[color2].mainChannel} / ${theme.vars.palette.action.hoverOpacity})` : alpha((theme.vars || theme).palette[color2].main, theme.palette.action.hoverOpacity) + } + })), { + props: { + size: "small" + }, + style: { + padding: 5, + fontSize: theme.typography.pxToRem(18) + } + }, { + props: { + size: "large" + }, + style: { + padding: 12, + fontSize: theme.typography.pxToRem(28) + } + }], + [`&.${iconButtonClasses.disabled}`]: { + backgroundColor: "transparent", + color: (theme.vars || theme).palette.action.disabled + }, + [`&.${iconButtonClasses.loading}`]: { + color: "transparent" + } +}))); +const IconButtonLoadingIndicator = styled("span", { + name: "MuiIconButton", + slot: "LoadingIndicator", + overridesResolver: (props, styles2) => styles2.loadingIndicator +})(({ + theme +}) => ({ + display: "none", + position: "absolute", + visibility: "visible", + top: "50%", + left: "50%", + transform: "translate(-50%, -50%)", + color: (theme.vars || theme).palette.action.disabled, + variants: [{ + props: { + loading: true + }, + style: { + display: "flex" + } + }] +})); +const IconButton = /* @__PURE__ */ reactExports.forwardRef(function IconButton2(inProps, ref) { + const props = useDefaultProps({ + props: inProps, + name: "MuiIconButton" + }); + const { + edge = false, + children, + className, + color: color2 = "default", + disabled = false, + disableFocusRipple = false, + size = "medium", + id: idProp, + loading = null, + loadingIndicator: loadingIndicatorProp, + ...other + } = props; + const loadingId = useId(idProp); + const loadingIndicator = loadingIndicatorProp ?? /* @__PURE__ */ jsxRuntimeExports.jsx(CircularProgress, { + "aria-labelledby": loadingId, + color: "inherit", + size: 16 + }); + const ownerState = { + ...props, + edge, + color: color2, + disabled, + disableFocusRipple, + loading, + loadingIndicator, + size + }; + const classes = useUtilityClasses$t(ownerState); + return /* @__PURE__ */ jsxRuntimeExports.jsxs(IconButtonRoot, { + id: loading ? loadingId : idProp, + className: clsx(classes.root, className), + centerRipple: true, + focusRipple: !disableFocusRipple, + disabled: disabled || loading, + ref, + ...other, + ownerState, + children: [typeof loading === "boolean" && // use plain HTML span to minimize the runtime overhead + /* @__PURE__ */ jsxRuntimeExports.jsx("span", { + className: classes.loadingWrapper, + style: { + display: "contents" + }, + children: /* @__PURE__ */ jsxRuntimeExports.jsx(IconButtonLoadingIndicator, { + className: classes.loadingIndicator, + ownerState, + children: loading && loadingIndicator + }) + }), children] + }); +}); +function getTypographyUtilityClass(slot) { + return generateUtilityClass("MuiTypography", slot); +} +const typographyClasses = generateUtilityClasses("MuiTypography", ["root", "h1", "h2", "h3", "h4", "h5", "h6", "subtitle1", "subtitle2", "body1", "body2", "inherit", "button", "caption", "overline", "alignLeft", "alignRight", "alignCenter", "alignJustify", "noWrap", "gutterBottom", "paragraph"]); +const v6Colors = { + primary: true, + secondary: true, + error: true, + info: true, + success: true, + warning: true, + textPrimary: true, + textSecondary: true, + textDisabled: true +}; +const extendSxProp = internal_createExtendSxProp(); +const useUtilityClasses$s = (ownerState) => { + const { + align, + gutterBottom, + noWrap, + paragraph: paragraph2, + variant, + classes + } = ownerState; + const slots = { + root: ["root", variant, ownerState.align !== "inherit" && `align${capitalize(align)}`, gutterBottom && "gutterBottom", noWrap && "noWrap", paragraph2 && "paragraph"] + }; + return composeClasses(slots, getTypographyUtilityClass, classes); +}; +const TypographyRoot = styled("span", { + name: "MuiTypography", + slot: "Root", + overridesResolver: (props, styles2) => { + const { + ownerState + } = props; + return [styles2.root, ownerState.variant && styles2[ownerState.variant], ownerState.align !== "inherit" && styles2[`align${capitalize(ownerState.align)}`], ownerState.noWrap && styles2.noWrap, ownerState.gutterBottom && styles2.gutterBottom, ownerState.paragraph && styles2.paragraph]; + } +})(memoTheme(({ + theme +}) => { + var _a; + return { + margin: 0, + variants: [{ + props: { + variant: "inherit" + }, + style: { + // Some elements, like