From 2a0dfaead257d456618a77648f0f68c5c191b623 Mon Sep 17 00:00:00 2001 From: erinhmclark Date: Fri, 28 Mar 2025 00:41:05 +0000 Subject: [PATCH 1/7] Add instagrapi server scripts --- scripts/instagrapi_server/.gitignore | 0 scripts/instagrapi_server/Dockerfile | 19 +++ scripts/instagrapi_server/pyproject.toml | 18 +++ scripts/instagrapi_server/src/instaserver.py | 157 +++++++++++++++++++ 4 files changed, 194 insertions(+) create mode 100644 scripts/instagrapi_server/.gitignore create mode 100644 scripts/instagrapi_server/Dockerfile create mode 100644 scripts/instagrapi_server/pyproject.toml create mode 100644 scripts/instagrapi_server/src/instaserver.py diff --git a/scripts/instagrapi_server/.gitignore b/scripts/instagrapi_server/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/scripts/instagrapi_server/Dockerfile b/scripts/instagrapi_server/Dockerfile new file mode 100644 index 0000000..0fa9a63 --- /dev/null +++ b/scripts/instagrapi_server/Dockerfile @@ -0,0 +1,19 @@ +FROM python:3.12-slim +WORKDIR /app + +# Install Poetry +RUN pip install --upgrade pip +RUN pip install poetry + +# Copy all source code +COPY . . + +# Prevent Poetry from creating a virtual environment +RUN poetry config virtualenvs.create false + +# Install dependencies +RUN poetry install --no-root + + +# Use uvicorn to run the FastAPI app +CMD ["poetry", "run", "uvicorn", "src.instaserver:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/scripts/instagrapi_server/pyproject.toml b/scripts/instagrapi_server/pyproject.toml new file mode 100644 index 0000000..3c0177c --- /dev/null +++ b/scripts/instagrapi_server/pyproject.toml @@ -0,0 +1,18 @@ +[project] +name = "instaserver" +version = "0.1.0" +description = "A FastAPI InstagrAPI server" +package-mode = false +requires-python = ">=3.10" +dependencies = [ + "fastapi (>=0.115.12,<0.116.0)", + "instagrapi (>=2.1.3,<3.0.0)", + "uvicorn (>=0.34.0,<0.35.0)", + "pillow (>=11.1.0,<12.0.0)", + "python-dotenv (>=1.1.0,<2.0.0)" +] + + +[build-system] +requires = ["poetry-core>=2.0.0,<3.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/scripts/instagrapi_server/src/instaserver.py b/scripts/instagrapi_server/src/instaserver.py new file mode 100644 index 0000000..8d5c57b --- /dev/null +++ b/scripts/instagrapi_server/src/instaserver.py @@ -0,0 +1,157 @@ +"""https://subzeroid.github.io/instagrapi/ + +Run using the following command: + uvicorn src.instgrapinstance.instaserver:app --host 0.0.0.0 --port 8000 --reload +""" + +import logging +import os +import sys +from dotenv import load_dotenv + +from fastapi import FastAPI, HTTPException +from instagrapi import Client +from instagrapi.exceptions import LoginRequired, BadCredentials + +load_dotenv(dotenv_path="secrets/.env") +logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") + +INSTAGRAM_USERNAME = os.getenv("INSTAGRAM_USERNAME") +INSTAGRAM_PASSWORD = os.getenv("INSTAGRAM_PASSWORD") +SESSION_FILE = "secrets/instagrapi_session.json" + +app = FastAPI() +cl = Client() + + +@app.on_event("startup") +def startup_event(): + """Login automatically when server starts""" + try: + login_instagram() + except RuntimeError as e: + logging.error(f"API failed to start: {e}") + sys.exit(1) + + +def login_instagram(): + """Ensures Instagrapi is logged in and session is persistent""" + if not INSTAGRAM_USERNAME or not INSTAGRAM_PASSWORD: + raise RuntimeError("Instagram credentials are missing.") + + if os.path.exists(SESSION_FILE): + try: + cl.load_settings(SESSION_FILE) + cl.get_timeline_feed() + logging.info("Using saved session.") + return + except LoginRequired: + logging.info("Session expired. Logging in again...") + + try: + cl.login(INSTAGRAM_USERNAME, INSTAGRAM_PASSWORD) + cl.dump_settings(SESSION_FILE) + logging.info("Login successful, session saved.") + except BadCredentials as bc: + raise RuntimeError("Incorrect Instagram username or password.") from bc + except Exception as e: + raise RuntimeError(f"Login failed: {e}") from e + + +@app.get("/v1/media/by/id") +def get_media_by_id(id: str): + """Fetch post details by media ID""" + logging.info(f"Fetching media by ID: {id}") + try: + media = cl.media_info(id) + return media.model_dump() + except Exception as e: + logging.warning(f"Media not found for ID {id}: {e}") + raise HTTPException(status_code=404, detail="Post not found") from e + + +@app.get("/v1/media/by/code") +def get_media_by_code(code: str): + """Fetch post details by shortcode""" + logging.info(f"Fetching media by shortcode: {code}") + try: + media_id = cl.media_pk_from_code(code) + media = cl.media_info(media_id) + return media.model_dump() + except Exception as e: + logging.warning(f"Media not found for code {code}: {e}") + raise HTTPException(status_code=404, detail="Post not found") from e + + +@app.get("/v2/user/tag/medias") +def get_user_tagged_medias(user_id: str, page_id: str = None): + logging.info(f"Fetching tagged medias for user_id={user_id} page_id={page_id}") + try: + # Placeholder for now + items, next_page_id = [], None + return {"response": {"items": items}, "next_page_id": next_page_id} + except Exception as e: + logging.warning(f"Tagged media not found for {user_id}: {e}") + raise HTTPException(status_code=404, detail="Tagged media not found") from e + + +@app.get("/v1/user/highlights") +def get_user_highlights(user_id: str): + logging.info(f"Fetching highlights list for user_id={user_id}") + try: + highlights = cl.user_highlights(user_id) + return [h.model_dump() for h in highlights] + except Exception as e: + logging.warning(f"Highlights not found for {user_id}: {e}") + raise HTTPException(status_code=404, detail="No highlights found") from e + + +@app.get("/v2/highlight/by/id") +def get_highlight_by_id(id: str): + logging.info(f"Fetching highlight details for id={id}") + try: + highlight = cl.highlight_info(id) + return {"response": {"reels": {f"highlight:{id}": highlight.model_dump()}}} + except Exception as e: + logging.warning(f"Highlight not found for id {id}: {e}") + raise HTTPException(status_code=404, detail="Highlight not found") from e + + +@app.get("/v1/user/stories/by/username") +def get_stories(username: str): + logging.info(f"Fetching stories for username={username}") + try: + user_id = cl.user_id_from_username(username) + stories = cl.user_stories(user_id) + return [story.model_dump() for story in stories] + except Exception as e: + logging.warning(f"Stories not found for {username}: {e}") + raise HTTPException(status_code=404, detail="Stories not found") from e + + +@app.get("/v2/user/by/username") +def get_user_by_username(username: str): + logging.info(f"Fetching user profile for username={username}") + try: + user = cl.user_info_by_username(username) + return {"user": user.model_dump()} + except Exception as e: + logging.warning(f"User not found: {username}: {e}") + raise HTTPException(status_code=404, detail="User not found") from e + + +@app.get("/v1/user/medias/chunk") +def get_user_medias(user_id: str, end_cursor: str = None): + logging.info(f"Fetching paginated medias for user_id={user_id}, end_cursor={end_cursor}") + try: + posts, next_cursor = cl.user_medias_paginated(user_id, end_cursor=end_cursor) + return [[post.model_dump() for post in posts], next_cursor] + except Exception as e: + logging.warning(f"No posts found for user_id={user_id}: {e}") + raise HTTPException(status_code=404, detail="No posts found") from e + + +if __name__ == "__main__": + import uvicorn + + uvicorn.run(app, host="0.0.0.0", port=8000) From 00b29db3904e99af99f4a202c207ab93a8927970 Mon Sep 17 00:00:00 2001 From: erinhmclark Date: Fri, 28 Mar 2025 00:41:35 +0000 Subject: [PATCH 2/7] Update documentaion for instagrapi api --- docs/source/how_to/run_instagrapi_server.md | 138 ++++++++++++++++++ .../instagram_api_extractor/__manifest__.py | 6 +- 2 files changed, 142 insertions(+), 2 deletions(-) create mode 100644 docs/source/how_to/run_instagrapi_server.md diff --git a/docs/source/how_to/run_instagrapi_server.md b/docs/source/how_to/run_instagrapi_server.md new file mode 100644 index 0000000..b55f737 --- /dev/null +++ b/docs/source/how_to/run_instagrapi_server.md @@ -0,0 +1,138 @@ +# InstagrAPI Server + +The instagram API Extractor requires a running instance of the InstagrAPI server. +We have a lightweight script with the endpoints required for our Instagram API Extractor module which you can run locally, or via Docker. + +To run this you need to install some additional requirements. + +## Setup + +Although there is an option to run the server in a Docker container, the authentication is usually rejected without an additional session file, which can be created by running the server locally first. + +⚠️ Warning: Remember that it's best not to use your own personal account for archiving. [Here's why](../installation/authentication.md#recommendations-for-authentication). + +## Overview: How the Setup Works + +1. You enter your Instagram credentials in a local `.env` file +2. You run the server **once locally** to generate a session file +3. After that, you can choose to run the server again locally or inside Docker without needing to log in again + +--- + +## 1. One-Time Local Setup + +This generates a session file using your login details so Instagram recognises your login. +This will be reused automatically by the script, and can also be passed to the Docker container. + +### 🔧 Step-by-step: + +1. **Navigate to the server folder (and stay there for the rest of this guide)**: + ```bash + cd scripts/instagrapi_server + ``` + +2. **Create a `secrets/` folder** (if it doesn't already exist in `scripts/instagrapi_server`): + ```bash + mkdir -p secrets + ``` + +3. **Create a `.env` file** inside `secrets/` with your Instagram credentials: + ```dotenv + INSTAGRAM_USERNAME="your_username" + INSTAGRAM_PASSWORD="your_password" + ``` + +4. **Install dependencies** using the pyproject.toml file: + + ```bash + poetry install --no-root + ``` + +5. **Run the server locally**: + ```bash + poetry run uvicorn src.instaserver:app --port 8000 + ``` + +6. **Watch for the message**: + ``` + Login successful, session saved. + ``` + +The server should now be running within that session, and accessible at http://127.0.0.1:8000 + +You can set this in the orchestration.yaml file like this: +```yaml +instagram_api_extractor: + api_endpoint: http://127.0.0.1:8000 +``` + +Or to run it in a Docker container, you can pass the session file to it now. +**Stop the server** (`Ctrl+C`). + +📅 Your session has now been saved to `secrets/instagrapi_session.json`. + +--- + +## 2. Running the Server Again + +Once the session file is created, you should be able to run the server without logging in again. + +### To run it locally (from scripts/instagrapi_server): +```bash +poetry run uvicorn src.instgrapinstance.instaserver:app --port 8000 +``` + +--- + +## 3. Running via Docker (After Setup to create the session file) + +Once the session file exists, you can pass this to docker Docker and it should authenticate successfully. + +### 🔨 Build the Docker image: +```bash +docker build -t instagrapi-server . +``` + +### ▶️ Run the container: +```bash +docker run -d \ + --env-file secrets/.env \ + -v "$(pwd)/secrets:/app/secrets" \ + -p 8000:8000 \ + --name ig-instasrv \ + instagrapi-server +``` + +This passes the /secrets/ directory to docker, so it can use your saved session file and credentials. + +--- + +## 4. Optional Cleanup + +- **Stop the Docker container**: + ```bash + docker stop ig-instasrv + ``` + +- **Remove the container**: + ```bash + docker rm ig-instasrv + ``` + +- **Remove the Docker image**: + ```bash + docker rmi instagrapi-server + ``` + +### ⏱ To run again later: +```bash +docker start ig-instasrv +``` + +--- + +## Notes + +- You only need to run the server **locally once** to generate a session. +- Never share your `.env` or `instagrapi_session.json` — these contain sensitive login data. +- If you want to reset your session, simply delete the `secrets/instagrapi_session.json` file and re-run the local server. diff --git a/src/auto_archiver/modules/instagram_api_extractor/__manifest__.py b/src/auto_archiver/modules/instagram_api_extractor/__manifest__.py index e10bd1e..c40a5d8 100644 --- a/src/auto_archiver/modules/instagram_api_extractor/__manifest__.py +++ b/src/auto_archiver/modules/instagram_api_extractor/__manifest__.py @@ -31,9 +31,11 @@ }, }, "description": """ -Archives various types of Instagram content using the Instagrapi API. +Archives Instagram content using a deployment of the [Instagrapi API](https://subzeroid.github.io/instagrapi/). -Requires setting up an Instagrapi API deployment and providing an access token and API endpoint. +Requires either getting a token from using a hosted [(paid) service](https://api.instagrapi.com/docs) and setting this in the configuration file. +Alternatively you can run your own server. We have a basic script which you can use for this which can be ran locally or using Docker. +For more information, read the [how to guide](https://auto-archiver.readthedocs.io/en/latest/how_to/run_instagrapi_server.html) on this. ### Features - Connects to an Instagrapi API deployment to fetch Instagram profiles, posts, stories, highlights, reels, and tagged content. From 1466700b455ca3ec497a1db183d6f167614e9b74 Mon Sep 17 00:00:00 2001 From: erinhmclark Date: Fri, 28 Mar 2025 08:23:10 +0000 Subject: [PATCH 3/7] Small updates to docs, poetry.lock --- docs/source/how_to/run_instagrapi_server.md | 11 +-- poetry.lock | 97 +++++---------------- 2 files changed, 27 insertions(+), 81 deletions(-) diff --git a/docs/source/how_to/run_instagrapi_server.md b/docs/source/how_to/run_instagrapi_server.md index b55f737..c7b8928 100644 --- a/docs/source/how_to/run_instagrapi_server.md +++ b/docs/source/how_to/run_instagrapi_server.md @@ -1,6 +1,6 @@ # InstagrAPI Server -The instagram API Extractor requires a running instance of the InstagrAPI server. +The instagram API Extractor requires access to a running instance of the InstagrAPI server. We have a lightweight script with the endpoints required for our Instagram API Extractor module which you can run locally, or via Docker. To run this you need to install some additional requirements. @@ -11,7 +11,8 @@ Although there is an option to run the server in a Docker container, the authent ⚠️ Warning: Remember that it's best not to use your own personal account for archiving. [Here's why](../installation/authentication.md#recommendations-for-authentication). -## Overview: How the Setup Works + +### Overview: How the Setup Works 1. You enter your Instagram credentials in a local `.env` file 2. You run the server **once locally** to generate a session file @@ -21,8 +22,8 @@ Although there is an option to run the server in a Docker container, the authent ## 1. One-Time Local Setup -This generates a session file using your login details so Instagram recognises your login. -This will be reused automatically by the script, and can also be passed to the Docker container. +This generates a session file using your login details so Instagram recognises your login as authentic. +This will be reused automatically by the server script, and can also be passed to the Docker container. ### 🔧 Step-by-step: @@ -103,7 +104,7 @@ docker run -d \ instagrapi-server ``` -This passes the /secrets/ directory to docker, so it can use your saved session file and credentials. +This passes the /secrets/ directory to docker, which should contain the session file created from the first local run. --- diff --git a/poetry.lock b/poetry.lock index 631c1d6..5a83804 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand. [[package]] name = "accessible-pygments" @@ -51,7 +51,7 @@ typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} [package.extras] doc = ["Sphinx (>=8.2,<9.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"] -test = ["anyio[trio]", "blockbuster (>=1.5.23)", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1) ; python_version >= \"3.10\"", "uvloop (>=0.21) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\" and python_version < \"3.14\""] +test = ["anyio[trio]", "blockbuster (>=1.5.23)", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"] trio = ["trio (>=0.26.1)"] [[package]] @@ -82,12 +82,12 @@ files = [ ] [package.extras] -benchmark = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] -cov = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] -dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier"] -tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] -tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\""] +tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] [[package]] name = "authlib" @@ -133,7 +133,7 @@ files = [ ] [package.extras] -dev = ["backports.zoneinfo ; python_version < \"3.9\"", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata ; sys_platform == \"win32\""] +dev = ["backports.zoneinfo", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata"] [[package]] name = "beautifulsoup4" @@ -645,10 +645,10 @@ files = [ cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} [package.extras] -docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=3.0.0) ; python_version >= \"3.8\""] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=3.0.0)"] docstest = ["pyenchant (>=3)", "readme-renderer (>=30.0)", "sphinxcontrib-spelling (>=7.3.1)"] -nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2) ; python_version >= \"3.8\""] -pep8test = ["check-sdist ; python_version >= \"3.8\"", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"] +nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2)"] +pep8test = ["check-sdist", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"] sdist = ["build (>=1.0.0)"] ssh = ["bcrypt (>=3.1.5)"] test = ["certifi (>=2024)", "cryptography-vectors (==44.0.2)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] @@ -766,7 +766,7 @@ files = [ [package.extras] docs = ["furo (>=2024.8.6)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] testing = ["covdefaults (>=2.3)", "coverage (>=7.6.10)", "diff-cover (>=9.2.1)", "pytest (>=8.3.4)", "pytest-asyncio (>=0.25.2)", "pytest-cov (>=6)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.28.1)"] -typing = ["typing-extensions (>=4.12.2) ; python_version < \"3.11\""] +typing = ["typing-extensions (>=4.12.2)"] [[package]] name = "future" @@ -801,7 +801,7 @@ requests = ">=2.18.0,<3.0.0" [package.extras] async-rest = ["google-auth[aiohttp] (>=2.35.0,<3.0.dev0)"] -grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio (>=1.49.1,<2.0dev) ; python_version >= \"3.11\"", "grpcio-status (>=1.33.2,<2.0.dev0)", "grpcio-status (>=1.49.1,<2.0.dev0) ; python_version >= \"3.11\""] +grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio (>=1.49.1,<2.0dev)", "grpcio-status (>=1.33.2,<2.0.dev0)", "grpcio-status (>=1.49.1,<2.0.dev0)"] grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] @@ -1100,7 +1100,7 @@ colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""} win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""} [package.extras] -dev = ["Sphinx (==8.1.3) ; python_version >= \"3.11\"", "build (==1.2.2) ; python_version >= \"3.11\"", "colorama (==0.4.5) ; python_version < \"3.8\"", "colorama (==0.4.6) ; python_version >= \"3.8\"", "exceptiongroup (==1.1.3) ; python_version >= \"3.7\" and python_version < \"3.11\"", "freezegun (==1.1.0) ; python_version < \"3.8\"", "freezegun (==1.5.0) ; python_version >= \"3.8\"", "mypy (==v0.910) ; python_version < \"3.6\"", "mypy (==v0.971) ; python_version == \"3.6\"", "mypy (==v1.13.0) ; python_version >= \"3.8\"", "mypy (==v1.4.1) ; python_version == \"3.7\"", "myst-parser (==4.0.0) ; python_version >= \"3.11\"", "pre-commit (==4.0.1) ; python_version >= \"3.9\"", "pytest (==6.1.2) ; python_version < \"3.8\"", "pytest (==8.3.2) ; python_version >= \"3.8\"", "pytest-cov (==2.12.1) ; python_version < \"3.8\"", "pytest-cov (==5.0.0) ; python_version == \"3.8\"", "pytest-cov (==6.0.0) ; python_version >= \"3.9\"", "pytest-mypy-plugins (==1.9.3) ; python_version >= \"3.6\" and python_version < \"3.8\"", "pytest-mypy-plugins (==3.1.0) ; python_version >= \"3.8\"", "sphinx-rtd-theme (==3.0.2) ; python_version >= \"3.11\"", "tox (==3.27.1) ; python_version < \"3.8\"", "tox (==4.23.2) ; python_version >= \"3.8\"", "twine (==6.0.1) ; python_version >= \"3.11\""] +dev = ["Sphinx (==8.1.3)", "build (==1.2.2)", "colorama (==0.4.5)", "colorama (==0.4.6)", "exceptiongroup (==1.1.3)", "freezegun (==1.1.0)", "freezegun (==1.5.0)", "mypy (==v0.910)", "mypy (==v0.971)", "mypy (==v1.13.0)", "mypy (==v1.4.1)", "myst-parser (==4.0.0)", "pre-commit (==4.0.1)", "pytest (==6.1.2)", "pytest (==8.3.2)", "pytest-cov (==2.12.1)", "pytest-cov (==5.0.0)", "pytest-cov (==6.0.0)", "pytest-mypy-plugins (==1.9.3)", "pytest-mypy-plugins (==3.1.0)", "sphinx-rtd-theme (==3.0.2)", "tox (==3.27.1)", "tox (==4.23.2)", "twine (==6.0.1)"] [[package]] name = "markdown-it-py" @@ -1564,7 +1564,7 @@ docs = ["furo", "olefile", "sphinx (>=8.1)", "sphinx-copybutton", "sphinx-inline fpx = ["olefile"] mic = ["olefile"] tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout", "trove-classifiers (>=2024.10.12)"] -typing = ["typing-extensions ; python_version < \"3.10\""] +typing = ["typing-extensions"] xmp = ["defusedxml"] [[package]] @@ -2265,23 +2265,6 @@ files = [ [package.dependencies] rich = ">=11.0.0" -[[package]] -name = "roman-numerals-py" -version = "3.1.0" -description = "Manipulate well-formed Roman numerals" -optional = false -python-versions = ">=3.9" -groups = ["docs"] -markers = "python_version >= \"3.12\"" -files = [ - {file = "roman_numerals_py-3.1.0-py3-none-any.whl", hash = "sha256:9da2ad2fb670bcf24e81070ceb3be72f6c11c440d73bd579fbeca1e9f330954c"}, - {file = "roman_numerals_py-3.1.0.tar.gz", hash = "sha256:be4bf804f083a4ce001b5eb7e3c0862479d10f94c936f6c4e5f250aa5ff5bd2d"}, -] - -[package.extras] -lint = ["mypy (==1.15.0)", "pyright (==1.1.394)", "ruff (==0.9.7)"] -test = ["pytest (>=8)"] - [[package]] name = "rsa" version = "4.9" @@ -2506,7 +2489,6 @@ description = "Python documentation generator" optional = false python-versions = ">=3.10" groups = ["docs"] -markers = "python_version < \"3.12\"" files = [ {file = "sphinx-8.1.3-py3-none-any.whl", hash = "sha256:09719015511837b76bf6e03e42eb7595ac8c2e41eeb9c29c5b755c6b677992a2"}, {file = "sphinx-8.1.3.tar.gz", hash = "sha256:43c1911eecb0d3e161ad78611bc905d1ad0e523e4ddc202a58a821773dc4c927"}, @@ -2536,43 +2518,6 @@ docs = ["sphinxcontrib-websupport"] lint = ["flake8 (>=6.0)", "mypy (==1.11.1)", "pyright (==1.1.384)", "pytest (>=6.0)", "ruff (==0.6.9)", "sphinx-lint (>=0.9)", "tomli (>=2)", "types-Pillow (==10.2.0.20240822)", "types-Pygments (==2.18.0.20240506)", "types-colorama (==0.4.15.20240311)", "types-defusedxml (==0.7.0.20240218)", "types-docutils (==0.21.0.20241005)", "types-requests (==2.32.0.20240914)", "types-urllib3 (==1.26.25.14)"] test = ["cython (>=3.0)", "defusedxml (>=0.7.1)", "pytest (>=8.0)", "setuptools (>=70.0)", "typing_extensions (>=4.9)"] -[[package]] -name = "sphinx" -version = "8.2.3" -description = "Python documentation generator" -optional = false -python-versions = ">=3.11" -groups = ["docs"] -markers = "python_version >= \"3.12\"" -files = [ - {file = "sphinx-8.2.3-py3-none-any.whl", hash = "sha256:4405915165f13521d875a8c29c8970800a0141c14cc5416a38feca4ea5d9b9c3"}, - {file = "sphinx-8.2.3.tar.gz", hash = "sha256:398ad29dee7f63a75888314e9424d40f52ce5a6a87ae88e7071e80af296ec348"}, -] - -[package.dependencies] -alabaster = ">=0.7.14" -babel = ">=2.13" -colorama = {version = ">=0.4.6", markers = "sys_platform == \"win32\""} -docutils = ">=0.20,<0.22" -imagesize = ">=1.3" -Jinja2 = ">=3.1" -packaging = ">=23.0" -Pygments = ">=2.17" -requests = ">=2.30.0" -roman-numerals-py = ">=1.0.0" -snowballstemmer = ">=2.2" -sphinxcontrib-applehelp = ">=1.0.7" -sphinxcontrib-devhelp = ">=1.0.6" -sphinxcontrib-htmlhelp = ">=2.0.6" -sphinxcontrib-jsmath = ">=1.0.1" -sphinxcontrib-qthelp = ">=1.0.6" -sphinxcontrib-serializinghtml = ">=1.1.9" - -[package.extras] -docs = ["sphinxcontrib-websupport"] -lint = ["betterproto (==2.0.0b6)", "mypy (==1.15.0)", "pypi-attestations (==0.0.21)", "pyright (==1.1.395)", "pytest (>=8.0)", "ruff (==0.9.9)", "sphinx-lint (>=0.9)", "types-Pillow (==10.2.0.20240822)", "types-Pygments (==2.19.0.20250219)", "types-colorama (==0.4.15.20240311)", "types-defusedxml (==0.7.0.20240218)", "types-docutils (==0.21.0.20241128)", "types-requests (==2.32.0.20241016)", "types-urllib3 (==1.26.25.14)"] -test = ["cython (>=3.0)", "defusedxml (>=0.7.1)", "pytest (>=8.0)", "pytest-xdist[psutil] (>=3.4)", "setuptools (>=70.0)", "typing_extensions (>=4.9)"] - [[package]] name = "sphinx-autoapi" version = "3.6.0" @@ -3031,7 +2976,7 @@ files = [ pysocks = {version = ">=1.5.6,<1.5.7 || >1.5.7,<2.0", optional = true, markers = "extra == \"socks\""} [package.extras] -brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] @@ -3054,7 +2999,7 @@ h11 = ">=0.8" typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} [package.extras] -standard = ["colorama (>=0.4) ; sys_platform == \"win32\"", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1) ; sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\"", "watchfiles (>=0.13)", "websockets (>=10.4)"] +standard = ["colorama (>=0.4)", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] [[package]] name = "virtualenv" @@ -3075,7 +3020,7 @@ platformdirs = ">=3.9.1,<5" [package.extras] docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] [[package]] name = "vk-api" @@ -3337,7 +3282,7 @@ files = [ ] [package.extras] -dev = ["black (>=19.3b0) ; python_version >= \"3.6\"", "pytest (>=4.6.2)"] +dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"] [[package]] name = "wsproto" @@ -3368,8 +3313,8 @@ files = [ [package.extras] build = ["build", "hatchling", "pip", "setuptools (>=71.0.2)", "wheel"] -curl-cffi = ["curl-cffi (==0.5.10) ; os_name == \"nt\" and implementation_name == \"cpython\"", "curl-cffi (>=0.5.10,!=0.6.*,<0.7.2) ; os_name != \"nt\" and implementation_name == \"cpython\""] -default = ["brotli ; implementation_name == \"cpython\"", "brotlicffi ; implementation_name != \"cpython\"", "certifi", "mutagen", "pycryptodomex", "requests (>=2.32.2,<3)", "urllib3 (>=1.26.17,<3)", "websockets (>=13.0)"] +curl-cffi = ["curl-cffi (==0.5.10)", "curl-cffi (>=0.5.10,!=0.6.*,<0.7.2)"] +default = ["brotli", "brotlicffi", "certifi", "mutagen", "pycryptodomex", "requests (>=2.32.2,<3)", "urllib3 (>=1.26.17,<3)", "websockets (>=13.0)"] dev = ["autopep8 (>=2.0,<3.0)", "pre-commit", "pytest (>=8.1,<9.0)", "pytest-rerunfailures (>=14.0,<15.0)", "ruff (>=0.11.0,<0.12.0)"] pyinstaller = ["pyinstaller (>=6.11.1)"] secretstorage = ["cffi", "secretstorage"] From f99dcc63a1b64c9423202cda40f6d8b42eec83c5 Mon Sep 17 00:00:00 2001 From: erinhmclark Date: Fri, 28 Mar 2025 09:46:44 +0000 Subject: [PATCH 4/7] Minor updates --- docs/source/how_to/run_instagrapi_server.md | 2 +- scripts/instagrapi_server/.gitignore | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/source/how_to/run_instagrapi_server.md b/docs/source/how_to/run_instagrapi_server.md index c7b8928..4eb588e 100644 --- a/docs/source/how_to/run_instagrapi_server.md +++ b/docs/source/how_to/run_instagrapi_server.md @@ -61,7 +61,7 @@ This will be reused automatically by the server script, and can also be passed t The server should now be running within that session, and accessible at http://127.0.0.1:8000 -You can set this in the orchestration.yaml file like this: +You can set this in the Auto Archiver orchestration.yaml file like this: ```yaml instagram_api_extractor: api_endpoint: http://127.0.0.1:8000 diff --git a/scripts/instagrapi_server/.gitignore b/scripts/instagrapi_server/.gitignore index e69de29..33bcfed 100644 --- a/scripts/instagrapi_server/.gitignore +++ b/scripts/instagrapi_server/.gitignore @@ -0,0 +1,2 @@ +secrets* +*instagrapi_session.json From f715100dd52ae3afa4c8c1c6c1f78700b5f50bd1 Mon Sep 17 00:00:00 2001 From: erinhmclark Date: Fri, 28 Mar 2025 11:31:23 +0000 Subject: [PATCH 5/7] Add run_instagrapi_server.sh and update docs --- docs/source/how_to/run_instagrapi_server.md | 64 ++++++++++++++----- .../run_instagrapi_server.sh | 49 ++++++++++++++ 2 files changed, 96 insertions(+), 17 deletions(-) create mode 100755 scripts/instagrapi_server/run_instagrapi_server.sh diff --git a/docs/source/how_to/run_instagrapi_server.md b/docs/source/how_to/run_instagrapi_server.md index 4eb588e..ace1550 100644 --- a/docs/source/how_to/run_instagrapi_server.md +++ b/docs/source/how_to/run_instagrapi_server.md @@ -3,13 +3,37 @@ The instagram API Extractor requires access to a running instance of the InstagrAPI server. We have a lightweight script with the endpoints required for our Instagram API Extractor module which you can run locally, or via Docker. -To run this you need to install some additional requirements. -## Setup - -Although there is an option to run the server in a Docker container, the authentication is usually rejected without an additional session file, which can be created by running the server locally first. ⚠️ Warning: Remember that it's best not to use your own personal account for archiving. [Here's why](../installation/authentication.md#recommendations-for-authentication). +## Quick Start: Using Docker + +We've provided a convenient shell script (`run_instagrapi_server.sh`) that simplifies the process of setting up and running the Instagrapi server in Docker. This script handles building the Docker image, setting up credentials, and starting the container. + +### 🔧 Running the script: + +Run this script either from the repository root or from within the `scripts/instagrapi_server` directory: + +```bash +./scripts/instagrapi_server/run_instagrapi_server.sh +``` + +This script will: +- Prompt for your Instagram username and password. +- Create the necessary `.env` file. +- Build the Docker image. +- Start the Docker container and authenticate with Instagram, creating a session automatically. + +### ⏱ To run the server again later: +```bash +docker start ig-instasrv +``` + +### 🐛 Debugging: +View logs: +```bash +docker logs ig-instasrv +``` ### Overview: How the Setup Works @@ -20,12 +44,10 @@ Although there is an option to run the server in a Docker container, the authent --- -## 1. One-Time Local Setup +## Optional: Manual / Local Setup -This generates a session file using your login details so Instagram recognises your login as authentic. -This will be reused automatically by the server script, and can also be passed to the Docker container. +If you'd prefer to run the server manually (without Docker), you can follow these steps: -### 🔧 Step-by-step: 1. **Navigate to the server folder (and stay there for the rest of this guide)**: ```bash @@ -59,6 +81,17 @@ This will be reused automatically by the server script, and can also be passed t Login successful, session saved. ``` +✅ Your session is now saved to `secrets/instagrapi_session.json`. + +### To run it again locally: +```bash +poetry run uvicorn src.instaserver:app --port 8000 +``` + +--- + +## Adding the API Endpoint to Auto Archiver + The server should now be running within that session, and accessible at http://127.0.0.1:8000 You can set this in the Auto Archiver orchestration.yaml file like this: @@ -67,10 +100,6 @@ instagram_api_extractor: api_endpoint: http://127.0.0.1:8000 ``` -Or to run it in a Docker container, you can pass the session file to it now. -**Stop the server** (`Ctrl+C`). - -📅 Your session has now been saved to `secrets/instagrapi_session.json`. --- @@ -85,11 +114,11 @@ poetry run uvicorn src.instgrapinstance.instaserver:app --port 8000 --- -## 3. Running via Docker (After Setup to create the session file) +## 3. Running via Docker (After Setup is Complete, either locally or via the script) -Once the session file exists, you can pass this to docker Docker and it should authenticate successfully. +Once the `instagrapi_session.json` and `.env` files are set up, you can pass them Docker and it should authenticate successfully. -### 🔨 Build the Docker image: +### 🔨 Build the Docker image manually: ```bash docker build -t instagrapi-server . ``` @@ -104,7 +133,9 @@ docker run -d \ instagrapi-server ``` -This passes the /secrets/ directory to docker, which should contain the session file created from the first local run. +This passes the /secrets/ directory to docker as well as the environment variables from the `.env` file. + + --- @@ -134,6 +165,5 @@ docker start ig-instasrv ## Notes -- You only need to run the server **locally once** to generate a session. - Never share your `.env` or `instagrapi_session.json` — these contain sensitive login data. - If you want to reset your session, simply delete the `secrets/instagrapi_session.json` file and re-run the local server. diff --git a/scripts/instagrapi_server/run_instagrapi_server.sh b/scripts/instagrapi_server/run_instagrapi_server.sh new file mode 100755 index 0000000..9f19c1f --- /dev/null +++ b/scripts/instagrapi_server/run_instagrapi_server.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +# +# run_instagrapi_server.sh +# Usage: +# From repo root: ./scripts/instagrapi_server/run_instagrapi_server.sh +# From repo root: ./scripts/instagrapi_server/run_instagrapi_server.sh +# Or from script dir: ./run_instagrapi_server.sh +# + +set -e + +# Step 1: cd to the script's directory (contains Dockerfile and secrets/) +cd "$(dirname "$0")" || exit 1 + +# Create secrets/ if it doesn't exist +if [[ ! -d "secrets" ]]; then + echo "Creating secrets/ directory..." + mkdir secrets +fi + +echo "Enter your Instagram credentials to store in secrets/.env" +read -rp "Instagram Username: " IGUSER +read -rsp "Instagram Password: " IGPASS +echo "" + +cat < secrets/.env +INSTAGRAM_USERNAME=$IGUSER +INSTAGRAM_PASSWORD=$IGPASS +EOF +echo "Created secrets/.env with your credentials." + +# Build Docker image +IMAGE_NAME="instagrapi-server" +echo "Building Docker image '$IMAGE_NAME'..." +docker build -t "$IMAGE_NAME" . + +# Run container +CONTAINER_NAME="ig-instasrv" +echo "Running container '$CONTAINER_NAME'..." +docker run -d \ + --env-file secrets/.env \ + -v "$(pwd)/secrets:/app/secrets" \ + -p 8000:8000 \ + --name "$CONTAINER_NAME" \ + "$IMAGE_NAME" + +echo "Done! Instagrapi server is running on port 8000." +echo "Use 'docker logs $CONTAINER_NAME' to view logs." +echo "Use 'docker stop $CONTAINER_NAME' and 'docker rm $CONTAINER_NAME' to stop/remove the container." From b1a8792f9f4fa5ad31c02999b7bba91afdde9d16 Mon Sep 17 00:00:00 2001 From: erinhmclark Date: Fri, 28 Mar 2025 11:44:37 +0000 Subject: [PATCH 6/7] Remove duplicate line --- scripts/instagrapi_server/run_instagrapi_server.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/instagrapi_server/run_instagrapi_server.sh b/scripts/instagrapi_server/run_instagrapi_server.sh index 9f19c1f..752c743 100755 --- a/scripts/instagrapi_server/run_instagrapi_server.sh +++ b/scripts/instagrapi_server/run_instagrapi_server.sh @@ -3,7 +3,6 @@ # run_instagrapi_server.sh # Usage: # From repo root: ./scripts/instagrapi_server/run_instagrapi_server.sh -# From repo root: ./scripts/instagrapi_server/run_instagrapi_server.sh # Or from script dir: ./run_instagrapi_server.sh # From dd7d85b4b4262041d943a3301b7de7cde0231d0d Mon Sep 17 00:00:00 2001 From: erinhmclark Date: Fri, 28 Mar 2025 13:47:18 +0000 Subject: [PATCH 7/7] Lock --- poetry.lock | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/poetry.lock b/poetry.lock index 5a83804..3fbf57c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. [[package]] name = "accessible-pygments" @@ -51,7 +51,7 @@ typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} [package.extras] doc = ["Sphinx (>=8.2,<9.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"] -test = ["anyio[trio]", "blockbuster (>=1.5.23)", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"] +test = ["anyio[trio]", "blockbuster (>=1.5.23)", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1) ; python_version >= \"3.10\"", "uvloop (>=0.21) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\" and python_version < \"3.14\""] trio = ["trio (>=0.26.1)"] [[package]] @@ -82,12 +82,12 @@ files = [ ] [package.extras] -benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +benchmark = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +cov = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier"] -tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] +tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\""] [[package]] name = "authlib" @@ -133,7 +133,7 @@ files = [ ] [package.extras] -dev = ["backports.zoneinfo", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata"] +dev = ["backports.zoneinfo ; python_version < \"3.9\"", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata ; sys_platform == \"win32\""] [[package]] name = "beautifulsoup4" @@ -645,10 +645,10 @@ files = [ cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} [package.extras] -docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=3.0.0)"] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=3.0.0) ; python_version >= \"3.8\""] docstest = ["pyenchant (>=3)", "readme-renderer (>=30.0)", "sphinxcontrib-spelling (>=7.3.1)"] -nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2)"] -pep8test = ["check-sdist", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"] +nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2) ; python_version >= \"3.8\""] +pep8test = ["check-sdist ; python_version >= \"3.8\"", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"] sdist = ["build (>=1.0.0)"] ssh = ["bcrypt (>=3.1.5)"] test = ["certifi (>=2024)", "cryptography-vectors (==44.0.2)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] @@ -766,7 +766,7 @@ files = [ [package.extras] docs = ["furo (>=2024.8.6)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] testing = ["covdefaults (>=2.3)", "coverage (>=7.6.10)", "diff-cover (>=9.2.1)", "pytest (>=8.3.4)", "pytest-asyncio (>=0.25.2)", "pytest-cov (>=6)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.28.1)"] -typing = ["typing-extensions (>=4.12.2)"] +typing = ["typing-extensions (>=4.12.2) ; python_version < \"3.11\""] [[package]] name = "future" @@ -801,7 +801,7 @@ requests = ">=2.18.0,<3.0.0" [package.extras] async-rest = ["google-auth[aiohttp] (>=2.35.0,<3.0.dev0)"] -grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio (>=1.49.1,<2.0dev)", "grpcio-status (>=1.33.2,<2.0.dev0)", "grpcio-status (>=1.49.1,<2.0.dev0)"] +grpc = ["grpcio (>=1.33.2,<2.0dev)", "grpcio (>=1.49.1,<2.0dev) ; python_version >= \"3.11\"", "grpcio-status (>=1.33.2,<2.0.dev0)", "grpcio-status (>=1.49.1,<2.0.dev0) ; python_version >= \"3.11\""] grpcgcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] @@ -1100,7 +1100,7 @@ colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""} win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""} [package.extras] -dev = ["Sphinx (==8.1.3)", "build (==1.2.2)", "colorama (==0.4.5)", "colorama (==0.4.6)", "exceptiongroup (==1.1.3)", "freezegun (==1.1.0)", "freezegun (==1.5.0)", "mypy (==v0.910)", "mypy (==v0.971)", "mypy (==v1.13.0)", "mypy (==v1.4.1)", "myst-parser (==4.0.0)", "pre-commit (==4.0.1)", "pytest (==6.1.2)", "pytest (==8.3.2)", "pytest-cov (==2.12.1)", "pytest-cov (==5.0.0)", "pytest-cov (==6.0.0)", "pytest-mypy-plugins (==1.9.3)", "pytest-mypy-plugins (==3.1.0)", "sphinx-rtd-theme (==3.0.2)", "tox (==3.27.1)", "tox (==4.23.2)", "twine (==6.0.1)"] +dev = ["Sphinx (==8.1.3) ; python_version >= \"3.11\"", "build (==1.2.2) ; python_version >= \"3.11\"", "colorama (==0.4.5) ; python_version < \"3.8\"", "colorama (==0.4.6) ; python_version >= \"3.8\"", "exceptiongroup (==1.1.3) ; python_version >= \"3.7\" and python_version < \"3.11\"", "freezegun (==1.1.0) ; python_version < \"3.8\"", "freezegun (==1.5.0) ; python_version >= \"3.8\"", "mypy (==v0.910) ; python_version < \"3.6\"", "mypy (==v0.971) ; python_version == \"3.6\"", "mypy (==v1.13.0) ; python_version >= \"3.8\"", "mypy (==v1.4.1) ; python_version == \"3.7\"", "myst-parser (==4.0.0) ; python_version >= \"3.11\"", "pre-commit (==4.0.1) ; python_version >= \"3.9\"", "pytest (==6.1.2) ; python_version < \"3.8\"", "pytest (==8.3.2) ; python_version >= \"3.8\"", "pytest-cov (==2.12.1) ; python_version < \"3.8\"", "pytest-cov (==5.0.0) ; python_version == \"3.8\"", "pytest-cov (==6.0.0) ; python_version >= \"3.9\"", "pytest-mypy-plugins (==1.9.3) ; python_version >= \"3.6\" and python_version < \"3.8\"", "pytest-mypy-plugins (==3.1.0) ; python_version >= \"3.8\"", "sphinx-rtd-theme (==3.0.2) ; python_version >= \"3.11\"", "tox (==3.27.1) ; python_version < \"3.8\"", "tox (==4.23.2) ; python_version >= \"3.8\"", "twine (==6.0.1) ; python_version >= \"3.11\""] [[package]] name = "markdown-it-py" @@ -1564,7 +1564,7 @@ docs = ["furo", "olefile", "sphinx (>=8.1)", "sphinx-copybutton", "sphinx-inline fpx = ["olefile"] mic = ["olefile"] tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout", "trove-classifiers (>=2024.10.12)"] -typing = ["typing-extensions"] +typing = ["typing-extensions ; python_version < \"3.10\""] xmp = ["defusedxml"] [[package]] @@ -2976,7 +2976,7 @@ files = [ pysocks = {version = ">=1.5.6,<1.5.7 || >1.5.7,<2.0", optional = true, markers = "extra == \"socks\""} [package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] @@ -2999,7 +2999,7 @@ h11 = ">=0.8" typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} [package.extras] -standard = ["colorama (>=0.4)", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] +standard = ["colorama (>=0.4) ; sys_platform == \"win32\"", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1) ; sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\"", "watchfiles (>=0.13)", "websockets (>=10.4)"] [[package]] name = "virtualenv" @@ -3020,7 +3020,7 @@ platformdirs = ">=3.9.1,<5" [package.extras] docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] [[package]] name = "vk-api" @@ -3282,7 +3282,7 @@ files = [ ] [package.extras] -dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"] +dev = ["black (>=19.3b0) ; python_version >= \"3.6\"", "pytest (>=4.6.2)"] [[package]] name = "wsproto" @@ -3313,8 +3313,8 @@ files = [ [package.extras] build = ["build", "hatchling", "pip", "setuptools (>=71.0.2)", "wheel"] -curl-cffi = ["curl-cffi (==0.5.10)", "curl-cffi (>=0.5.10,!=0.6.*,<0.7.2)"] -default = ["brotli", "brotlicffi", "certifi", "mutagen", "pycryptodomex", "requests (>=2.32.2,<3)", "urllib3 (>=1.26.17,<3)", "websockets (>=13.0)"] +curl-cffi = ["curl-cffi (==0.5.10) ; os_name == \"nt\" and implementation_name == \"cpython\"", "curl-cffi (>=0.5.10,!=0.6.*,<0.7.2) ; os_name != \"nt\" and implementation_name == \"cpython\""] +default = ["brotli ; implementation_name == \"cpython\"", "brotlicffi ; implementation_name != \"cpython\"", "certifi", "mutagen", "pycryptodomex", "requests (>=2.32.2,<3)", "urllib3 (>=1.26.17,<3)", "websockets (>=13.0)"] dev = ["autopep8 (>=2.0,<3.0)", "pre-commit", "pytest (>=8.1,<9.0)", "pytest-rerunfailures (>=14.0,<15.0)", "ruff (>=0.11.0,<0.12.0)"] pyinstaller = ["pyinstaller (>=6.11.1)"] secretstorage = ["cffi", "secretstorage"]