diff --git a/.claude/.gitignore b/.claude/.gitignore new file mode 100644 index 000000000..3a2f7f6a1 --- /dev/null +++ b/.claude/.gitignore @@ -0,0 +1,4 @@ +CLAUDE.local.md +settings.local.json +worktrees/ +plans/ diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md new file mode 100644 index 000000000..dba71e970 --- /dev/null +++ b/.claude/CLAUDE.md @@ -0,0 +1 @@ +@../AGENTS.md diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 000000000..705fd286c --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,34 @@ +{ + "permissions": { + "allow": [ + "Bash(./scripts/build_pypi_package.sh:*)", + "Bash(./scripts/format.sh:*)", + "Bash(./scripts/install_all_and_run_tests.sh:*)", + "Bash(./scripts/lint.sh:*)", + "Bash(./scripts/run_mypy.sh:*)", + "Bash(./scripts/run_tests.sh:*)", + "Bash(./scripts/install.sh:*)", + "Bash(echo $VIRTUAL_ENV)", + "Bash(gh issue view:*)", + "Bash(gh label list:*)", + "Bash(gh pr checks:*)", + "Bash(gh pr diff:*)", + "Bash(gh pr list:*)", + "Bash(gh pr status:*)", + "Bash(gh pr update-branch:*)", + "Bash(gh pr view:*)", + "Bash(gh search code:*)", + "Bash(git diff:*)", + "Bash(git grep:*)", + "Bash(git log:*)", + "Bash(git show:*)", + "Bash(git status:*)", + "Bash(grep:*)", + "Bash(ls:*)", + "Bash(tree:*)", + "WebFetch(domain:github.com)", + "WebFetch(domain:docs.slack.dev)", + "WebFetch(domain:raw.githubusercontent.com)" + ] + } +} diff --git a/.claude/skills/managing-dependencies/SKILL.md b/.claude/skills/managing-dependencies/SKILL.md new file mode 100644 index 000000000..8f67fccd8 --- /dev/null +++ b/.claude/skills/managing-dependencies/SKILL.md @@ -0,0 +1,127 @@ +--- +name: managing-dependencies +description: >- + Use when adding, updating, pinning, or reviewing any dependency in this repo's requirements/*.txt files, including taking a Dependabot bump or writing a requirement line by hand. Also use when a pip install fails on the older Python jobs while newer ones pass, or when CI errors with "Could not find a version that satisfies", "No matching distribution found", "Requires-Python >=3.x", or "ResolutionImpossible". Triggers include: "add to requirements", "bump/update ", "pin ", "fix this dependabot PR", "CI can't install on Python 3.7/3.8/3.9", "requires-python error in the install step". This skill defines the layout and version-constraint conventions to follow; reach for it before hand-editing any requirements/*.txt file. +--- + +# Managing dependencies + +## Overview + +Every `requirements/*.txt` file in this repo follows one layout convention and one version-constraint pattern. This keeps a single set of pins working across the whole Python matrix and every diff readable. The mechanism for making one line behave differently per interpreter is [PEP 508](https://peps.python.org/pep-0508/) environment markers. That spec is the authority for the marker syntax used throughout this skill. + +Two facts about this repo drive everything below: + +- It supports **Python `>=3.7`** (`requires-python` and the classifiers in `pyproject.toml`). Its CI matrix in `.github/workflows/ci-build.yml` tests **CPython 3.7 through 3.14 only** — there is **no PyPy** (the classifiers list only `Programming Language :: Python :: Implementation :: CPython`). +- Many popular packages keep raising their minimum Python. A bump that raises a dependency's **lower bound** to a release requiring a newer Python makes pip's resolver find nothing installable on the old interpreters. The install step then fails there before tests even run. + +The convention resolves this without dropping old-Python support: pin each interpreter to the newest release it can actually install, using PEP 508 `python_version` markers. + +## File-layout convention + +Each file starts with a `# pip install -r requirements/.txt` header, then lists **one dependency per section**: the name of the dependency (as a `# name` header), an optional rationale note (starting with `# Note:`, explaining why a version is pinned or split), then the requirement line(s), separated from the next section by a blank line. This makes every pin self-documenting. + +``` +# pip install -r requirements/test.txt + +# pytest +pytest<9.2 + +# pytest-cov +# Note: only needed to evaluate coverage on the latest supported python version +pytest-cov>=7.1.0,<8; python_version >= "3.14" +``` + +Keep this layout when adding or editing dependencies. Never leave an empty trailing `;` (a fossil of a collapsed split; delete it — the old `pytest-asyncio<2;` line was exactly this). + +## Which files need Python-version markers + +A marker split is only needed for requirements files installed across the **full** Python matrix. Which file you are editing decides this. To see where a file is installed, read `.github/workflows/ci-build.yml`. It is the source of truth for which Python versions install which requirements files. Everything except `dev_tools.txt` is installed by the `unittest` matrix job across 3.7–3.14. + +| File | Installed on | Needs markers? | +| ----------------------------- | ----------------------------------------------------- | ----------------------------------- | +| `requirements/adapter_dev.txt` | full matrix (`unittest`; also `typecheck`/`codecov` @3.14) | **Yes, if a bump raises the floor** | +| `requirements/async_dev.txt` | full matrix (`unittest`; also `typecheck`/`codecov` @3.14) | **Yes, if a bump raises the floor** | +| `requirements/test.txt` | full matrix (`unittest`) | **Yes, if a bump raises the floor** | +| `requirements/test_adapter.txt` | full matrix (`unittest`; `codecov` @3.14) | **Yes, if a bump raises the floor** | +| `requirements/test_async.txt` | full matrix (`unittest`) | **Yes, if a bump raises the floor** | +| `requirements/dev_tools.txt` | `lint` + `typecheck`, **latest Python only** (3.14) | No, just take the bump (`==` pins) | + +If the bump lands in a latest-Python-only file, take it as-is: no markers, no ceiling, just the layout convention above. + +Unlike some sibling repos, bolt-python has **no requirements file that feeds packaged wheel metadata**: `pyproject.toml` has no `[project.optional-dependencies]`, and `[tool.setuptools.dynamic]` resolves only `version` and `readme`. So there is no "special" file whose comments leak into a wheel — every `requirements/*.txt` file is dev/test-only. + +## The version-constraint pattern + +When a dependency's floor rises to a release that requires a newer Python, **do not** just take the bump, and **do not** drop old-Python support to make CI pass. Instead, split the requirement into `python_version`-marked lines that **partition the whole matrix**. Every interpreter matches exactly one line. Old interpreters keep the last compatible release (with an explicit ceiling), and the newest line is open-ended so future Pythons stay covered. + +``` +# aiohttp +aiohttp>=3,<4; python_version < "3.9" +aiohttp>=3.13.5,<4; python_version >= "3.9" +``` + +`aiohttp` 3.13.5 requires Python `>=3.9`, so Python 3.7/3.8 stay on the older line. The same 3.9 split appears for `falcon`, `fastapi`, `Flask`, `Werkzeug`, `starlette`, `tornado`, `websocket_client`, and (in `test_async.txt`) `asgiref`. The `Django` split lands at 3.8 instead — Django 4.x requires `>=3.8`, so 3.7 keeps the 3.2 line: + +``` +# Django +Django>=3.2,<4; python_version < "3.8" +Django>=4.2.30,<6; python_version >= "3.8" +``` + +## Canonical marker style + +Consistency matters because these lines are read and edited often, and a stray style makes diffs noisy. Standardize on this: + +- Spaces around every operator in the marker: `python_version >= "3.9"`, never `python_version>="3.9"`. +- Double-quoted `major.minor` string: `"3.9"`. (`packaging` compares these version-aware, so `python_version >= "3.9"` correctly includes 3.10–3.14, no lexicographic surprise.) +- Use `>=` / `<` for the Python boundary; avoid `>` / `<=` so the boundary version lands on exactly one side. **This rule is about the `python_version` marker, not the version specifier** — `boto3<=2` and `cheroot<12` are correct as written. +- One space after the `;`, none before: `pkg>=1,<2; python_version >= "3.9"`. +- The old-side line always carries an explicit upper bound (the floor-jump version). +- The marker set must be **exhaustive and mutually exclusive** across the matrix. The newest line ends open-ended (`>= "X.Y"`), never a bare `==` that leaves future Pythons unmatched. + +## Deriving the versions to pin + +You need two numbers: the **floor** (which Python the new release requires) and the old-side **ceiling** (the first release that raised that floor). + +1. **Floor.** Read the metadata for the _exact target version_ at `https://pypi.org/pypi///json`. The `info.requires_python` field gives the new minimum (e.g. `">=3.9"`). A `null` there means the release declares no floor. + +2. **Ceiling.** Walk the release history at `https://pypi.org/pypi//json` and find the **first version that raised the floor above the oldest matrix Python**. The old-side ceiling is `< `. For example, if a package jumped to `>=3.9` at version **4.0.0**, the old-side cap is `<4` even if the target is `4.2.0` (pinning `<4.2.0` would wrongly admit 4.0.0–4.1.x, which are also 3.9-only). + +Why an explicit ceiling instead of trusting pip to filter by `Requires-Python`? Because that filtering only holds if every future release keeps its metadata correct; a single mis-tagged release would silently float onto an untested interpreter. An explicit ceiling makes the intent self-documenting and robust. + +`tracerite` is the cautionary case: its releases after 1.1.2 break on Python `<= 3.8`, yet those releases publish **no `requires_python` metadata at all** (verify: `https://pypi.org/pypi/tracerite/1.1.3/json` shows `requires_python: null`). So pip filtering offers zero protection on the old interpreters, and the explicit `tracerite<1.1.2` ceiling is load-bearing, not decorative. + +**If you arrived here from a red CI job:** the failing _install_ log is ground truth. It names the interpreter that failed and the versions pip was actually offered, e.g.: + +``` +ERROR: Ignored the following versions that require a different python version: 4.2.0 Requires-Python >=3.9 +ERROR: Could not find a version that satisfies the requirement falcon>=4.2.0 (from versions: ..., 3.1.3) +``` + +Cross-check the PyPI value against that log so you are never guessing. (If instead the failure is a real test failure, or hits _every_ Python version, this pattern does not apply, so investigate the bump normally.) + +## One harder shape: a coupled companion dependency + +Sometimes a package drags in another distribution with loose or wildcard versions that you must co-pin on the same boundary. `Sanic` imports `tracerite` with wildcard versions, so `tracerite` is pinned right beside it: + +``` +# sanic +# Note: Sanic pulls in tracerite via a wildcard version, so tracerite is co-pinned here. +# Note: tracerite > 1.1.2 is incompatible with Python <= 3.8 and ships no requires_python, so an explicit ceiling is required. +tracerite<1.1.2; python_version < "3.9" +sanic>=21,<24; python_version < "3.9" +sanic>=25.3.0,<26; python_version >= "3.9" +``` + +The split boundary here (3.9) is driven by `tracerite`, **not** by Sanic itself — Sanic 25.3 supports Python 3.8 (`requires_python: >=3.8`). Python 3.8 is kept on old Sanic only because tracerite breaks there. When a companion transitive dependency is the thing that breaks, pin it explicitly rather than hoping the parent's resolver picks a compatible version, and keep it in the same section so the coupling stays visible. + +## Collapse when a Python is dropped + +Marker splits are maintenance cost, so remove them when they stop earning their keep. When a Python version is dropped from the CI matrix (and from `requires-python` / the classifiers), collapse any split whose only reason was that version back into a single unmarked line, and delete the trailing `;`. For example, if 3.7 is dropped, the `Django>=3.2,<4; python_version < "3.8"` line has no interpreter left to serve, and the section collapses to a single `Django` line. A leaner file is easier for both humans and Dependabot to reason about. + +## What to leave alone + +- **Do not touch `requires-python` or the CI matrix.** Keeping 3.7 working on old dependency versions is the entire point; changing the floor is a separate, deliberate decision. +- **Do not add runtime dependencies to `pyproject.toml`.** The core package depends only on `slack_sdk` (see the "Single Runtime Dependency Rule" in `AGENTS.md`); everything else belongs in `requirements/*.txt`. +- **Prefer markers over a Dependabot `ignore`.** An `ignore` rule freezes newer Pythons on the old version too, and hides the version knowledge in config. Reserve `ignore` for the rare dep that must stay pinned everywhere for reproducible output. diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..4a08579c2 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,12 @@ +# Salesforce Open Source project configuration +# Learn more: https://github.com/salesforce/oss-template +#ECCN:Open Source +#GUSINFO:Open Source,Open Source Workflow + +# @slackapi/slack-platform-python +# are code reviewers for all changes in this repo. +* @slackapi/slack-platform-python + +# @slackapi/developer-education +# are code reviewers for changes in the `/docs` directory. +/docs/ @slackapi/developer-education diff --git a/.github/ISSUE_TEMPLATE/03_document.md b/.github/ISSUE_TEMPLATE/03_document.md index 3ce4e48e5..4eb5af847 100644 --- a/.github/ISSUE_TEMPLATE/03_document.md +++ b/.github/ISSUE_TEMPLATE/03_document.md @@ -10,7 +10,7 @@ assignees: '' ### The page URLs -* https://slack.dev/bolt-python/ +* https://docs.slack.dev/tools/bolt-python/ ## Requirements diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 8cc5d1809..774d13833 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -8,15 +8,11 @@ updates: schedule: interval: "monthly" open-pull-requests-limit: 5 + ignore: + # setuptools is pinned due to pyramid's dependency on deprecated pkg_resources + # See: https://github.com/Pylons/pyramid/issues/3731 + - dependency-name: "setuptools" - package-ecosystem: "github-actions" directory: "/" schedule: interval: "monthly" - - package-ecosystem: "npm" - directory: "/docs" - schedule: - interval: "monthly" - groups: - docusaurus: - patterns: - - "@docusaurus/*" diff --git a/.github/maintainers_guide.md b/.github/maintainers_guide.md index 333ffa354..47d9ccd58 100644 --- a/.github/maintainers_guide.md +++ b/.github/maintainers_guide.md @@ -10,14 +10,14 @@ this project. If you use this package within your own software as is but don't p We recommend using [pyenv](https://github.com/pyenv/pyenv) for Python runtime management. If you use macOS, follow the following steps: -```bash -$ brew update -$ brew install pyenv +```sh +brew update +brew install pyenv ``` Install necessary Python runtimes for development/testing. You can rely on GitHub Actions workflows for testing with various major versions. -```bash +```sh $ pyenv install -l | grep -v "-e[conda|stackless|pypy]" $ pyenv install 3.8.5 # select the latest patch version @@ -25,8 +25,8 @@ $ pyenv local 3.8.5 $ pyenv versions system - 3.6.10 - 3.7.7 + 3.7.17 + 3.13.7 * 3.8.5 (set by /path-to-bolt-python/.python-version) $ pyenv rehash @@ -34,9 +34,9 @@ $ pyenv rehash Then, you can create a new Virtual Environment this way: -```bash -$ python -m venv env_3.8.5 -$ source env_3.8.5/bin/activate +```sh +python -m venv env_3.8.5 +source env_3.8.5/bin/activate ``` ## Tasks @@ -49,30 +49,30 @@ If you make some changes to this SDK, please write corresponding unit tests as m If this is your first time to run tests, although it may take a bit long time, running the following script is the easiest. -```bash -$ ./scripts/install_all_and_run_tests.sh +```sh +./scripts/install_all_and_run_tests.sh ``` Once you installed all the required dependencies, you can use the following one. -```bash -$ ./scripts/run_tests.sh +```sh +./scripts/run_tests.sh ``` Also, you can run a single test this way. -```bash -$ ./scripts/run_tests.sh tests/scenario_tests/test_app.py +```sh +./scripts/run_tests.sh tests/scenario_tests/test_app.py ``` #### Run the Samples If you make changes to `slack_bolt/adapter/*`, please verify if it surely works by running the apps under `examples` directory. -```bash +```sh # Install all optional dependencies -$ pip install -r requirements/adapter.txt -$ pip install -r requirements/adapter_testing.txt +$ pip install -r requirements/adapter_dev.txt +$ pip install -r requirements/test_adapter.txt # Set required env variables $ export SLACK_SIGNING_SECRET=*** @@ -97,119 +97,127 @@ If you want to test the package locally you can. 1. Build the package locally - Run - ```bash + ```sh scripts/build_pypi_package.sh ``` - This will create a `.whl` file in the `./dist` folder 2. Use the built package - Example `/dist/slack_bolt-1.2.3-py2.py3-none-any.whl` was created - - From anywhere on your machine you can install this package to a project with - ```bash + - From anywhere on your machine you can install this package to a project with + ```sh pip install /dist/slack_bolt-1.2.3-py2.py3-none-any.whl ``` - - It is also possible to include `/dist/slack_bolt-1.2.3-py2.py3-none-any.whl` in a [requirements.txt](https://pip.pypa.io/en/stable/user_guide/#requirements-files) file + - It is also possible to include `slack_bolt @ file:////dist/slack_bolt-1.2.3-py2.py3-none-any.whl` in a [requirements.txt](https://pip.pypa.io/en/stable/user_guide/#requirements-files) file -### Releasing - -#### Generate API reference documents +### Generate API reference documents -```bash +```sh ./scripts/generate_api_docs.sh ``` -#### test.pypi.org deployment - -##### $HOME/.pypirc - -```toml -[testpypi] -username: {your username} -password: {your password} -``` - -#### Development Deployment - -1. Create a branch in which the development release will live: - - Bump the version number in adherence to [Semantic Versioning](http://semver.org/) and [Developmental Release](https://peps.python.org/pep-0440/#developmental-releases) in `slack_bolt/version.py` - - Example the current version is `1.2.3` a proper development bump would be `1.3.0.dev0` - - `.dev` will indicate to pip that this is a [Development Release](https://peps.python.org/pep-0440/#developmental-releases) - - Note that the `dev` version can be bumped in development releases: `1.3.0.dev0` -> `1.3.0.dev1` - - Commit with a message including the new version number. For example `1.3.0.dev0` & Push the commit to a branch where the development release will live (create it if it does not exist) - - `git checkout -b future-release` - - `git commit -m 'version 1.3.0.dev0'` - - `git push future-release` - - Create a git tag for the release. For example `git tag v1.3.0.dev0`. - - Push the tag up to github with `git push origin --tags` - -2. Distribute the release - - Use the latest stable Python runtime - - `python -m venv .venv` - - `./scripts/deploy_to_pypi_org.sh` - - You do not need to create a GitHub release - -3. (Slack Internal) Communicate the release internally - -#### Production Deployment +### Releasing -1. Create the commit for the release: - - Bump the version number in adherence to [Semantic Versioning](http://semver.org/) in `slack_bolt/version.py` - - Build the docs with `./scripts/generate_api_docs.sh`. - - Commit with a message including the new version number. For example `1.2.3` & Push the commit to a branch and create a PR to sanity check. - - `git checkout -b v1.2.3` - - `git commit -a -m 'version 1.2.3'` - - Open a PR and merge after receiving at least one approval from other maintainers. +#### test.pypi.org deployment -2. Distribute the release - - Use the latest stable Python runtime - - `python --version` - - `python -m venv .venv` - - `./scripts/deploy_to_pypi_org.sh` - - Create a new GitHub Release from the [Releases page](https://github.com/slackapi/bolt-python/releases) by clicking the "Draft a new release" button. - - Enter the new version number updated from the commit (e.g. `v1.2.3`) into the "Choose a tag" input. - - Ensure the tag `Target` branch is `main` (e.g `Target:main`). - - Click the "Create a new tag: x.x.x on publish" button. This won't create your tag immediately. - - Name the release after the version number updated from the commit (e.g. `version 1.2.3`) - - Auto-generate the release notes by clicking the "Auto-generate release - notes" button. This will pull in changes that will be included in your - release. - - Edit the resulting notes to ensure they have decent messaging that are - understandable by non-contributors, but each commit should still have it's - own line. - - Ensure that this version adheres to [semantic versioning](http://semver.org/). See - [Versioning](#versioning-and-tags) for correct version format. Version tags - should match the following pattern: `v2.5.0`. +[TestPyPI](https://test.pypi.org/) is a separate instance of the Python Package +Index that allows you to try distribution tools and processes without affecting +the real index. This is particularly useful when making changes related to the +package configuration itself, for example, modifications to the `pyproject.toml` file. - ```markdown - ## New Features +You can deploy this project to TestPyPI using GitHub Actions. - ### Awesome Feature 1 +To deploy using GitHub Actions: - Description here. +1. Push your changes to a branch or tag +2. Navigate to +3. Click on "Run workflow" +4. Select your branch or tag from the dropdown +5. Click "Run workflow" to build and deploy your branch to TestPyPI - ### Awesome Feature 2 +Alternatively, you can deploy from your local machine with: - Description here. +```sh +./scripts/deploy_to_test_pypi.sh +``` - ## Changes +#### Development Deployment - * #123 Make it better - thanks @SlackHQ - * #123 Fix something wrong - thanks @seratch - ``` +Deploying a new version of this library to PyPI is triggered by publishing a GitHub Release. +Before creating a new release, ensure that everything on a stable branch has +landed, then [run the tests](#run-all-the-unit-tests). -3. (Slack Internal) Communicate the release internally - - Include a link to the GitHub release +1. Create the commit for the release + 1. Use the latest supported Python version. Using a [virtual environment](#python-and-friends) is recommended. + 2. In `slack_bolt/version.py` bump the version number in adherence to [Semantic Versioning](http://semver.org/) and [Developmental Release](https://peps.python.org/pep-0440/#developmental-releases). + - Example: if the current version is `1.2.3`, a proper development bump would be `1.2.4.dev0` + - `.dev` will indicate to pip that this is a [Development Release](https://peps.python.org/pep-0440/#developmental-releases) + - Note that the `dev` version can be bumped in development releases: `1.2.4.dev0` -> `1.2.4.dev1` + 3. Build the docs with `./scripts/generate_api_docs.sh`. + 4. Commit with a message including the new version number. For example `1.2.4.dev0` & push the commit to a branch where the development release will live (create it if it does not exist) + 1. `git checkout -b future-release` + 2. `git add --all` (review files with `git status` before committing) + 3. `git commit -m 'chore(release): version 1.2.4.dev0'` + 4. `git push -u origin future-release` +2. Create a new GitHub Release + 1. Navigate to the [Releases page](https://github.com/slackapi/bolt-python/releases). + 2. Click the "Draft a new release" button. + 3. Set the "Target" to the feature branch with the development changes. + 4. Click "Tag: Select tag" + 5. Input a new tag name manually. The tag name must match the version in `slack_bolt/version.py` prefixed with "v" (e.g., if version is `1.2.4.dev0`, enter `v1.2.4.dev0`) + 6. Click the "Create a new tag" button. This won't create your tag immediately. + 7. Click the "Generate release notes" button. + 8. The release name should match the tag name! + 9. Edit the resulting notes to ensure they have decent messaging that is understandable by non-contributors, but each commit should still have its own line. + 10. Set this release as a pre-release. + 11. Publish the release by clicking the "Publish release" button! +3. Navigate to the [release workflow run](https://github.com/slackapi/bolt-python/actions/workflows/pypi-release.yml). You will need to approve the deployment! +4. After a few minutes, the corresponding version will be available on . +5. (Slack Internal) Communicate the release internally -4. Make announcements - - #tools-bolt in community.slack.com +#### Production Deployment -5. (Slack Internal) Tweet by @SlackAPI - - Not necessary for patch updates, might be needed for minor updates, definitely needed for major updates. Include a link to the GitHub release +Deploying a new version of this library to PyPI is triggered by publishing a GitHub Release. +Before creating a new release, ensure that everything on the `main` branch since +the last tag is in a releasable state! At a minimum, [run the tests](#run-all-the-unit-tests). + +1. Create the commit for the release + 1. Use the latest supported Python version. Using a [virtual environment](#python-and-friends) is recommended. + 2. In `slack_bolt/version.py` bump the version number in adherence to [Semantic Versioning](http://semver.org/) and the [Versioning](#versioning-and-tags) section. + 3. Build the docs with `./scripts/generate_api_docs.sh`. + 4. Commit with a message including the new version number. For example `1.2.3` & push the commit to a branch and create a PR to sanity check. + 1. `git checkout -b 1.2.3-release` + 2. `git add --all` (review files with `git status` before committing) + 3. `git commit -m 'chore(release): version 1.2.3'` + 4. `git push -u origin 1.2.3-release` + 5. Add relevant labels to the PR and add the PR to a GitHub Milestone. + 6. Merge in release PR after getting an approval from at least one maintainer. +2. Create a new GitHub Release + 1. Navigate to the [Releases page](https://github.com/slackapi/bolt-python/releases). + 2. Click the "Draft a new release" button. + 3. Set the "Target" to the `main` branch. + 4. Click "Tag: Select tag" + 5. Input a new tag name manually. The tag name must match the version in `slack_bolt/version.py` prefixed with "v" (e.g., if version is `1.2.3`, enter `v1.2.3`) + 6. Click the "Create a new tag" button. This won't create your tag immediately. + 7. Click the "Generate release notes" button. + 8. The release name should match the tag name! + 9. Edit the resulting notes to ensure they have decent messaging that is understandable by non-contributors, but each commit should still have its own line. + 10. Include a link to the current GitHub Milestone. + 11. Ensure the "latest release" checkbox is checked to mark this as the latest stable release. + 12. Publish the release by clicking the "Publish release" button! +3. Navigate to the [release workflow run](https://github.com/slackapi/bolt-python/actions/workflows/pypi-release.yml). You will need to approve the deployment! +4. After a few minutes, the corresponding version will be available on . +5. Close the current GitHub Milestone and create one for the next patch version. +6. (Slack Internal) Communicate the release internally + - Include a link to the GitHub release +7. (Slack Internal) Tweet by @SlackAPI + - Not necessary for patch updates, might be needed for minor updates, + definitely needed for major updates. Include a link to the GitHub release ## Workflow ### Versioning and Tags -This project uses semantic versioning, expressed through the numbering scheme of +This project uses [Semantic Versioning](http://semver.org/), expressed through the numbering scheme of [PEP-0440](https://www.python.org/dev/peps/pep-0440/). ### Branches diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 000000000..b2574b7cc --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,24 @@ +# https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes#configuring-automatically-generated-release-notes +changelog: + categories: + - title: 🚀 Enhancements + labels: + - enhancement + - title: 🐛 Bug Fixes + labels: + - bug + - title: 📚 Documentation + labels: + - docs + - title: 🤖 Build + labels: + - build + - title: 🧪 Testing/Code Health + labels: + - code health + - title: 🔒 Security + labels: + - security + - title: 📦 Other changes + labels: + - "*" diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml new file mode 100644 index 000000000..dd2119b8c --- /dev/null +++ b/.github/workflows/ci-build.yml @@ -0,0 +1,189 @@ +name: Python CI + +on: + push: + branches: + - main + pull_request: + schedule: + - cron: "0 0 * * *" + workflow_dispatch: + +env: + LATEST_SUPPORTED_PY: "3.14" + +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + contents: read + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Set up Python ${{ env.LATEST_SUPPORTED_PY }} + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: ${{ env.LATEST_SUPPORTED_PY }} + - name: Run lint verification + run: ./scripts/lint.sh + + typecheck: + name: Typecheck + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + contents: read + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Set up Python ${{ env.LATEST_SUPPORTED_PY }} + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: ${{ env.LATEST_SUPPORTED_PY }} + - name: Install synchronous dependencies + run: | + pip install -U pip + pip install -U . + pip install -r requirements/dev_tools.txt + - name: Type check synchronous modules + run: mypy --config-file pyproject.toml --exclude "async_|/adapter/" + - name: Install async and adapter dependencies + run: | + pip install -r requirements/async_dev.txt + pip install -r requirements/adapter_dev.txt + - name: Type check all modules + run: mypy --config-file pyproject.toml + + unittest: + name: Unit tests + runs-on: ubuntu-22.04 + timeout-minutes: 10 + strategy: + fail-fast: false + matrix: + python-version: + - "3.7" + - "3.8" + - "3.9" + - "3.10" + - "3.11" + - "3.12" + - "3.13" + - "3.14" + permissions: + contents: read + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: ${{ matrix.python-version }} + - name: Install synchronous dependencies + run: | + pip install -U pip + pip install . + pip install -r requirements/test.txt + - name: Run tests without aiohttp + run: | + pytest tests/slack_bolt/ --junitxml=reports/test_slack_bolt.xml + pytest tests/scenario_tests/ --junitxml=reports/test_scenario.xml + - name: Install adapter dependencies + run: | + pip install -r requirements/adapter_dev.txt + pip install -r requirements/test_adapter.txt + - name: Run tests for HTTP Mode adapters + run: | + pytest tests/adapter_tests/ \ + --ignore=tests/adapter_tests/socket_mode/ \ + --ignore=tests/adapter_tests/asgi/ \ + --junitxml=reports/test_adapter.xml + - name: Install async dependencies + run: | + pip install -r requirements/async_dev.txt + - name: Run tests for Socket Mode adapters + run: | + # Requires async test dependencies + pytest tests/adapter_tests/socket_mode/ --junitxml=reports/test_adapter_socket_mode.xml + - name: Install all dependencies + run: | + pip install -r requirements/test_async.txt + - name: Run tests for HTTP Mode adapters (ASGI) + run: | + # Requires async test dependencies + pytest tests/adapter_tests/asgi/ --junitxml=reports/test_adapter_asgi.xml + - name: Run tests for HTTP Mode adapters (asyncio-based libraries) + run: | + pytest tests/adapter_tests_async/ --junitxml=reports/test_adapter_async.xml + - name: Run asynchronous tests + run: | + pytest tests/slack_bolt_async/ --junitxml=reports/test_slack_bolt_async.xml + pytest tests/scenario_tests_async/ --junitxml=reports/test_scenario_async.xml + - name: Upload test results to Codecov + if: ${{ !cancelled() }} + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 + with: + directory: ./reports/ + fail_ci_if_error: false + flags: ${{ matrix.python-version }} + report_type: test_results + token: ${{ secrets.CODECOV_TOKEN }} + verbose: true + + codecov: + name: Code Coverage + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: read + env: + BOLT_PYTHON_CODECOV_RUNNING: "1" + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Set up Python ${{ env.LATEST_SUPPORTED_PY }} + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: ${{ env.LATEST_SUPPORTED_PY }} + - name: Install dependencies + run: | + pip install -U pip + pip install . + pip install -r requirements/adapter_dev.txt + pip install -r requirements/test_async.txt + pip install -r requirements/test_adapter.txt + - name: Run all tests for codecov + run: | + pytest --cov=./slack_bolt/ --cov-report=xml + - name: Upload coverage to Codecov + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 + with: + fail_ci_if_error: true + report_type: coverage + token: ${{ secrets.CODECOV_TOKEN }} + verbose: true + + notifications: + name: Regression notifications + runs-on: ubuntu-latest + needs: + - lint + - typecheck + - unittest + if: ${{ !success() && github.ref == 'refs/heads/main' && github.event_name != 'workflow_dispatch' }} + steps: + - name: Send notifications of failing tests + uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0 + with: + errors: true + webhook: ${{ secrets.SLACK_REGRESSION_FAILURES_WEBHOOK_URL }} + webhook-type: webhook-trigger + payload: | + action_url: "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + repository: "${{ github.repository }}" diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml deleted file mode 100644 index 79fd440b2..000000000 --- a/.github/workflows/codecov.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: Run codecov - -on: - push: - branches: [main] - pull_request: - -jobs: - build: - runs-on: ubuntu-latest - timeout-minutes: 10 - strategy: - matrix: - python-version: ["3.13"] - env: - BOLT_PYTHON_CODECOV_RUNNING: "1" - steps: - - uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - pip install -U pip - pip install . - pip install -r requirements/adapter.txt - pip install -r requirements/testing.txt - pip install -r requirements/adapter_testing.txt - - name: Run all tests for codecov - run: | - pytest --cov=./slack_bolt/ --cov-report=xml - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v5 - with: - fail_ci_if_error: true - verbose: true - token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/dependencies.yml b/.github/workflows/dependencies.yml new file mode 100644 index 000000000..aafc2c0af --- /dev/null +++ b/.github/workflows/dependencies.yml @@ -0,0 +1,29 @@ +name: Merge updates to dependencies +on: + pull_request: +jobs: + dependabot: + name: "@dependabot" + if: github.event.pull_request.user.login == 'dependabot[bot]' + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - name: Collect metadata + id: metadata + uses: dependabot/fetch-metadata@25dd0e34f4fe68f24cc83900b1fe3fe149efef98 # v3.1.0 + with: + github-token: "${{ secrets.GITHUB_TOKEN }}" + - name: Approve + if: steps.metadata.outputs.update-type == 'version-update:semver-patch' || steps.metadata.outputs.update-type == 'version-update:semver-minor' + run: gh pr review --approve "$PR_URL" + env: + PR_URL: ${{github.event.pull_request.html_url}} + GH_TOKEN: ${{secrets.GITHUB_TOKEN}} + - name: Automerge + if: steps.metadata.outputs.update-type == 'version-update:semver-patch' || steps.metadata.outputs.update-type == 'version-update:semver-minor' + run: gh pr merge --auto --squash "$PR_URL" + env: + PR_URL: ${{ github.event.pull_request.html_url }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/docs-deploy.yml b/.github/workflows/docs-deploy.yml deleted file mode 100644 index 54523819e..000000000 --- a/.github/workflows/docs-deploy.yml +++ /dev/null @@ -1,62 +0,0 @@ -name: Deploy to GitHub Pages - -on: - pull_request: - branches: - - main - paths: - - 'docs/**' - push: - branches: - - main - paths: - - 'docs/**' - workflow_dispatch: - -jobs: - build: - name: Build Docusaurus - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: npm - cache-dependency-path: docs/package-lock.json - - - name: Install dependencies - run: npm ci - working-directory: ./docs - - - name: Build website - run: npm run build - working-directory: ./docs - - - name: Upload Build Artifact - uses: actions/upload-pages-artifact@v3 - with: - path: ./docs/build - - deploy: - name: Deploy to GitHub Pages - if: github.event_name != 'pull_request' - needs: build - - # Grant GITHUB_TOKEN the permissions required to make a Pages deployment - permissions: - pages: write # to deploy to Pages - id-token: write # verifies deployment is from an appropriate source - - # Deploy to the github-pages environment - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - - runs-on: ubuntu-latest - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 \ No newline at end of file diff --git a/.github/workflows/flake8.yml b/.github/workflows/flake8.yml deleted file mode 100644 index bdc9036f0..000000000 --- a/.github/workflows/flake8.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: Run flake8 validation - -on: - push: - branches: [main] - pull_request: - -jobs: - build: - runs-on: ubuntu-latest - timeout-minutes: 20 - strategy: - matrix: - python-version: ["3.9"] - steps: - - uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - name: Run flake8 verification - run: | - ./scripts/run_flake8.sh diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml deleted file mode 100644 index a592bd8cd..000000000 --- a/.github/workflows/mypy.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: Run mypy validation - -on: - push: - branches: [main] - pull_request: - -jobs: - build: - runs-on: ubuntu-latest - timeout-minutes: 20 - strategy: - matrix: - python-version: ["3.13"] - steps: - - uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - name: Run mypy verification - run: | - ./scripts/run_mypy.sh diff --git a/.github/workflows/pypi-release.yml b/.github/workflows/pypi-release.yml new file mode 100644 index 000000000..2d0f7630a --- /dev/null +++ b/.github/workflows/pypi-release.yml @@ -0,0 +1,87 @@ +name: Upload A Release to pypi.org or test.pypi.org + +on: + release: + types: + - published + workflow_dispatch: + inputs: + dry_run: + description: "Dry run (build only, do not publish)" + required: false + type: boolean + +jobs: + release-build: + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ github.event.release.tag_name || github.ref }} + persist-credentials: false + + - name: Set up Python + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: "3.x" + + - name: Build release distributions + run: | + scripts/build_pypi_package.sh + + - name: Persist dist folder + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: release-dist + path: dist/ + + test-pypi-publish: + runs-on: ubuntu-latest + needs: + - release-build + # Run this job for workflow_dispatch events when dry_run input is not 'true' + # Note: The comparison is against a string value 'true' since GitHub Actions inputs are strings + if: github.event_name == 'workflow_dispatch' && github.event.inputs.dry_run != 'true' + environment: + name: testpypi + permissions: + id-token: write + + steps: + - name: Retrieve dist folder + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: release-dist + path: dist/ + + - name: Publish release distributions to test.pypi.org + # Using OIDC for PyPI publishing (no API tokens needed) + # See: https://docs.github.com/en/actions/how-tos/secure-your-work/security-harden-deployments/oidc-in-pypi + uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2 + with: + repository-url: https://test.pypi.org/legacy/ + + pypi-publish: + runs-on: ubuntu-latest + needs: + - release-build + if: github.event_name == 'release' + environment: + name: pypi + permissions: + id-token: write + + steps: + - name: Retrieve dist folder + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: release-dist + path: dist/ + + - name: Publish release distributions to pypi.org + # Using OIDC for PyPI publishing (no API tokens needed) + # See: https://docs.github.com/en/actions/how-tos/secure-your-work/security-harden-deployments/oidc-in-pypi + uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml deleted file mode 100644 index c0b58b9d7..000000000 --- a/.github/workflows/tests.yml +++ /dev/null @@ -1,83 +0,0 @@ -name: Run all the unit tests - -on: - push: - branches: [main] - pull_request: - -jobs: - build: - # Avoiding -latest due to https://github.com/actions/setup-python/issues/162 - runs-on: ubuntu-20.04 - timeout-minutes: 10 - strategy: - matrix: - python-version: - - "3.6" - - "3.7" - - "3.8" - - "3.9" - - "3.10" - - "3.11" - - "3.12" - - "3.13" - steps: - - uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - name: Install synchronous dependencies - run: | - pip install -U pip - pip install -r requirements.txt - pip install -r requirements/testing_without_asyncio.txt - - name: Run tests without aiohttp - run: | - pytest tests/slack_bolt/ - pytest tests/scenario_tests/ - - name: Install adapter dependencies - run: | - pip install -r requirements/adapter.txt - pip install -r requirements/adapter_testing.txt - - name: Run tests for HTTP Mode adapters (AWS) - run: | - pytest tests/adapter_tests/aws/ - - name: Run tests for HTTP Mode adapters (Bottle) - run: | - pytest tests/adapter_tests/bottle/ - - name: Run tests for HTTP Mode adapters (CherryPy) - run: | - pytest tests/adapter_tests/cherrypy/ - - name: Run tests for HTTP Mode adapters (Django) - run: | - pytest tests/adapter_tests/django/ - - name: Run tests for HTTP Mode adapters (Falcon) - run: | - pytest tests/adapter_tests/falcon/ - - name: Run tests for HTTP Mode adapters (Flask) - run: | - pytest tests/adapter_tests/flask/ - - name: Run tests for HTTP Mode adapters (Pyramid) - run: | - pytest tests/adapter_tests/pyramid/ - - name: Run tests for HTTP Mode adapters (Starlette) - run: | - pytest tests/adapter_tests/starlette/ - - name: Run tests for HTTP Mode adapters (Tornado) - run: | - pytest tests/adapter_tests/tornado/ - - name: Install async dependencies - run: | - pip install -r requirements/async.txt - - name: Run tests for Socket Mode adapters - run: | - # Requires async test dependencies - pytest tests/adapter_tests/socket_mode/ - - name: Run tests for HTTP Mode adapters (asyncio-based libraries) - run: | - pytest tests/adapter_tests_async/ - - name: Run tests for HTTP Mode adapters (ASGI) - run: | - # Requires async test dependencies - pytest tests/adapter_tests/asgi/ diff --git a/.github/workflows/triage-issues.yml b/.github/workflows/triage-issues.yml index 741221516..adad546b8 100644 --- a/.github/workflows/triage-issues.yml +++ b/.github/workflows/triage-issues.yml @@ -4,20 +4,19 @@ name: Close stale issues and PRs -on: +on: workflow_dispatch: schedule: - - cron: '0 0 * * 1' - -permissions: - issues: write - pull-requests: write + - cron: "0 0 * * 1" jobs: stale: runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write steps: - - uses: actions/stale@v9.0.0 + - uses: actions/stale@4391f3da665fdf50b6810c1a66712fb9ba21aa93 # v11.0.0 with: days-before-issue-stale: 30 days-before-issue-close: 10 @@ -30,4 +29,4 @@ jobs: exempt-all-milestones: true remove-stale-when-updated: true enable-statistics: true - operations-per-run: 60 \ No newline at end of file + operations-per-run: 60 diff --git a/.gitignore b/.gitignore index f36c24c5a..2549060e7 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,7 @@ venv/ .coverage cov_* coverage.xml +reports/ # due to using tox and pytest .tox diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..eae86f330 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,248 @@ +# AGENTS.md - bolt-python + +## Project Overview + +Slack Bolt for Python -- a framework for building Slack apps in Python. + +- **Foundation:** Built on top of `slack_sdk` (see `pyproject.toml` constraints). +- **Execution Models:** Supports both synchronous (`App`) and asynchronous (`AsyncApp` using `asyncio`) execution. Async mode requires `aiohttp` as an additional dependency. +- **Framework Adapters:** Features built-in adapters for web frameworks (Flask, FastAPI, Django, Tornado, Pyramid, and many more) and serverless environments (AWS Lambda, Google Cloud Functions). +- **Python Version:** Requires Python 3.7+ as defined in `pyproject.toml`. + +- **Repository**: +- **Documentation**: +- **PyPI**: +- **Current version**: defined in `slack_bolt/version.py` (referenced by `pyproject.toml` via `[tool.setuptools.dynamic]`) + +## Environment Setup + +You can verify the venv is active by checking `echo $VIRTUAL_ENV`. If tools like `black`, `flake8`, `mypy` or `pytest` are not found, ask the user to activate the venv. + +A python virtual environment (`venv`) should be activated before running any commands. + +```bash +# Create a venv (first time only) +python -m venv .venv + +# Activate +source .venv/bin/activate + +# Install all dependencies +./scripts/install.sh +``` + +## Common Commands + +### Pre-submission Checklist + +Before considering any work complete, you MUST run these commands in order and confirm they all pass: + +```bash +./scripts/format.sh --no-install # 1. Format +./scripts/lint.sh --no-install # 2. Lint +./scripts/run_tests.sh # 3. Run relevant tests (see Testing below) +./scripts/run_mypy.sh --no-install # 4. Type check +``` + +To run everything at once (installs deps + formats + lints + tests + typechecks): + +```bash +./scripts/install_all_and_run_tests.sh +``` + +### Testing + +Always use the project scripts instead of calling `pytest` directly: + +```bash +# Run a single test file +./scripts/run_tests.sh tests/scenario_tests/test_app.py + +# Run a single test function +./scripts/run_tests.sh tests/scenario_tests/test_app.py::TestApp::test_name +``` + +### Formatting, Linting, Type Checking + +```bash +# Format -- Black, configured in pyproject.toml +./scripts/format.sh --no-install + +# Lint -- Flake8, configured in .flake8 +./scripts/lint.sh --no-install + +# Type check -- mypy, configured in pyproject.toml +./scripts/run_mypy.sh --no-install +``` + +## Critical Conventions + +### Sync/Async Mirroring Rule + +**When modifying any sync module, you MUST also update the corresponding async module (and vice versa).** This is the most important convention in this codebase. + +Almost every module has both a sync and async variant. Async files use the `async_` prefix alongside their sync counterpart: + +```text +slack_bolt/middleware/custom_middleware.py # sync +slack_bolt/middleware/async_custom_middleware.py # async + +slack_bolt/context/say/say.py # sync +slack_bolt/context/say/async_say.py # async + +slack_bolt/listener/custom_listener.py # sync +slack_bolt/listener/async_listener.py # async +``` + +**Modules that come in sync/async pairs:** + +- `slack_bolt/app/` -- `app.py` / `async_app.py` +- `slack_bolt/middleware/` -- every middleware has an `async_` counterpart +- `slack_bolt/listener/` -- `listener.py` / `async_listener.py`, plus error/completion/start handlers +- `slack_bolt/listener_matcher/` -- `builtins.py` / `async_builtins.py` +- `slack_bolt/context/` -- each subdirectory (e.g., `say/`, `ack/`, `respond/`) has `async_` variants +- `slack_bolt/kwargs_injection/` -- `args.py` / `async_args.py`, `utils.py` / `async_utils.py` + +**Adapters are an exception:** Most adapters are sync-only or async-only depending on the framework. Async-native frameworks (FastAPI, Starlette, Sanic, Tornado, ASGI, Socket Mode) have `async_handler.py`. Sync-only frameworks (Flask, Django, Bottle, CherryPy, Falcon, Pyramid, AWS Lambda, Google Cloud Functions, WSGI) have `handler.py`. + +### Prefer the Middleware Pattern + +Middleware is the project's preferred approach for cross-cutting concerns. Before adding logic to individual listeners or utility functions, consider whether it belongs as a built-in middleware in the framework. + +**When to add built-in middleware:** + +- Cross-cutting concerns that apply to many or all requests (logging, metrics, observability) +- Request validation, transformation, or enrichment +- Authorization extensions beyond the built-in `SingleTeamAuthorization`/`MultiTeamsAuthorization` +- Feature-level request handling (the `Assistant` middleware in `slack_bolt/middleware/assistant/assistant.py` is the canonical example -- it intercepts assistant thread events and dispatches them to registered sub-listeners) + +**How to add built-in middleware:** + +1. Subclass `Middleware` (sync) and implement `process(self, *, req, resp, next)`. Call `next()` to continue the chain. +2. Subclass `AsyncMiddleware` (async) and implement `async_process(self, *, req, resp, next)`. Call `await next()` to continue. +3. Export from `slack_bolt/middleware/__init__.py` (sync) and `slack_bolt/middleware/async_builtins.py` (async). +4. Register the middleware in `App.__init__()` (`slack_bolt/app/app.py`) and `AsyncApp.__init__()` (`slack_bolt/app/async_app.py`) where the default middleware chain is assembled. + +**Canonical example:** `AttachingFunctionToken` (`slack_bolt/middleware/attaching_function_token/`) is a good small middleware to follow -- it has a clean sync/async pair, a focused `process()` method, and is properly exported and registered in the app's middleware chain. + +### Single Runtime Dependency Rule + +The core package depends ONLY on `slack_sdk` (defined in `pyproject.toml`). Never add runtime dependencies to `pyproject.toml`. Additional dependencies go in the appropriate `requirements/*.txt` file. + +## Architecture + +### Request Processing Pipeline + +Incoming requests flow through a middleware chain before reaching listeners: + +1. **SSL Check** -> **Request Verification** (signature) -> **URL Verification** -> **Authorization** (token injection) -> **Ignoring Self Events** -> Custom middleware +2. **Listener Matching** -- `ListenerMatcher` implementations check if a listener should handle the request +3. **Listener Execution** -- listener-specific middleware runs, then `ack()` is called, then the handler executes + +For FaaS environments (`process_before_response=True`), long-running handlers execute as "lazy listeners" in a thread pool after the ack response is returned. + +### Core Abstractions + +- **`App` / `AsyncApp`** (`slack_bolt/app/`) -- Central class. Registers listeners via decorators (`@app.event()`, `@app.action()`, `@app.command()`, `@app.message()`, `@app.view()`, `@app.shortcut()`, `@app.options()`, `@app.function()`). Dispatches incoming requests through middleware to matching listeners. +- **`Middleware`** (`slack_bolt/middleware/`) -- Abstract base with `process(req, resp, next)`. Built-in: authorization, request verification, SSL check, URL verification, assistant, self-event ignoring. +- **`Listener`** (`slack_bolt/listener/`) -- Has matchers, middleware, and an ack/handler function. `CustomListener` is the main implementation. +- **`ListenerMatcher`** (`slack_bolt/listener_matcher/`) -- Determines if a listener handles a given request. Built-in matchers for events, actions, commands, messages (regex), shortcuts, views, options, functions. +- **`BoltContext`** (`slack_bolt/context/`) -- Dict-like object passed to listeners with `client`, `say()`, `ack()`, `respond()`, `complete()`, `fail()`, plus event metadata (`user_id`, `channel_id`, `team_id`, etc.). +- **`BoltRequest` / `BoltResponse`** (`slack_bolt/request/`, `slack_bolt/response/`) -- Request/response wrappers. Request has `mode` of "http" or "socket_mode". + +### Kwargs Injection + +Listeners receive arguments by parameter name. The framework inspects function signatures and injects matching args: `body`, `event`, `action`, `command`, `payload`, `context`, `client`, `ack`, `say`, `respond`, `logger`, `complete`, `fail`, etc. Defined in `slack_bolt/kwargs_injection/args.py`. + +### Adapter System + +Each adapter in `slack_bolt/adapter/` converts between a web framework's request/response types and `BoltRequest`/`BoltResponse`. Adapters exist for: Flask, FastAPI, Django, Starlette, Sanic, Bottle, Tornado, CherryPy, Falcon, Pyramid, AWS Lambda, Google Cloud Functions, Socket Mode, WSGI, ASGI, and more. + +### AI Agents & Assistants + +`Assistant` middleware (`slack_bolt/middleware/assistant/`) handles assistant thread events. + +## Key Development Patterns + +### Adding a Context Utility + +Each context utility lives in its own subdirectory under `slack_bolt/context/`: + +```text +slack_bolt/context/my_util/ + __init__.py + my_util.py # sync implementation + async_my_util.py # async implementation + internals.py # shared logic (optional) +``` + +Then wire it into `BoltContext` (`slack_bolt/context/context.py`) and `AsyncBoltContext` (`slack_bolt/context/async_context.py`). + +### Adding a New Adapter + +1. Create `slack_bolt/adapter//` +2. Add `__init__.py` and `handler.py` (or `async_handler.py` for async frameworks) +3. The handler converts the framework's request to `BoltRequest`, calls `app.dispatch()`, and converts `BoltResponse` back +4. Add the framework to `requirements/adapter_dev.txt` with version constraints +5. Add adapter tests in `tests/adapter_tests/` (sync) or `tests/adapter_tests_async/` (async) + +### Adding a Kwargs-Injectable Argument + +1. Add the new arg to `slack_bolt/kwargs_injection/args.py` and `async_args.py` +2. Update the `Args` class with the new property +3. Populate the arg in the appropriate context or listener setup code + +## Security Considerations + +- **Request Verification:** The built-in `RequestVerification` middleware validates `x-slack-signature` and `x-slack-request-timestamp` on every incoming HTTP request. Never disable this in production. It is automatically skipped for `socket_mode` requests. +- **Tokens & Secrets:** `SLACK_SIGNING_SECRET` and `SLACK_BOT_TOKEN` must come from environment variables. Never hardcode or commit secrets. +- **Authorization Middleware:** `SingleTeamAuthorization` and `MultiTeamsAuthorization` verify tokens and inject an authorized `WebClient` into the context. Do not bypass these. +- **Tests:** Always use mock servers (`tests/mock_web_api_server/`) and dummy values. Never use real tokens in tests. + +## Dependencies + +The core package has a **single required runtime dependency**: `slack_sdk` (defined in `pyproject.toml`). Do not add runtime dependencies. + +**`requirements/` directory structure:** + +- `async_dev.txt` -- async runtime deps (`aiohttp`, `websockets`) +- `adapter_dev.txt` -- all framework adapter deps (Flask, Django, FastAPI, etc.) +- `test_async.txt` -- test runner deps (`pytest`, `pytest-asyncio`, includes `async_dev.txt`) +- `test.txt` -- test deps without async (`pytest`, `pytest-cov`) +- `test_adapter.txt` -- adapter-specific test deps (`moto`, `boddle`, `sanic-testing`) +- `dev_tools.txt` -- dev tools (`mypy`, `flake8`, `black`) + +When adding a new dependency: add it to the appropriate `requirements/*.txt` file with version constraints, never to `pyproject.toml` `dependencies` (unless it's a core runtime dep, which is very rare). + +Before adding, bumping, pinning, or reviewing any dependency in `requirements/*.txt` -- whether a Dependabot PR or a manual edit -- follow the `managing-dependencies` skill in `.claude/skills/`. It defines the layout and `python_version` marker conventions that keep one set of pins working across the full CPython 3.7--3.14 matrix. + +## Test Organization and CI + +### Directory Structure + +- `tests/scenario_tests/` -- Integration-style tests with realistic Slack payloads +- `tests/slack_bolt/` -- Unit tests mirroring the source structure +- `tests/adapter_tests/` and `tests/adapter_tests_async/` -- Framework adapter tests +- `tests/mock_web_api_server/` -- Mock Slack API server used by tests +- Async test variants use `_async` suffix directories + +**Where to put new tests:** Mirror the source structure. For `slack_bolt/middleware/foo.py`, add tests in `tests/slack_bolt/middleware/test_foo.py`. For async variants, use the `_async` suffix directory or file naming pattern. Adapter tests go in `tests/adapter_tests/` (sync) or `tests/adapter_tests_async/` (async). + +**Mock server:** Many tests use `tests/mock_web_api_server/` to simulate Slack API responses. Look at existing tests for usage patterns rather than making real API calls. + +### CI Pipeline + +GitHub Actions (`.github/workflows/ci-build.yml`) runs on every push to `main` and every PR: + +- **Lint** -- `./scripts/lint.sh` on latest Python +- **Typecheck** -- `./scripts/run_mypy.sh` on latest Python +- **Unit tests** -- full test suite across Python 3.7--3.14 matrix +- **Code coverage** -- uploaded to Codecov + +## PR and Commit Guidelines + +- PRs target the `main` branch +- You MUST run `./scripts/install_all_and_run_tests.sh` before submitting +- PR template (`.github/pull_request_template.md`) requires: Summary, Testing steps, Category checkboxes (`App`, `AsyncApp`, Adapters, Docs, Others) +- Requirements: CLA signed, test suite passes, code review approval +- Commits should be atomic with descriptive messages. Reference related issue numbers. diff --git a/README.md b/README.md index 7576597d5..39747df40 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -

Bolt Bolt logo for Python

+

Bolt Bolt logo for Python

@@ -10,16 +10,16 @@
Python Versions - + Documentation

-A Python framework to build Slack apps in a flash with the latest platform features. Read the [getting started guide](https://tools.slack.dev/bolt-python/getting-started) and look at our [code examples](https://github.com/slackapi/bolt-python/tree/main/examples) to learn how to build apps using Bolt. The Python module documents are available [here](https://tools.slack.dev/bolt-python/api-docs/slack_bolt/). +A Python framework to build Slack apps in a flash with the latest platform features. Read the [getting started guide](https://docs.slack.dev/tools/bolt-python/getting-started) and look at our [code examples](https://github.com/slackapi/bolt-python/tree/main/examples) to learn how to build apps using Bolt. The Python module documents are available [here](https://docs.slack.dev/tools/bolt-python/reference/). ## Setup ```bash -# Python 3.6+ required +# Python 3.7+ required python -m venv .venv source .venv/bin/activate @@ -60,7 +60,7 @@ ngrok http 3000 ## Running a Socket Mode app -If you use [Socket Mode](https://api.slack.com/socket-mode) for running your app, `SocketModeHandler` is available for it. +If you use [Socket Mode](https://docs.slack.dev/apis/events-api/using-socket-mode/) for running your app, `SocketModeHandler` is available for it. ```python import os @@ -91,7 +91,7 @@ python app.py ## Listening for events -Apps typically react to a collection of incoming events, which can correspond to [Events API events](https://api.slack.com/events-api), [actions](https://api.slack.com/interactivity/components), [shortcuts](https://api.slack.com/interactivity/shortcuts), [slash commands](https://api.slack.com/interactivity/slash-commands) or [options requests](https://api.slack.com/reference/block-kit/block-elements#external_select). For each type of +Apps typically react to a collection of incoming events, which can correspond to [Events API events](https://docs.slack.dev/apis/events-api/), [actions](https://docs.slack.dev/block-kit/#making-things-interactive), [shortcuts](https://docs.slack.dev/interactivity/implementing-shortcuts/), [slash commands](https://docs.slack.dev/interactivity/implementing-slash-commands/) or [options requests](https://docs.slack.dev/reference/block-kit/block-elements/select-menu-element#external_select). For each type of request, there's a method to build a listener function. ```python @@ -138,12 +138,12 @@ Most of the app's functionality will be inside listener functions (the `fn` para | Argument | Description | | :---: | :--- | | `body` | Dictionary that contains the entire body of the request (superset of `payload`). Some accessory data is only available outside of the payload (such as `trigger_id` and `authorizations`). -| `payload` | Contents of the incoming event. The payload structure depends on the listener. For example, for an Events API event, `payload` will be the [event type structure](https://api.slack.com/events-api#event_type_structure). For a block action, it will be the action from within the `actions` list. The `payload` dictionary is also accessible via the alias corresponding to the listener (`message`, `event`, `action`, `shortcut`, `view`, `command`, or `options`). For example, if you were building a `message()` listener, you could use the `payload` and `message` arguments interchangably. **An easy way to understand what's in a payload is to log it**. | +| `payload` | Contents of the incoming event. The payload structure depends on the listener. For example, for an Events API event, `payload` will be the [event type structure](https://docs.slack.dev/apis/events-api/#event-type-structure). For a block action, it will be the action from within the `actions` list. The `payload` dictionary is also accessible via the alias corresponding to the listener (`message`, `event`, `action`, `shortcut`, `view`, `command`, or `options`). For example, if you were building a `message()` listener, you could use the `payload` and `message` arguments interchangably. **An easy way to understand what's in a payload is to log it**. | | `context` | Event context. This dictionary contains data about the event and app, such as the `botId`. Middleware can add additional context before the event is passed to listeners. -| `ack` | Function that **must** be called to acknowledge that your app received the incoming event. `ack` exists for all actions, shortcuts, view submissions, slash command and options requests. `ack` returns a promise that resolves when complete. Read more in [Acknowledging events](https://tools.slack.dev/bolt-python/concepts/acknowledge). +| `ack` | Function that **must** be called to acknowledge that your app received the incoming event. `ack` exists for all actions, shortcuts, view submissions, slash command and options requests. `ack` returns a promise that resolves when complete. Read more in [Acknowledging events](https://docs.slack.dev/tools/bolt-python/concepts/acknowledge/). | `respond` | Utility function that responds to incoming events **if** it contains a `response_url` (shortcuts, actions, and slash commands). | `say` | Utility function to send a message to the channel associated with the incoming event. This argument is only available when the listener is triggered for events that contain a `channel_id` (the most common being `message` events). `say` accepts simple strings (for plain-text messages) and dictionaries (for messages containing blocks). -| `client` | Web API client that uses the token associated with the event. For single-workspace installations, the token is provided to the constructor. For multi-workspace installations, the token is returned by using [the OAuth library](https://tools.slack.dev/bolt-python/concepts/authenticating-oauth), or manually using the `authorize` function. +| `client` | Web API client that uses the token associated with the event. For single-workspace installations, the token is provided to the constructor. For multi-workspace installations, the token is returned by using [the OAuth library](https://docs.slack.dev/tools/bolt-python/concepts/authenticating-oauth/), or manually using the `authorize` function. | `logger` | The built-in [`logging.Logger`](https://docs.python.org/3/library/logging.html) instance you can use in middleware/listeners. | `complete` | Utility function used to signal the successful completion of a custom step execution. This tells Slack to proceed with the next steps in the workflow. This argument is only available with the `.function` and `.action` listener when handling custom workflow step executions. | `fail` | Utility function used to signal that a custom step failed to complete. This tells Slack to stop the workflow execution. This argument is only available with the `.function` and `.action` listener when handling custom workflow step executions. @@ -153,7 +153,7 @@ Most of the app's functionality will be inside listener functions (the `fn` para If you'd prefer to build your app with [asyncio](https://docs.python.org/3/library/asyncio.html), you can import the [AIOHTTP](https://docs.aiohttp.org/en/stable/) library and call the `AsyncApp` constructor. Within async apps, you can use the async/await pattern. ```bash -# Python 3.6+ required +# Python 3.7+ required python -m venv .venv source .venv/bin/activate @@ -192,7 +192,7 @@ Apps can be run the same way as the syncronous example above. If you'd prefer an ## Getting Help -[The documentation](https://tools.slack.dev/bolt-python) has more information on basic and advanced concepts for Bolt for Python. Also, all the Python module documents of this library are available [here](https://tools.slack.dev/bolt-python/api-docs/slack_bolt/). +[The documentation](https://docs.slack.dev/tools/bolt-python/) has more information on basic and advanced concepts for Bolt for Python. Also, all the Python module documents of this library are available [here](https://docs.slack.dev/tools/bolt-python/reference/). If you otherwise get stuck, we're here to help. The following are the best ways to get assistance working through your issue: diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..06dfdb664 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,63 @@ +# Security Policy + +Slack takes the security of its software and services seriously, including all open-source repositories managed through the [slackapi](https://github.com/slackapi) GitHub organization. + +## Reporting a Vulnerability + +**Do NOT report security vulnerabilities through public GitHub issues, pull requests, or discussions.** + +If you believe you have found a security vulnerability in `slack-bolt`, please report it through the Slack bug bounty program on HackerOne: + +**** + +Even if `slack-bolt` is not explicitly listed as an in-scope asset on the HackerOne program page, reports for vulnerabilities in this package should still be submitted there. The Slack security team triages reports for all `slackapi` open-source repositories through this program. + +If HackerOne is inaccessible, you may alternatively report the issue to [security@salesforce.com](mailto:security@salesforce.com). + +Please do not discuss potential vulnerabilities in public without first coordinating with the security team. + +## What to Include + +To help us triage and respond quickly, please include: + +- Type of vulnerability (e.g., signature bypass, token leakage, denial of service) +- Affected version(s) of `slack-bolt` +- Step-by-step reproduction instructions +- Proof-of-concept code or payloads, if available +- Impact assessment: what an attacker could achieve +- Any specific configuration required to trigger the vulnerability +- Affected source file paths, if known + +## Threat Model + +Bolt for Python is a framework that sits between the Slack platform and developer application code. Its security boundary covers the integrity and confidentiality of that interface. + +### In Scope + +The following are considered framework vulnerabilities: + +- Bypass of request signature verification (HMAC-SHA256 validation) +- OAuth token leakage or cross-tenant token exposure during authorization flows +- Denial of service caused by malformed or specially crafted payloads processed by framework internals +- Authentication or authorization bypass in any built-in adapter +- Information disclosure through framework error responses or timing side channels +- Bypass of the `ssl_check` endpoint protections + +### Out of Scope + +The following are NOT framework vulnerabilities: + +- Vulnerabilities in the Python runtime, operating system, or hosting infrastructure +- Security issues in developer application logic built on top of Bolt (e.g., SQL injection caused by passing unsanitized payload data to a database) +- Vulnerabilities in third-party PyPI packages chosen and installed by the developer outside of Bolt's direct dependencies +- Vulnerabilities in Slack's server-side platform infrastructure (report those directly under Slack's main HackerOne scope) +- Attacks that require possession of a valid signing secret or bot token +- Arbitrary attribute injection or unsafe deserialization caused by developer code handling untrusted input +- Issues that only affect end-of-life versions with no reproduction on supported versions + +## Disclosure Policy + +This project follows coordinated disclosure: + +- Allow a reasonable timeframe for the team to investigate, develop, and release a fix before any public disclosure. +- Researchers who follow responsible disclosure practices are eligible for recognition and bounty consideration through the Slack HackerOne program. diff --git a/docs/.gitignore b/docs/.gitignore deleted file mode 100644 index 53a1610fd..000000000 --- a/docs/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -node_modules/ -.docusaurus -build \ No newline at end of file diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index c1f79adfb..000000000 --- a/docs/README.md +++ /dev/null @@ -1,130 +0,0 @@ -# tools.slack.dev/bolt-python - -This website is built using [Docusaurus](https://docusaurus.io/). 'Tis cool. - -Each Bolt/SDK has its own Docusaurus website, with matching CSS and nav/footer. There is also be a Docusaurus website of just the homepage and community tools. - -``` -website/ -├── docs/ (the good stuff. md and mdx files supported) -│ ├── getting-started.md -│ └── concepts -│ └── sending-message.md -├── i18n/ja/ (the japanese translations) -│ ├──docusaurus-theme-classic/ (footer/navbar translations) -│ └──docusaurus-plugin-content-docs/ -│ └── current/ ( file names need to exactly match **/docs/, but japanese content) -│ ├── getting-started.md -│ └── concepts -│ └── sending-message.md -├── static/ -│ ├── css/ -│ │ └── custom.css (the css for everything!) -│ ├── img/ (the pictures for the site) -│ │ ├── rory.png -│ │ └── oslo.svg -│ └── api-docs/slack_bolt (the generated reference docs with their own HTML/CSS) -│ ├── index.html -│ └── adaptor -│ └── index.html -├── src/ -│ ├── pages/ (stuff that isn't docs. This is empty for this repo!) -│ └── theme/ (only contains the 404 page) -├── docusaurus.config.js (main config file) -├── footerConfig.js (footer. go to main repo to change) -├── navbarConfig.js (navbar. go to main repo to change) -└── sidebar.js (manually set where the docs are in the sidebar.) -``` - -A cheat-sheet: -* _I want to edit a doc._ `docs/*/*.md` -* _I want to edit a Japanese doc._ `i18n/ja-jp/docusaurus-plugin-content-docs/current/*/*.md`. See the [Japanese docs README](./docs/README.md) -* _I want to change the docs sidebar._ `sidebar.js` -* _I want to change the css._ Don't use this repo, use the home repo and the changes will propagate here. -* _I want to change anything else._ `docusaurus.config.js` - ----- - -## Adding a doc - -1. Make a markdown file. Add a `# Title` or use [front matter](https://docusaurus.io/docs/next/create-doc) with `title:`. -2. Save it in `docs/folder/title.md` or `docs/title.md`, depending on if it's in a sidebar category. The nuance is just for internal organization. -3. There needs to be 1:1 docs for the sidebar. Copy the folder/file and put it in the Japanese docs: `i18n/ja/docusaurus-plugin-content-docs/current/*`. Just leave it in English if you don't speak Japanese. -4. Add the doc's path to the sidebar within `docusaurus.config.js`. Where ever makes most sense for you. -5. Test the changes ↓ - ---- - -## Running locally - -Docusaurus requires at least Node 18. You can update Node however you want. `nvm` is one way. - -Install `nvm` if you don't have it: - -``` -curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash -``` - -Then grab the latest version of Node. - -``` -nvm install node -``` - - -If you are running this project locally for the first time, you'll need to install the packages with the following command: - -``` -npm install -``` - -The following command starts a local development server and opens up a browser window. - -``` -npm run start -``` - -Edits to pages are reflected live — no restarting the server or reloading the page. (I'd say... 95% of the time, and 100% time if you're just editing a markdown file). The generated reference docs only load in prod! - -Remember — you're only viewing the Bolt for Python docs right now. - -#### Running locally in Japanese - -For local runs, Docusaurus treats each language as a different instance of the website. You'll want to specify the language to run the japanese site locally: - -``` -npm run start -- --locale ja-jp -``` - -Don't worry - both languages will be built/served on deployment. - ---- - -## Deploying - -The following command generates static content into the `build` directory. - -``` -$ npm run build -``` - -Then you can test out with the following command: - -``` -npm run serve -``` - -If it looks good, make a PR request! - -### Deployment to GitHub pages - -There is a GitHub action workflow set up in each repo. - -* On PR, it tests a site build. -* On Merge, it builds the site and deploys it. Site should update in a minute or two. - ---- - -## Something's broken - -Luke goofed. Open an issue please! `:bufo-appreciates-the-insight:` \ No newline at end of file diff --git a/docs/babel.config.js b/docs/babel.config.js deleted file mode 100644 index e00595dae..000000000 --- a/docs/babel.config.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - presets: [require.resolve('@docusaurus/core/lib/babel/preset')], -}; diff --git a/docs/content/basic/action-listening.md b/docs/content/basic/action-listening.md deleted file mode 100644 index cd677d22d..000000000 --- a/docs/content/basic/action-listening.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -title: Listening to actions -lang: en -slug: /concepts/action-listening ---- - -Your app can listen to user actions, like button clicks, and menu selects, using the `action` method. - -Actions can be filtered on an `action_id` of type `str` or `re.Pattern`. `action_id`s act as unique identifiers for interactive components on the Slack platform. - -You'll notice in all `action()` examples, `ack()` is used. It is required to call the `ack()` function within an action listener to acknowledge that the request was received from Slack. This is discussed in the [acknowledging requests section](/concepts/acknowledge). - -Refer to [the module document](https://tools.slack.dev/bolt-python/api-docs/slack_bolt/kwargs_injection/args.html) to learn the available listener arguments. -```python -# Your listener will be called every time a block element with the action_id "approve_button" is triggered -@app.action("approve_button") -def update_message(ack): - ack() - # Update the message to reflect the action -``` - -
- -Listening to actions using a constraint object - - -You can use a constraints object to listen to `block_id`s and `action_id`s (or any combination of them). Constraints in the object can be of type `str` or `re.Pattern`. - -```python -# Your function will only be called when the action_id matches 'select_user' AND the block_id matches 'assign_ticket' -@app.action({ - "block_id": "assign_ticket", - "action_id": "select_user" -}) -def update_message(ack, body, client): - ack() - - if "container" in body and "message_ts" in body["container"]: - client.reactions_add( - name="white_check_mark", - channel=body["channel"]["id"], - timestamp=body["container"]["message_ts"], - ) -``` - -
\ No newline at end of file diff --git a/docs/content/basic/action-respond.md b/docs/content/basic/action-respond.md deleted file mode 100644 index 7153f18bd..000000000 --- a/docs/content/basic/action-respond.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -title: Responding to actions -lang: en -slug: /concepts/action-respond ---- - -There are two main ways to respond to actions. The first (and most common) way is to use `say()`, which sends a message back to the conversation where the incoming request took place. - -The second way to respond to actions is using `respond()`, which is a utility to use the `response_url` associated with the action. - -Refer to [the module document](https://tools.slack.dev/bolt-python/api-docs/slack_bolt/kwargs_injection/args.html) to learn the available listener arguments. -```python -# Your listener will be called every time an interactive component with the action_id “approve_button” is triggered -@app.action("approve_button") -def approve_request(ack, say): - # Acknowledge action request - ack() - say("Request approved 👍") -``` - -
- -Using respond() - - -Since `respond()` is a utility for calling the `response_url`, it behaves in the same way. You can pass [all the message payload properties](https://api.slack.com/reference/messaging/payload) as keyword arguments along with optional properties like `response_type` (which has a value of `"in_channel"` or `"ephemeral"`), `replace_original`, `delete_original`, `unfurl_links`, and `unfurl_media`. With that, your app can send a new message payload that will be published back to the source of the original interaction. - -```python -# Listens to actions triggered with action_id of “user_select” -@app.action("user_select") -def select_user(ack, action, respond): - ack() - respond(f"You selected <@{action['selected_user']}>") -``` - -
\ No newline at end of file diff --git a/docs/content/basic/assistant.md b/docs/content/basic/assistant.md deleted file mode 100644 index 6b39a9b62..000000000 --- a/docs/content/basic/assistant.md +++ /dev/null @@ -1,241 +0,0 @@ ---- -title: Agents & Assistants -lang: en -slug: /concepts/assistant ---- - -This guide focuses on how to implement Agents & Assistants using Bolt. For general information about the feature, please refer to the [API documentation](https://api.slack.com/docs/apps/ai). - -To get started, enable the **Agents & Assistants** feature on [the app configuration page](https://api.slack.com/apps). Add [`assistant:write`](https://api.slack.com/scopes/assistant:write), [`chat:write`](https://api.slack.com/scopes/chat:write), and [`im:history`](https://api.slack.com/scopes/im:history) to the **bot** scopes on the **OAuth & Permissions** page. Make sure to subscribe to [`assistant_thread_started`](https://api.slack.com/events/assistant_thread_started), [`assistant_thread_context_changed`](https://api.slack.com/events/assistant_thread_context_changed), and [`message.im`](https://api.slack.com/events/message.im) events on the **Event Subscriptions** page. - -Please note that this feature requires a paid plan. If you don't have a paid workspace for development, you can join the [Developer Program](https://api.slack.com/developer-program) and provision a sandbox with access to all Slack features for free. - -To handle assistant thread interactions with humans, although you can implement your agents [using `app.event(...)` listeners](event-listening) for `assistant_thread_started`, `assistant_thread_context_changed`, and `message` events, Bolt offers a simpler approach. You just need to create an `Assistant` instance, attach the needed event handlers to it, and then add the assistant to your `App` instance. - -```python -assistant = Assistant() - -# This listener is invoked when a human user opened an assistant thread -@assistant.thread_started -def start_assistant_thread(say: Say, set_suggested_prompts: SetSuggestedPrompts): - # Send the first reply to the human who started chat with your app's assistant bot - say(":wave: Hi, how can I help you today?") - - # Setting suggested prompts is optional - set_suggested_prompts( - prompts=[ - # If the suggested prompt is long, you can use {"title": "short one to display", "message": "full prompt"} instead - "What does SLACK stand for?", - "When Slack was released?", - ], - ) - -# This listener is invoked when the human user sends a reply in the assistant thread -@assistant.user_message -def respond_in_assistant_thread( - payload: dict, - logger: logging.Logger, - context: BoltContext, - set_status: SetStatus, - client: WebClient, - say: Say, -): - try: - # Tell the human user the assistant bot acknowledges the request and is working on it - set_status("is typing...") - - # Collect the conversation history with this user - replies_in_thread = client.conversations_replies( - channel=context.channel_id, - ts=context.thread_ts, - oldest=context.thread_ts, - limit=10, - ) - messages_in_thread: List[Dict[str, str]] = [] - for message in replies_in_thread["messages"]: - role = "user" if message.get("bot_id") is None else "assistant" - messages_in_thread.append({"role": role, "content": message["text"]}) - - # Pass the latest prompt and chat history to the LLM (call_llm is your own code) - returned_message = call_llm(messages_in_thread) - - # Post the result in the assistant thread - say(text=returned_message) - - except Exception as e: - logger.exception(f"Failed to respond to an inquiry: {e}") - # Don't forget sending a message telling the error - # Without this, the status 'is typing...' won't be cleared, therefore the end-user is unable to continue the chat - say(f":warning: Sorry, something went wrong during processing your request (error: {e})") - -# Enable this assistant middleware in your Bolt app -app.use(assistant) -``` - -Refer to [the module document](https://tools.slack.dev/bolt-python/api-docs/slack_bolt/kwargs_injection/args.html) to learn the available listener arguments. - -When a user opens an Assistant thread while in a channel, the channel information is stored as the thread's `AssistantThreadContext` data. You can access this information by using the `get_thread_context` utility. The reason Bolt provides this utility is that the most recent thread context information is not included in the subsequent user message event payload data. Therefore, an app must store the context data when it is changed so that the app can refer to the data in message event listeners. - -When the user switches channels, the `assistant_thread_context_changed` event will be sent to your app. If you use the built-in `Assistant` middleware without any custom configuration (like the above code snippet does), the updated context data is automatically saved as message metadata of the first reply from the assistant bot. - -As long as you use the built-in approach, you don't need to store the context data within a datastore. The downside of this default behavior is the overhead of additional calls to the Slack API. These calls include those to `conversations.history` which are used to look up the stored message metadata that contains the thread context (via `get_thread_context`). - -To store context elsewhere, pass a custom `AssistantThreadContextStore` implementation to the `Assistant` constructor. We provide `FileAssistantThreadContextStore`, which is a reference implementation that uses the local file system: - -```python -# You can use your own thread_context_store if you want -from slack_bolt import FileAssistantThreadContextStore -assistant = Assistant(thread_context_store=FileAssistantThreadContextStore()) -``` - -Since this reference implementation relies on local files, it's not advised for use in production. For production apps, we recommend creating a class that inherits `AssistantThreadContextStore`. - -
- - -Block Kit interactions in the assistant thread - - -For advanced use cases, Block Kit buttons may be used instead of suggested prompts, as well as the sending of messages with structured [metadata](https://api.slack.com/metadata) to trigger subsequent interactions with the user. - -For example, an app can display a button like "Summarize the referring channel" in the initial reply. When the user clicks the button and submits detailed information (such as the number of messages, days to check, the purpose of the summary, etc.), the app can handle that information and post a message that describes the request with structured metadata. - -By default, apps can't respond to their own bot messages (Bolt prevents infinite loops by default). However, if you pass `ignoring_self_assistant_message_events_enabled=False` to the `App` constructor and add a `bot_message` listener to your `Assistant` middleware, your app can continue processing the request as shown below: - -```python -app = App( - token=os.environ["SLACK_BOT_TOKEN"], - # This must be set to handle bot message events - ignoring_self_assistant_message_events_enabled=False, -) - -assistant = Assistant() - -# Refer to https://tools.slack.dev/bolt-python/api-docs/slack_bolt/kwargs_injection/args.html to learn available listener arguments - -@assistant.thread_started -def start_assistant_thread(say: Say): - say( - text=":wave: Hi, how can I help you today?", - blocks=[ - { - "type": "section", - "text": {"type": "mrkdwn", "text": ":wave: Hi, how can I help you today?"}, - }, - { - "type": "actions", - "elements": [ - # You can have multiple buttons here - { - "type": "button", - "action_id": "assistant-generate-random-numbers", - "text": {"type": "plain_text", "text": "Generate random numbers"}, - "value": "clicked", - }, - ], - }, - ], - ) - -# This listener is invoked when the above button is clicked -@app.action("assistant-generate-random-numbers") -def configure_random_number_generation(ack: Ack, client: WebClient, body: dict): - ack() - client.views_open( - trigger_id=body["trigger_id"], - view={ - "type": "modal", - "callback_id": "configure_assistant_summarize_channel", - "title": {"type": "plain_text", "text": "My Assistant"}, - "submit": {"type": "plain_text", "text": "Submit"}, - "close": {"type": "plain_text", "text": "Cancel"}, - # Relay the assistant thread information to app.view listener - "private_metadata": json.dumps( - { - "channel_id": body["channel"]["id"], - "thread_ts": body["message"]["thread_ts"], - } - ), - "blocks": [ - { - "type": "input", - "block_id": "num", - "label": {"type": "plain_text", "text": "# of outputs"}, - # You can have this kind of predefined input from a user instead of parsing human text - "element": { - "type": "static_select", - "action_id": "input", - "placeholder": {"type": "plain_text", "text": "How many numbers do you need?"}, - "options": [ - {"text": {"type": "plain_text", "text": "5"}, "value": "5"}, - {"text": {"type": "plain_text", "text": "10"}, "value": "10"}, - {"text": {"type": "plain_text", "text": "20"}, "value": "20"}, - ], - "initial_option": {"text": {"type": "plain_text", "text": "5"}, "value": "5"}, - }, - } - ], - }, - ) - -# This listener is invoked when the above modal is submitted -@app.view("configure_assistant_summarize_channel") -def receive_random_number_generation_details(ack: Ack, client: WebClient, payload: dict): - ack() - num = payload["state"]["values"]["num"]["input"]["selected_option"]["value"] - thread = json.loads(payload["private_metadata"]) - - # Post a bot message with structured input data - # The following assistant.bot_message will continue processing - # If you prefer processing this request within this listener, it also works! - # If you don't need bot_message listener, no need to set ignoring_self_assistant_message_events_enabled=False - client.chat_postMessage( - channel=thread["channel_id"], - thread_ts=thread["thread_ts"], - text=f"OK, you need {num} numbers. I will generate it shortly!", - metadata={ - "event_type": "assistant-generate-random-numbers", - "event_payload": {"num": int(num)}, - }, - ) - -# This listener is invoked whenever your app's bot user posts a message -@assistant.bot_message -def respond_to_bot_messages(logger: logging.Logger, set_status: SetStatus, say: Say, payload: dict): - try: - if payload.get("metadata", {}).get("event_type") == "assistant-generate-random-numbers": - # Handle the above random-number-generation request - set_status("is generating an array of random numbers...") - time.sleep(1) - nums: Set[str] = set() - num = payload["metadata"]["event_payload"]["num"] - while len(nums) < num: - nums.add(str(random.randint(1, 100))) - say(f"Here you are: {', '.join(nums)}") - else: - # nothing to do for this bot message - # If you want to add more patterns here, be careful not to cause infinite loop messaging - pass - - except Exception as e: - logger.exception(f"Failed to respond to an inquiry: {e}") - -# This listener is invoked when the human user posts a reply -@assistant.user_message -def respond_to_user_messages(logger: logging.Logger, set_status: SetStatus, say: Say): - try: - set_status("is typing...") - say("Please use the buttons in the first reply instead :bow:") - except Exception as e: - logger.exception(f"Failed to respond to an inquiry: {e}") - say(f":warning: Sorry, something went wrong during processing your request (error: {e})") - - -# Enable this assistant middleware in your Bolt app -app.use(assistant) -``` - -
- - -Lastly, if you want to check full working example app, you can check [our sample repository](https://github.com/slack-samples/bolt-python-assistant-template) on GitHub. \ No newline at end of file diff --git a/docs/content/basic/message-sending.md b/docs/content/basic/message-sending.md deleted file mode 100644 index d7b5d2da9..000000000 --- a/docs/content/basic/message-sending.md +++ /dev/null @@ -1,50 +0,0 @@ ---- -title: Sending messages -lang: en -slug: /concepts/message-sending ---- - -Within your listener function, `say()` is available whenever there is an associated conversation (for example, a conversation where the event or action which triggered the listener occurred). `say()` accepts a string to post simple messages and JSON payloads to send more complex messages. The message payload you pass in will be sent to the associated conversation. - -In the case that you'd like to send a message outside of a listener or you want to do something more advanced (like handle specific errors), you can call `client.chat_postMessage` [using the client attached to your Bolt instance](/concepts/web-api). - -Refer to [the module document](https://tools.slack.dev/bolt-python/api-docs/slack_bolt/kwargs_injection/args.html) to learn the available listener arguments. -```python -# Listens for messages containing "knock knock" and responds with an italicized "who's there?" -@app.message("knock knock") -def ask_who(message, say): - say("_Who's there?_") -``` - -
- -Sending a message with blocks - - -`say()` accepts more complex message payloads to make it easy to add functionality and structure to your messages. - -To explore adding rich message layouts to your app, read through [the guide on our API site](https://api.slack.com/messaging/composing/layouts) and look through templates of common app flows [in the Block Kit Builder](https://api.slack.com/tools/block-kit-builder?template=1). - -```python -# Sends a section block with datepicker when someone reacts with a 📅 emoji -@app.event("reaction_added") -def show_datepicker(event, say): - reaction = event["reaction"] - if reaction == "calendar": - blocks = [{ - "type": "section", - "text": {"type": "mrkdwn", "text": "Pick a date for me to remind you"}, - "accessory": { - "type": "datepicker", - "action_id": "datepicker_remind", - "initial_date": "2020-05-04", - "placeholder": {"type": "plain_text", "text": "Select a date"} - } - }] - say( - blocks=blocks, - text="Pick a date for me to remind you" - ) -``` - -
\ No newline at end of file diff --git a/docs/content/basic/web-api.md b/docs/content/basic/web-api.md deleted file mode 100644 index c4f9a526b..000000000 --- a/docs/content/basic/web-api.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -title: Using the Web API -lang: en -slug: /concepts/web-api ---- - -You can call [any Web API method](https://api.slack.com/methods) using the [`WebClient`](https://tools.slack.dev/python-slack-sdk/web) provided to your Bolt app as either `app.client` or `client` in middleware/listener arguments (given that your app has the appropriate scopes). When you call one the client's methods, it returns a `SlackResponse` which contains the response from Slack. - -The token used to initialize Bolt can be found in the `context` object, which is required to call most Web API methods. - -:::info - -Refer to [the module document](https://tools.slack.dev/bolt-python/api-docs/slack_bolt/kwargs_injection/args.html) to learn the available listener arguments. - -::: - -```python -@app.message("wake me up") -def say_hello(client, message): - # Unix Epoch time for September 30, 2020 11:59:59 PM - when_september_ends = 1601510399 - channel_id = message["channel"] - client.chat_scheduleMessage( - channel=channel_id, - post_at=when_september_ends, - text="Summer has come and passed" - ) -``` diff --git a/docs/content/getting-started.md b/docs/content/getting-started.md deleted file mode 100644 index 79ccfff61..000000000 --- a/docs/content/getting-started.md +++ /dev/null @@ -1,325 +0,0 @@ ---- -title: Getting started -slug: getting-started -lang: en ---- - -# Getting started with Bolt for Python - -This guide is meant to walk you through getting up and running with a Slack app using Bolt for Python. Along the way, we’ll create a new Slack app, set up your local environment, and develop an app that listens and responds to messages from a Slack workspace. - - -When you're finished, you'll have this ⚡️[Getting Started with Slack app](https://github.com/slackapi/bolt-python/tree/main/examples/getting_started) to run, modify, and make your own. The possibilities are endless! - -:::info - -For this guide, we are going to be using [Socket Mode](https://api.slack.com/apis/connections/socket), our recommended option for those just getting started and building something for their team. If you already know you're going to want to use HTTP as your app's communication protocol, head over to our parallel guide, [Getting Started over HTTP](/tutorial/getting-started-http). - -::: - ---- - -### Create an app {#create-an-app} -First thing's first: before you start developing with Bolt, you'll want to [create a Slack app](https://api.slack.com/apps/new). - -:::tip - -We recommend using a workspace where you won't disrupt real work getting done — [you can create a new one for free](https://slack.com/get-started#create). - -::: - -After you fill out an app name (_you can change it later_) and pick a workspace to install it to, hit the `Create App` button and you'll land on your app's **Basic Information** page. - -This page contains an overview of your app in addition to important credentials you'll want to reference later. - -![Basic Information page](/img/boltpy/basic-information-page.png "Basic Information page") - -Look around, add an app icon and description, and then let's start configuring your app 🔩 - ---- - -### Tokens and installing apps {#tokens-and-installing-apps} -Slack apps use [OAuth to manage access to Slack's APIs](https://api.slack.com/docs/oauth). When an app is installed, you'll receive a token that the app can use to call API methods. - -There are three main token types available to a Slack app: user (`xoxp`), bot (`xoxb`), and app-level (`xapp`) tokens. -- [User tokens](https://api.slack.com/authentication/token-types#user) allow you to call API methods on behalf of users after they install or authenticate the app. There may be several user tokens for a single workspace. -- [Bot tokens](https://api.slack.com/authentication/token-types#bot) are associated with bot users, and are only granted once in a workspace where someone installs the app. The bot token your app uses will be the same no matter which user performed the installation. Bot tokens are the token type that _most_ apps use. -- [App-level tokens](https://api.slack.com/authentication/token-types#app) represent your app across organizations, including installations by all individual users on all workspaces in a given organization and are commonly used for creating WebSocket connections to your app. - -We're going to use bot and app-level tokens for this guide. - -1. Navigate to the **OAuth & Permissions** on the left sidebar and scroll down to the **Bot Token Scopes** section. Click **Add an OAuth Scope**. - -2. For now, we'll just add one scope: [`chat:write`](https://api.slack.com/scopes/chat:write). This grants your app the permission to post messages in channels it's a member of. - -3. Scroll up to the top of the **OAuth & Permissions** page and click **Install App to Workspace**. You'll be led through Slack's OAuth UI, where you should allow your app to be installed to your development workspace. - -4. Once you authorize the installation, you'll land on the **OAuth & Permissions** page and see a **Bot User OAuth Access Token**. - -![OAuth Tokens](/img/boltpy/bot-token.png "Bot OAuth Token") - -5. Then head over to **Basic Information** and scroll down under the App Token section and click **Generate Token and Scopes** to generate an app-level token. Add the `connections:write` scope to this token and save the generated `xapp` token, we'll use both these tokens in just a moment. - -6. Navigate to **Socket Mode** on the left side menu and toggle to enable. - - -:::tip - -Treat your tokens like passwords and [keep them safe](https://api.slack.com/docs/oauth-safety). Your app uses tokens to post and retrieve information from Slack workspaces. - -::: - ---- - -### Setting up your project {#setting-up-your-project} -With the initial configuration handled, it's time to set up a new Bolt project. This is where you'll write the code that handles the logic for your app. - -If you don’t already have a project, let’s create a new one. Create an empty directory: - -```shell -mkdir first-bolt-app -cd first-bolt-app -``` - -Next, we recommend using a [Python virtual environment](https://packaging.python.org/guides/installing-using-pip-and-virtual-environments/#creating-a-virtual-environment) to manage your project's dependencies. This is a great way to prevent conflicts with your system's Python packages. Let's create and activate a new virtual environment with [Python 3.6 or later](https://www.python.org/downloads/): - -```shell -python3 -m venv .venv -source .venv/bin/activate -``` - -We can confirm that the virtual environment is active by checking that the path to `python3` is _inside_ your project ([a similar command is available on Windows](https://packaging.python.org/guides/installing-using-pip-and-virtual-environments/#activating-a-virtual-environment)): - -```shell -which python3 -# Output: /path/to/first-bolt-app/.venv/bin/python3 -``` - -Before we install the Bolt for Python package to your new project, let's save the **bot token** and **app-level token** that were generated when you configured your app. - -1. **Copy your bot (xoxb) token from the OAuth & Permissions page** and store it in a new environment variable. The following example works on Linux and macOS; but [similar commands are available on Windows](https://superuser.com/questions/212150/how-to-set-env-variable-in-windows-cmd-line/212153#212153). -```shell -export SLACK_BOT_TOKEN=xoxb- -``` - -2. **Copy your app-level (xapp) token from the Basic Information page** and then store it in a new environment variable. -```shell -export SLACK_APP_TOKEN= -``` - -:::warning - -Keep all tokens secure. At a minimum, you should avoid checking them into public version control, and access them via environment variables as we've done above. Checkout the API documentation for more on [best practices for app security](https://api.slack.com/authentication/best-practices). - -::: - -Now, let's create your app. Install the `slack_bolt` Python package to your virtual environment using the following command: - -```shell -pip install slack_bolt -``` - -Create a new file called `app.py` in this directory and add the following code: - -```python -import os -from slack_bolt import App -from slack_bolt.adapter.socket_mode import SocketModeHandler - -# Initializes your app with your bot token and socket mode handler -app = App(token=os.environ.get("SLACK_BOT_TOKEN")) - -# Start your app -if __name__ == "__main__": - SocketModeHandler(app, os.environ["SLACK_APP_TOKEN"]).start() -``` - -Your tokens are enough to create your first Bolt app. Save your `app.py` file then on the command line run the following: - -```script -python3 app.py -``` - -Your app should let you know that it's up and running. 🎉 - ---- - -### Setting up events {#setting-up-events} -Your app behaves similarly to people on your team — it can post messages, add emoji reactions, and listen and respond to events. - -To listen for events happening in a Slack workspace (like when a message is posted or when a reaction is posted to a message) you'll use the [Events API to subscribe to event types](https://api.slack.com/events-api). - -:::info - -Earlier in this tutorial we enabled Socket Mode. Socket Mode lets apps use the Events API and interactive components without exposing a public HTTP endpoint. This can be helpful during development, or if you're receiving requests from behind a firewall. HTTP is more useful for apps being deployed to hosting environments, or apps intended for distribution via the Slack App Directory. To follow this getting started guide with HTTP instead, head over [here](/tutorial/getting-started-http). - -::: - -It's time to tell Slack what events we'd like to listen for. - -When an event occurs, Slack will send your app some information about the event, like the user that triggered it and the channel it occurred in. Your app will process the details and can respond accordingly. - -Navigate to **Event Subscriptions** on the left sidebar and toggle to enable. Under **Subscribe to Bot Events**, you can add events for your bot to respond to. There are four events related to messages: -- [`message.channels`](https://api.slack.com/events/message.channels) listens for messages in public channels that your app is added to -- [`message.groups`](https://api.slack.com/events/message.groups) listens for messages in 🔒 private channels that your app is added to -- [`message.im`](https://api.slack.com/events/message.im) listens for messages in your app's DMs with users -- [`message.mpim`](https://api.slack.com/events/message.mpim) listens for messages in multi-person DMs that your app is added to - -If you want your bot to listen to messages from everywhere it is added to, choose all four message events. After you’ve selected the events you want your bot to listen to, click the green **Save Changes** button. - ---- - -### Listening and responding to a message {#listening-and-responding-to-a-message} -Your app is now ready for some logic. Let's start by using the `message()` method to attach a listener for messages. - -The following example listens and responds to all messages in channels/DMs where your app has been added that contain the word "hello": - -```python -import os -from slack_bolt import App -from slack_bolt.adapter.socket_mode import SocketModeHandler - -# Initializes your app with your bot token and socket mode handler -app = App(token=os.environ.get("SLACK_BOT_TOKEN")) - -# Listens to incoming messages that contain "hello" -# To learn available listener arguments, -# visit https://tools.slack.dev/bolt-python/api-docs/slack_bolt/kwargs_injection/args.html -@app.message("hello") -def message_hello(message, say): - # say() sends a message to the channel where the event was triggered - say(f"Hey there <@{message['user']}>!") - -# Start your app -if __name__ == "__main__": - SocketModeHandler(app, os.environ["SLACK_APP_TOKEN"]).start() -``` - -If you restart your app, so long as your bot user has been added to the channel/DM, when you send any message that contains "hello", it will respond. - -This is a basic example, but it gives you a place to start customizing your app based on your own goals. Let's try something a little more interactive by sending a button rather than plain text. - ---- - -### Sending and responding to actions {#sending-and-responding-to-actions} - -To use features like buttons, select menus, datepickers, modals, and shortcuts, you’ll need to enable interactivity. Head over to **Interactivity & Shortcuts** in your app configuration. - -:::tip - -You’ll notice that with Socket Mode on, basic interactivity is enabled for us by default, so no further action here is needed. If you’re using HTTP, you’ll need to supply a Request URL for Slack to send events to. - -::: - -When interactivity is enabled, interactions with shortcuts, modals, or interactive components (such as buttons, select menus, and datepickers) will be sent to your app as events. - -Now, let's go back to your app's code and add logic to handle those events: -- First, we'll send a message that contains an interactive component (in this case a button) -- Next, we'll listen for the action of a user clicking the button before responding - -Below, the code from the last section is modified to send a message containing a button rather than just a string: - -```python -import os -from slack_bolt import App -from slack_bolt.adapter.socket_mode import SocketModeHandler - -# Initializes your app with your bot token and socket mode handler -app = App( - token=os.environ.get("SLACK_BOT_TOKEN"), - # signing_secret=os.environ.get("SLACK_SIGNING_SECRET") # not required for socket mode -) - -# Listens to incoming messages that contain "hello" -@app.message("hello") -def message_hello(message, say): - # say() sends a message to the channel where the event was triggered - say( - blocks=[ - { - "type": "section", - "text": {"type": "mrkdwn", "text": f"Hey there <@{message['user']}>!"}, - "accessory": { - "type": "button", - "text": {"type": "plain_text", "text": "Click Me"}, - "action_id": "button_click" - } - } - ], - text=f"Hey there <@{message['user']}>!" - ) - -# Start your app -if __name__ == "__main__": - SocketModeHandler(app, os.environ["SLACK_APP_TOKEN"]).start() - -``` - -The value inside of `say()` is now an object that contains an array of `blocks`. Blocks are the building components of a Slack message and can range from text to images to datepickers. In this case, your app will respond with a section block that includes a button as an accessory. Since we're using `blocks`, the `text` is a fallback for notifications and accessibility. - -You'll notice in the button `accessory` object, there is an `action_id`. This will act as a unique identifier for the button so your app can specify what action it wants to respond to. - -:::tip - -The [Block Kit Builder](https://app.slack.com/block-kit-builder) is an simple way to prototype your interactive messages. The builder lets you (or anyone on your team) mockup messages and generates the corresponding JSON that you can paste directly in your app. - -::: - -Now, if you restart your app and say "hello" in a channel your app is in, you'll see a message with a button. But if you click the button, nothing happens (*yet!*). - -Let's add a handler to send a followup message when someone clicks the button: - -```python -import os -from slack_bolt import App -from slack_bolt.adapter.socket_mode import SocketModeHandler - -# Initializes your app with your bot token and socket mode handler -app = App(token=os.environ.get("SLACK_BOT_TOKEN")) - -# Listens to incoming messages that contain "hello" -@app.message("hello") -def message_hello(message, say): - # say() sends a message to the channel where the event was triggered - say( - blocks=[ - { - "type": "section", - "text": {"type": "mrkdwn", "text": f"Hey there <@{message['user']}>!"}, - "accessory": { - "type": "button", - "text": {"type": "plain_text", "text": "Click Me"}, - "action_id": "button_click" - } - } - ], - text=f"Hey there <@{message['user']}>!" - ) - -@app.action("button_click") -def action_button_click(body, ack, say): - # Acknowledge the action - ack() - say(f"<@{body['user']['id']}> clicked the button") - -# Start your app -if __name__ == "__main__": - SocketModeHandler(app, os.environ["SLACK_APP_TOKEN"]).start() -``` - -You can see that we used `app.action()` to listen for the `action_id` that we named `button_click`. If you restart your app and click the button, you'll see a new message from your app that says you clicked the button. - ---- - -### Next steps {#next-steps} -You just built your first [Bolt for Python app](https://github.com/slackapi/bolt-python/tree/main/examples/getting_started) with Socket Mode! 🎉 - -Now that you have a basic app up and running, you can start exploring how to make your Bolt app stand out. Here are some ideas about what to explore next: - -* Read through the _Basic concepts_ to learn about the different methods and features your Bolt app has access to. - -* Explore the different events your bot can listen to with the [`app.event()`](/concepts/event-listening) method. All of the events are listed [on the API site](https://api.slack.com/events). - -* Bolt allows you to [call Web API methods](/concepts/web-api) with the client attached to your app. There are [over 220 methods](https://api.slack.com/methods) on our API site. - -* Learn more about the different token types [on our API site](https://api.slack.com/docs/token-types). Your app may need different tokens depending on the actions you want it to perform. For apps that do not use Socket Mode, typically only the bot (`xoxb`) token and Signing Secret are required. For example of this, see our parallel guide [Getting Started with HTTP](/tutorial/getting-started-http). \ No newline at end of file diff --git a/docs/content/steps/adding-editing-steps.md b/docs/content/steps/adding-editing-steps.md deleted file mode 100644 index 99ca6d587..000000000 --- a/docs/content/steps/adding-editing-steps.md +++ /dev/null @@ -1,62 +0,0 @@ ---- -title: Adding or editing steps from apps -lang: en -slug: /concepts/adding-editing-steps ---- - -:::danger - -Steps from apps are a deprecated feature. - -Steps from apps are different than, and not interchangeable with, Slack automation workflows. We encourage those who are currently publishing steps from apps to consider the new [Slack automation features](https://api.slack.com/automation), such as custom steps for Bolt. - -Please [read the Slack API changelog entry](https://api.slack.com/changelog/2023-08-workflow-steps-from-apps-step-back) for more information. - -::: - -When a builder adds (or later edits) your step in their workflow, your app will receive a [`workflow_step_edit` event](https://api.slack.com/reference/workflows/workflow_step_edit). The `edit` callback in your `WorkflowStep` configuration will be run when this event is received. - -Whether a builder is adding or editing a step, you need to send them a [step from app configuration modal](https://api.slack.com/reference/workflows/configuration-view). This modal is where step-specific settings are chosen, and it has more restrictions than typical modals—most notably, it cannot include `title`, `submit`, or `close` properties. By default, the configuration modal's `callback_id` will be the same as the step from app. - -Within the `edit` callback, the `configure()` utility can be used to easily open your step's configuration modal by passing in the view's blocks with the corresponding `blocks` argument. To disable saving the configuration before certain conditions are met, you can also pass in `submit_disabled` with a value of `True`. - -To learn more about opening configuration modals, [read the documentation](https://api.slack.com/workflows/steps#handle_config_view). - -Refer to the module documents (common / step-specific) to learn the available arguments. - -```python -def edit(ack, step, configure): - ack() - - blocks = [ - { - "type": "input", - "block_id": "task_name_input", - "element": { - "type": "plain_text_input", - "action_id": "name", - "placeholder": {"type": "plain_text", "text": "Add a task name"}, - }, - "label": {"type": "plain_text", "text": "Task name"}, - }, - { - "type": "input", - "block_id": "task_description_input", - "element": { - "type": "plain_text_input", - "action_id": "description", - "placeholder": {"type": "plain_text", "text": "Add a task description"}, - }, - "label": {"type": "plain_text", "text": "Task description"}, - }, - ] - configure(blocks=blocks) - -ws = WorkflowStep( - callback_id="add_task", - edit=edit, - save=save, - execute=execute, -) -app.step(ws) -``` diff --git a/docs/content/steps/creating-steps.md b/docs/content/steps/creating-steps.md deleted file mode 100644 index 6728a77e1..000000000 --- a/docs/content/steps/creating-steps.md +++ /dev/null @@ -1,59 +0,0 @@ ---- -title: Creating steps from apps -lang: en -slug: /concepts/creating-steps ---- - -:::danger - -Steps from apps are a deprecated feature. - -Steps from apps are different than, and not interchangeable with, Slack automation workflows. We encourage those who are currently publishing steps from apps to consider the new [Slack automation features](https://api.slack.com/automation), such as custom steps for Bolt. - -Please [read the Slack API changelog entry](https://api.slack.com/changelog/2023-08-workflow-steps-from-apps-step-back) for more information. - -::: - -To create a step from app, Bolt provides the `WorkflowStep` class. - -When instantiating a new `WorkflowStep`, pass in the step's `callback_id` and a configuration object. - -The configuration object contains three keys: `edit`, `save`, and `execute`. Each of these keys must be a single callback or a list of callbacks. All callbacks have access to a `step` object that contains information about the step from app event. - -After instantiating a `WorkflowStep`, you can pass it into `app.step()`. Behind the scenes, your app will listen and respond to the step’s events using the callbacks provided in the configuration object. - -Alternatively, steps from apps can also be created using the `WorkflowStepBuilder` class alongside a decorator pattern. For more information, including an example of this approach, [refer to the documentation](https://tools.slack.dev/bolt-python/api-docs/slack_bolt/workflows/step/step.html#slack_bolt.workflows.step.step.WorkflowStepBuilder). - -Refer to the module documents (common / step-specific) to learn the available arguments. - -```python -import os -from slack_bolt import App -from slack_bolt.workflows.step import WorkflowStep - -# Initiate the Bolt app as you normally would -app = App( - token=os.environ.get("SLACK_BOT_TOKEN"), - signing_secret=os.environ.get("SLACK_SIGNING_SECRET") -) - -def edit(ack, step, configure): - pass - -def save(ack, view, update): - pass - -def execute(step, complete, fail): - pass - -# Create a new WorkflowStep instance -ws = WorkflowStep( - callback_id="add_task", - edit=edit, - save=save, - execute=execute, -) - -# Pass Step to set up listeners -app.step(ws) -``` diff --git a/docs/content/steps/executing-steps.md b/docs/content/steps/executing-steps.md deleted file mode 100644 index 12d557cd0..000000000 --- a/docs/content/steps/executing-steps.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -title: Executing steps from apps -lang: en -slug: /concepts/executing-steps ---- - -:::danger - -Steps from apps are a deprecated feature. - -Steps from apps are different than, and not interchangeable with, Slack automation workflows. We encourage those who are currently publishing steps from apps to consider the new [Slack automation features](https://api.slack.com/automation), such as custom steps for Bolt. - -Please [read the Slack API changelog entry](https://api.slack.com/changelog/2023-08-workflow-steps-from-apps-step-back) for more information. - -::: - -When your step from app is executed by an end user, your app will receive a [`workflow_step_execute` event](https://api.slack.com/events/workflow_step_execute). The `execute` callback in your `WorkflowStep` configuration will be run when this event is received. - -Using the `inputs` from the `save` callback, this is where you can make third-party API calls, save information to a database, update the user's Home tab, or decide the outputs that will be available to subsequent steps from apps by mapping values to the `outputs` object. - -Within the `execute` callback, your app must either call `complete()` to indicate that the step's execution was successful, or `fail()` to indicate that the step's execution failed. - -Refer to the module documents (common / step-specific) to learn the available arguments. -```python -def execute(step, complete, fail): - inputs = step["inputs"] - # if everything was successful - outputs = { - "task_name": inputs["task_name"]["value"], - "task_description": inputs["task_description"]["value"], - } - complete(outputs=outputs) - - # if something went wrong - error = {"message": "Just testing step failure!"} - fail(error=error) - -ws = WorkflowStep( - callback_id="add_task", - edit=edit, - save=save, - execute=execute, -) -app.step(ws) -``` diff --git a/docs/content/steps/saving-steps.md b/docs/content/steps/saving-steps.md deleted file mode 100644 index 079cf5d71..000000000 --- a/docs/content/steps/saving-steps.md +++ /dev/null @@ -1,62 +0,0 @@ ---- -title: Saving step configurations -lang: en -slug: /concepts/saving-steps ---- - -:::danger - -Steps from apps are a deprecated feature. - -Steps from apps are different than, and not interchangeable with, Slack automation workflows. We encourage those who are currently publishing steps from apps to consider the new [Slack automation features](https://api.slack.com/automation), such as custom steps for Bolt. - -Please [read the Slack API changelog entry](https://api.slack.com/changelog/2023-08-workflow-steps-from-apps-step-back) for more information. - -::: - -After the configuration modal is opened, your app will listen for the `view_submission` event. The `save` callback in your `WorkflowStep` configuration will be run when this event is received. - -Within the `save` callback, the `update()` method can be used to save the builder's step configuration by passing in the following arguments: - -- `inputs` is a dictionary representing the data your app expects to receive from the user upon step execution. -- `outputs` is a list of objects containing data that your app will provide upon the step's completion. Outputs can then be used in subsequent steps of the workflow. -- `step_name` overrides the default Step name -- `step_image_url` overrides the default Step image - -To learn more about how to structure these parameters, [read the documentation](https://api.slack.com/reference/workflows/workflow_step). - -Refer to the module documents (common / step-specific) to learn the available arguments. -```python -def save(ack, view, update): - ack() - - values = view["state"]["values"] - task_name = values["task_name_input"]["name"] - task_description = values["task_description_input"]["description"] - - inputs = { - "task_name": {"value": task_name["value"]}, - "task_description": {"value": task_description["value"]} - } - outputs = [ - { - "type": "text", - "name": "task_name", - "label": "Task name", - }, - { - "type": "text", - "name": "task_description", - "label": "Task description", - } - ] - update(inputs=inputs, outputs=outputs) - -ws = WorkflowStep( - callback_id="add_task", - edit=edit, - save=save, - execute=execute, -) -app.step(ws) -``` diff --git a/docs/content/steps/steps.md b/docs/content/steps/steps.md deleted file mode 100644 index 64604ba78..000000000 --- a/docs/content/steps/steps.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -title: Overview of steps from apps -lang: en -slug: /concepts/steps ---- - -:::danger - -Steps from apps are a deprecated feature. - -Steps from apps are different than, and not interchangeable with, Slack automation workflows. We encourage those who are currently publishing steps from apps to consider the new [Slack automation features](https://api.slack.com/automation), such as custom steps for Bolt. - -Please [read the Slack API changelog entry](https://api.slack.com/changelog/2023-08-workflow-steps-from-apps-step-back) for more information. - -::: - -Steps from apps for legacy workflows are now deprecated. Use new [custom steps](https://api.slack.com/automation/functions/custom-bolt). - -Steps from apps allow your app to create and process steps that users can add using [Workflow Builder](https://api.slack.com/workflows). - -Steps from apps are made up of three distinct user events: - -- Adding or editing the step in a Workflow -- Saving or updating the step's configuration -- The end user's execution of the step - -All three events must be handled for a step from app to function. - -Read more about steps from apps in the [API documentation](https://api.slack.com/workflows/steps). diff --git a/docs/content/tutorial/getting-started-http.md b/docs/content/tutorial/getting-started-http.md deleted file mode 100644 index 6cd4a8ae6..000000000 --- a/docs/content/tutorial/getting-started-http.md +++ /dev/null @@ -1,322 +0,0 @@ ---- -title: Getting started over HTTP -slug: getting-started-http -lang: en ---- - -# Getting started with Bolt for Python over HTTP - -This guide is meant to walk you through getting up and running with a Slack app using **Bolt for Python over HTTP**. Along the way, we’ll create a new Slack app, set up your local environment, and develop an app that listens and responds to messages from a Slack workspace. - - -When you're finished, you'll have this ⚡️[Getting Started with Slack app](https://github.com/slackapi/bolt-python/tree/main/examples/getting_started) to run, modify, and make your own. - ---- - -### Create an app {#create-an-app} -First thing's first: before you start developing with Bolt, you'll want to [create a Slack app](https://api.slack.com/apps/new). - -:::tip - -We recommend using a workspace where you won't disrupt real work getting done — [you can create a new one for free](https://slack.com/get-started#create). - -::: - -After you fill out an app name (_you can change it later_) and pick a workspace to install it to, hit the `Create App` button and you'll land on your app's **Basic Information** page. - -This page contains an overview of your app in addition to important credentials you'll need later, like the `Signing Secret` under the **App Credentials** header. - -![Basic Information page](/img/boltpy/basic-information-page.png "Basic Information page") - -Look around, add an app icon and description, and then let's start configuring your app 🔩 - ---- - -### Tokens and installing apps {#tokens-and-installing-apps} -Slack apps use [OAuth to manage access to Slack's APIs](https://api.slack.com/docs/oauth). When an app is installed, you'll receive a token that the app can use to call API methods. - -There are three main token types available to a Slack app: user (`xoxp`), bot (`xoxb`), and app-level (`xapp`) tokens. -- [User tokens](https://api.slack.com/authentication/token-types#user) allow you to call API methods on behalf of users after they install or authenticate the app. There may be several user tokens for a single workspace. -- [Bot tokens](https://api.slack.com/authentication/token-types#bot) are associated with bot users, and are only granted once in a workspace where someone installs the app. The bot token your app uses will be the same no matter which user performed the installation. Bot tokens are the token type that _most_ apps use. -- [App-level tokens](https://api.slack.com/authentication/token-types#app) represent your app across organizations, including installations by all individual users on all workspaces in a given organization and are commonly used for creating websocket connections to your app. - -For brevity, we're going to use bot tokens for this guide. - -1. Navigate to the **OAuth & Permissions** on the left sidebar and scroll down to the **Bot Token Scopes** section. Click **Add an OAuth Scope**. - -2. For now, we'll just add one scope: [`chat:write`](https://api.slack.com/scopes/chat:write). This grants your app the permission to post messages in channels it's a member of. - -3. Scroll up to the top of the OAuth & Permissions page and click **Install App to Workspace**. You'll be led through Slack's OAuth UI, where you should allow your app to be installed to your development workspace. - -4. Once you authorize the installation, you'll land on the **OAuth & Permissions** page and see a **Bot User OAuth Access Token**. We'll use that in just a moment. - -![OAuth Tokens](/img/boltpy/bot-token.png "Bot OAuth Token") - -:::info - -Treat your token like a password and [keep it safe](https://api.slack.com/docs/oauth-safety). Your app uses it to post and retrieve information from Slack workspaces. - -::: - ---- - -### Setting up your project {#setting-up-your-project} -With the initial configuration handled, it's time to set up a new Bolt project. This is where you'll write the code that handles the logic for your app. - -If you don’t already have a project, let’s create a new one. Create an empty directory: - -```shell -mkdir first-bolt-app -cd first-bolt-app -``` - -Next, we recommend using a [Python virtual environment](https://packaging.python.org/guides/installing-using-pip-and-virtual-environments/#creating-a-virtual-environment) to manage your project's dependencies. This is a great way to prevent conflicts with your system's Python packages. Let's create and activate a new virtual environment with [Python 3.6 or later](https://www.python.org/downloads/): - -```shell -python3 -m venv .venv -source .venv/bin/activate -``` - -We can confirm that the virtual environment is active by checking that the path to `python3` is _inside_ your project ([a similar command is available on Windows](https://packaging.python.org/guides/installing-using-pip-and-virtual-environments/#activating-a-virtual-environment)): - -```shell -which python3 -# Output: /path/to/first-bolt-app/.venv/bin/python3 -``` - -Before we install the Bolt for Python package to your new project, let's save the **bot token** and **signing secret** that were generated when you configured your app. - -1. **Copy your Signing Secret from the Basic Information page** and then store it in a new environment variable. The following example works on Linux and macOS; but [similar commands are available on Windows](https://superuser.com/questions/212150/how-to-set-env-variable-in-windows-cmd-line/212153#212153). -```shell -export SLACK_SIGNING_SECRET= -``` - -2. **Copy your bot (xoxb) token from the OAuth & Permissions page** and store it in another environment variable. -```shell -export SLACK_BOT_TOKEN=xoxb- -``` -> 🔒 Remember to keep your tokens and signing secret secure. At a minimum, you should avoid checking them into public version control, and access them via environment variables as we've done above. Checkout the API documentation for more on [best practices for app security](https://api.slack.com/authentication/best-practices). - -Now, let's create your app. Install the `slack_bolt` Python package to your virtual environment using the following command: - -```shell -pip install slack_bolt -``` - -Create a new file called `app.py` in this directory and add the following code: - -```python -import os -from slack_bolt import App - -# Initializes your app with your bot token and signing secret -app = App( - token=os.environ.get("SLACK_BOT_TOKEN"), - signing_secret=os.environ.get("SLACK_SIGNING_SECRET") -) - -# Start your app -if __name__ == "__main__": - app.start(port=int(os.environ.get("PORT", 3000))) -``` - -Your token and signing secret are enough to create your first Bolt app. Save your `app.py` file then on the command line run the following: - -```script -python3 app.py -``` - -Your app should let you know that it's up and running. 🎉 - ---- - -### Setting up events over HTTP {#setting-up-events} -Your app behaves similarly to people on your team — it can post messages, add emoji reactions, and listen and respond to events. - -To listen for events happening in a Slack workspace (like when a message is posted or when a reaction is posted to a message) you'll use the [Events API to subscribe to event types](https://api.slack.com/events-api). - -Let's enable events for your app: -1. Go back to your app configuration page (click on the app [from your app management page](https://api.slack.com/apps)). Click **Event Subscriptions** on the left sidebar. Toggle the switch labeled **Enable Events**. - -2. Add your Request URL. Slack will send HTTP POST requests corresponding to events to this [Request URL](https://api.slack.com/apis/connections/events-api#the-events-api__subscribing-to-event-types__events-api-request-urls) endpoint. Bolt uses the `/slack/events` path to listen to all incoming requests (whether shortcuts, events, or interactivity payloads). When configuring your Request URL within your app configuration, you'll append `/slack/events`, e.g. `https:///slack/events`. 💡 As long as your Bolt app is still running, your URL should become verified. - -:::tip - -For local development, you can use a proxy service like ngrok to create a public URL and tunnel requests to your development environment. Refer to [ngrok's getting started guide](https://ngrok.com/docs#getting-started-expose) on how to create this tunnel. And when you get to hosting your app, we've collected some of the most common hosting providers Slack developers use to host their apps [on our API site](https://api.slack.com/docs/hosting). - -::: - -Finally, it's time to tell Slack what events we'd like to listen for. - -When an event occurs, Slack will send your app some information about the event, like the user that triggered it and the channel it occurred in. Your app will process the details and can respond accordingly. - -Navigate to **Event Subscriptions** on the left sidebar and toggle to enable. Under **Subscribe to Bot Events**, you can add events for your bot to respond to. There are four events related to messages: -- [`message.channels`](https://api.slack.com/events/message.channels) listens for messages in public channels that your app is added to -- [`message.groups`](https://api.slack.com/events/message.groups) listens for messages in 🔒 private channels that your app is added to -- [`message.im`](https://api.slack.com/events/message.im) listens for messages in your app's DMs with users -- [`message.mpim`](https://api.slack.com/events/message.mpim) listens for messages in multi-person DMs that your app is added to - -If you want your bot to listen to messages from everywhere it is added to, choose all four message events. After you’ve selected the events you want your bot to listen to, click the green **Save Changes** button. - ---- - -### Listening and responding to a message {#listening-and-responding-to-a-message} -Your app is now ready for some logic. Let's start by using the `message()` method to attach a listener for messages. - -The following example listens and responds to all messages in channels/DMs where your app has been added that contain the word "hello": - -```python -import os -from slack_bolt import App - -# Initializes your app with your bot token and signing secret -app = App( - token=os.environ.get("SLACK_BOT_TOKEN"), - signing_secret=os.environ.get("SLACK_SIGNING_SECRET") -) - -# Listens to incoming messages that contain "hello" -# To learn available listener arguments, -# visit https://tools.slack.dev/bolt-python/api-docs/slack_bolt/kwargs_injection/args.html -@app.message("hello") -def message_hello(message, say): - # say() sends a message to the channel where the event was triggered - say(f"Hey there <@{message['user']}>!") - -# Start your app -if __name__ == "__main__": - app.start(port=int(os.environ.get("PORT", 3000))) -``` - -If you restart your app, so long as your bot user has been added to the channel/DM, when you send any message that contains "hello", it will respond. - -This is a basic example, but it gives you a place to start customizing your app based on your own goals. Let's try something a little more interactive by sending a button rather than plain text. - ---- - -### Sending and responding to actions {#sending-and-responding-to-actions} - -To use features like buttons, select menus, datepickers, modals, and shortcuts, you’ll need to enable interactivity. Similar to events, you'll need to specify a URL for Slack to send the action (such as *user clicked a button*). - -Back on your app configuration page, click on **Interactivity & Shortcuts** on the left side. You'll see that there's another **Request URL** box. - -:::tip - -By default, Bolt is configured to use the same endpoint for interactive components that it uses for events, so use the same request URL as above (for example, `https://8e8ec2d7.ngrok.io/slack/events`). Press the **Save Changes** button in the lower right hand corner, and that's it. Your app is set up to handle interactivity! - -::: - -![Configuring a Request URL](/img/boltpy/request-url-config.png "Configuring a Request URL") - -When interactivity is enabled, interactions with shortcuts, modals, or interactive components (such as buttons, select menus, and datepickers) will be sent to your app as events. - -Now, let's go back to your app's code and add logic to handle those events: -- First, we'll send a message that contains an interactive component (in this case a button) -- Next, we'll listen for the action of a user clicking the button before responding - -Below, the code from the last section is modified to send a message containing a button rather than just a string: - -```python -import os -from slack_bolt import App - -# Initializes your app with your bot token and signing secret -app = App( - token=os.environ.get("SLACK_BOT_TOKEN"), - signing_secret=os.environ.get("SLACK_SIGNING_SECRET") -) - -# Listens to incoming messages that contain "hello" -@app.message("hello") -def message_hello(message, say): - # say() sends a message to the channel where the event was triggered - say( - blocks=[ - { - "type": "section", - "text": {"type": "mrkdwn", "text": f"Hey there <@{message['user']}>!"}, - "accessory": { - "type": "button", - "text": {"type": "plain_text", "text": "Click Me"}, - "action_id": "button_click" - } - } - ], - text=f"Hey there <@{message['user']}>!" - ) - -# Start your app -if __name__ == "__main__": - app.start(port=int(os.environ.get("PORT", 3000))) -``` - -The value inside of `say()` is now an object that contains an array of `blocks`. Blocks are the building components of a Slack message and can range from text to images to datepickers. In this case, your app will respond with a section block that includes a button as an accessory. Since we're using `blocks`, the `text` is a fallback for notifications and accessibility. - -You'll notice in the button `accessory` object, there is an `action_id`. This will act as a unique identifier for the button so your app can specify what action it wants to respond to. - -:::tip - -The [Block Kit Builder](https://app.slack.com/block-kit-builder) is an simple way to prototype your interactive messages. The builder lets you (or anyone on your team) mockup messages and generates the corresponding JSON that you can paste directly in your app. - -::: - -Now, if you restart your app and say "hello" in a channel your app is in, you'll see a message with a button. But if you click the button, nothing happens (*yet!*). - -Let's add a handler to send a followup message when someone clicks the button: - -```python -import os -from slack_bolt import App - -# Initializes your app with your bot token and signing secret -app = App( - token=os.environ.get("SLACK_BOT_TOKEN"), - signing_secret=os.environ.get("SLACK_SIGNING_SECRET") -) - -# Listens to incoming messages that contain "hello" -@app.message("hello") -def message_hello(message, say): - # say() sends a message to the channel where the event was triggered - say( - blocks=[ - { - "type": "section", - "text": {"type": "mrkdwn", "text": f"Hey there <@{message['user']}>!"}, - "accessory": { - "type": "button", - "text": {"type": "plain_text", "text": "Click Me"}, - "action_id": "button_click" - } - } - ], - text=f"Hey there <@{message['user']}>!" - ) - -@app.action("button_click") -def action_button_click(body, ack, say): - # Acknowledge the action - ack() - say(f"<@{body['user']['id']}> clicked the button") - -# Start your app -if __name__ == "__main__": - app.start(port=int(os.environ.get("PORT", 3000))) -``` - -You can see that we used `app.action()` to listen for the `action_id` that we named `button_click`. If you restart your app and click the button, you'll see a new message from your app that says you clicked the button. - ---- - -### Next steps {#next-steps} -You just built your first [Bolt for Python app](https://github.com/slackapi/bolt-python/tree/main/examples/getting_started)! 🎉 - -Now that you have a basic app up and running, you can start exploring how to make your Bolt app stand out. Here are some ideas about what to explore next: - -* Read through the _Basic concepts_ to learn about the different methods and features your Bolt app has access to. - -* Explore the different events your bot can listen to with the [`app.event()` method](/concepts/event-listening). All of the events are listed [on the API site](https://api.slack.com/events). - -* Bolt allows you to [call Web API methods](/concepts/web-api) with the client attached to your app. There are [over 220 methods](https://api.slack.com/methods) on our API site. - -* Learn more about the different token types [on our API site](https://api.slack.com/docs/token-types). Your app may need different tokens depending on the actions you want it to perform. If you are using Socket Mode instead of HTTP, an additional (`xapp`) token with `connections:write` scopes is required. \ No newline at end of file diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js deleted file mode 100644 index d6c3aa5a7..000000000 --- a/docs/docusaurus.config.js +++ /dev/null @@ -1,92 +0,0 @@ -import { themes as prismThemes } from 'prism-react-renderer'; -const footer = require('./footerConfig'); -const navbar = require('./navbarConfig'); - -/** @type {import('@docusaurus/types').Config} */ -const config = { - title: "Bolt for Python", - tagline: "Official frameworks, libraries, and SDKs for Slack developers", - favicon: "img/favicon.ico", - url: "https://tools.slack.dev", - baseUrl: "/bolt-python/", - organizationName: "slackapi", - projectName: "bolt-python", - - onBrokenLinks: "ignore", - onBrokenAnchors: "warn", - onBrokenMarkdownLinks: "warn", - - i18n: { - defaultLocale: "en", - locales: ["en", "ja-jp"], - }, - - presets: [ - [ - "classic", - /** @type {import('@docusaurus/preset-classic').Options} */ - ({ - docs: { - path: "content", - breadcrumbs: false, - routeBasePath: "/", // Serve the docs at the site's root - sidebarPath: "./sidebars.js", - editUrl: "https://github.com/slackapi/bolt-python/tree/main/docs", - }, - blog: false, - theme: { - customCss: "./src/css/custom.css", - }, - }), - ], - ], - - plugins: [ - "docusaurus-theme-github-codeblock", - [ - "@docusaurus/plugin-client-redirects", - { - redirects: [ - { - to: "/getting-started", - from: ["/tutorial/getting-started"], - }, - { - to: "/", - from: ["/concepts", "/concepts/basic", "/concepts/advanced"], - }, - ], - }, - ], - ], - - themeConfig: - /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ - ({ - colorMode: { - respectPrefersColorScheme: true, - }, - docs: { - sidebar: { - autoCollapseCategories: true, - }, - }, - navbar, - footer, - prism: { - // switch to alucard when available in prism? - theme: prismThemes.github, - darkTheme: prismThemes.dracula, - }, - codeblock: { - showGithubLink: true, - githubLinkLabel: "View on GitHub", - }, - // announcementBar: { - // id: `announcementBar`, - // content: `🎉️ Version 2.26.0 of the developer tools for the Slack automations platform is here! 🎉️ `, - // }, - }), -}; - -export default config; diff --git a/docs/english/_sidebar.json b/docs/english/_sidebar.json new file mode 100644 index 000000000..aa75b0c15 --- /dev/null +++ b/docs/english/_sidebar.json @@ -0,0 +1,206 @@ +[ + { + "type": "doc", + "id": "tools/bolt-python/index", + "label": "Bolt for Python", + "className": "sidebar-title" + }, + "tools/bolt-python/getting-started", + { "type": "html", "value": "
" }, + "tools/bolt-python/creating-an-app", + { + "type": "category", + "label": "AI & Agents", + "link": { + "type": "doc", + "id": "tools/bolt-python/concepts/adding-agent-features" + }, + "items": [ + "tools/bolt-python/concepts/adding-agent-features", + "tools/bolt-python/concepts/using-the-assistant-class" + ] + }, + { + "type": "category", + "label": "Slack API calls", + "items": [ + "tools/bolt-python/concepts/message-sending", + "tools/bolt-python/concepts/web-api" + ] + }, + { + "type": "category", + "label": "Events", + "items": [ + "tools/bolt-python/concepts/message-listening", + "tools/bolt-python/concepts/event-listening" + ] + }, + { + "type": "category", + "label": "App UI & Interactivity", + "items": [ + "tools/bolt-python/concepts/acknowledge", + "tools/bolt-python/concepts/shortcuts", + "tools/bolt-python/concepts/commands", + "tools/bolt-python/concepts/actions", + "tools/bolt-python/concepts/opening-modals", + "tools/bolt-python/concepts/updating-pushing-views", + "tools/bolt-python/concepts/view-submissions", + "tools/bolt-python/concepts/select-menu-options", + "tools/bolt-python/concepts/app-home" + ] + }, + { + "type": "category", + "label": "Custom Steps", + "items": [ + "tools/bolt-python/concepts/custom-steps", + "tools/bolt-python/concepts/custom-steps-dynamic-options" + ] + }, + { + "type": "category", + "label": "App Configuration", + "items": [ + "tools/bolt-python/concepts/socket-mode", + "tools/bolt-python/concepts/errors", + "tools/bolt-python/concepts/logging", + "tools/bolt-python/concepts/async" + ] + }, + { + "type": "category", + "label": "Middleware & Context", + "items": [ + "tools/bolt-python/concepts/global-middleware", + "tools/bolt-python/concepts/listener-middleware", + "tools/bolt-python/concepts/context" + ] + }, + "tools/bolt-python/concepts/lazy-listeners", + { + "type": "category", + "label": "Adaptors", + "items": [ + "tools/bolt-python/concepts/adapters", + "tools/bolt-python/concepts/custom-adapters" + ] + }, + { + "type": "category", + "label": "Authorization & Security", + "items": [ + "tools/bolt-python/concepts/authenticating-oauth", + "tools/bolt-python/concepts/authorization", + "tools/bolt-python/concepts/token-rotation" + ] + }, + "tools/bolt-python/experiments", + { + "type": "category", + "label": "Legacy", + "items": ["tools/bolt-python/legacy/steps-from-apps"] + }, + { "type": "html", "value": "
" }, + { + "type": "category", + "label": "Tutorials", + "items": [ + "tools/bolt-python/tutorial/ai-chatbot/ai-chatbot", + "tools/bolt-python/tutorial/order-confirmation/order-confirmation", + "tools/bolt-python/tutorial/custom-steps", + "tools/bolt-python/tutorial/custom-steps-for-jira/custom-steps-for-jira", + "tools/bolt-python/tutorial/custom-steps-workflow-builder-new/custom-steps-workflow-builder-new", + "tools/bolt-python/tutorial/custom-steps-workflow-builder-existing/custom-steps-workflow-builder-existing", + "tools/bolt-python/tutorial/modals/modals" + ] + }, + { "type": "html", "value": "
" }, + { + "type": "link", + "label": "Reference", + "href": "https://docs.slack.dev/tools/bolt-python/reference/index.html" + }, + { "type": "html", "value": "
" }, + { + "type": "category", + "label": "日本語 (日本)", + "items": [ + "tools/bolt-python/ja-jp/getting-started", + { + "type": "category", + "label": "Slack API コール", + "items": [ + "tools/bolt-python/ja-jp/concepts/message-sending", + "tools/bolt-python/ja-jp/concepts/web-api" + ] + }, + { + "type": "category", + "label": "イベント API", + "items": [ + "tools/bolt-python/ja-jp/concepts/message-listening", + "tools/bolt-python/ja-jp/concepts/event-listening" + ] + }, + { + "type": "category", + "label": "インタラクティビティ & ショートカット", + "items": [ + "tools/bolt-python/ja-jp/concepts/acknowledge", + "tools/bolt-python/ja-jp/concepts/shortcuts", + "tools/bolt-python/ja-jp/concepts/commands", + "tools/bolt-python/ja-jp/concepts/actions", + "tools/bolt-python/ja-jp/concepts/opening-modals", + "tools/bolt-python/ja-jp/concepts/updating-pushing-views", + "tools/bolt-python/ja-jp/concepts/view-submissions", + "tools/bolt-python/ja-jp/concepts/select-menu-options", + "tools/bolt-python/ja-jp/concepts/app-home" + ] + }, + { + "type": "category", + "label": "App の設定", + "items": [ + "tools/bolt-python/ja-jp/concepts/socket-mode", + "tools/bolt-python/ja-jp/concepts/errors", + "tools/bolt-python/ja-jp/concepts/logging", + "tools/bolt-python/ja-jp/concepts/async" + ] + }, + { + "type": "category", + "label": "ミドルウェア & コンテキスト", + "items": [ + "tools/bolt-python/ja-jp/concepts/global-middleware", + "tools/bolt-python/ja-jp/concepts/listener-middleware", + "tools/bolt-python/ja-jp/concepts/context" + ] + }, + "tools/bolt-python/ja-jp/concepts/lazy-listeners", + { + "type": "category", + "label": "アダプター", + "items": [ + "tools/bolt-python/ja-jp/concepts/adapters", + "tools/bolt-python/ja-jp/concepts/custom-adapters" + ] + }, + { + "type": "category", + "label": "認可 & セキュリティ", + "items": [ + "tools/bolt-python/ja-jp/concepts/authenticating-oauth", + "tools/bolt-python/ja-jp/concepts/authorization", + "tools/bolt-python/ja-jp/concepts/token-rotation" + ] + }, + { + "type": "category", + "label": "レガシー(非推奨)", + "items": ["tools/bolt-python/ja-jp/legacy/steps-from-apps"] + } + ] + } +] diff --git a/docs/content/basic/acknowledge.md b/docs/english/concepts/acknowledge.md similarity index 61% rename from docs/content/basic/acknowledge.md rename to docs/english/concepts/acknowledge.md index b1d2000ef..57b346bd3 100644 --- a/docs/content/basic/acknowledge.md +++ b/docs/english/concepts/acknowledge.md @@ -1,22 +1,19 @@ ---- -title: Acknowledging requests -lang: en -slug: /concepts/acknowledge ---- +# Acknowledging requests Actions, commands, shortcuts, options requests, and view submissions must **always** be acknowledged using the `ack()` function. This lets Slack know that the request was received so that it may update the Slack user interface accordingly. -Depending on the type of request, your acknowledgement may be different. For example, when acknowledging a menu selection associated with an external data source, you would call `ack()` with a list of relevant [options](https://api.slack.com/reference/block-kit/composition-objects#option). When acknowledging a view submission, you may supply a `response_action` as part of your acknowledgement to [update the view](/concepts/view_submissions). +Depending on the type of request, your acknowledgement may be different. For example, when acknowledging a menu selection associated with an external data source, you would call `ack()` with a list of relevant [options](/reference/block-kit/composition-objects/option-object/). When acknowledging a view submission, you may supply a `response_action` as part of your acknowledgement to [update the view](/tools/bolt-python/concepts/view-submissions). We recommend calling `ack()` right away before initiating any time-consuming processes such as fetching information from your database or sending a new message, since you only have 3 seconds to respond before Slack registers a timeout error. -:::info - -When working in a FaaS / serverless environment, our guidelines for when to `ack()` are different. See the section on [Lazy listeners (FaaS)](/concepts/lazy-listeners) for more detail on this. +:::info[When working in a FaaS / serverless environment, our guidelines for when to `ack()` are different. See the section on [Lazy listeners (FaaS)](/tools/bolt-python/concepts/lazy-listeners) for more detail on this.] ::: -Refer to [the module document](https://tools.slack.dev/bolt-python/api-docs/slack_bolt/kwargs_injection/args.html) to learn the available listener arguments. +Refer to [the module document](https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html) to learn the available listener arguments. + +## Example + ```python # Example of responding to an external_select options request @app.options("menu_selection") diff --git a/docs/english/concepts/actions.md b/docs/english/concepts/actions.md new file mode 100644 index 000000000..d7dfa6ba1 --- /dev/null +++ b/docs/english/concepts/actions.md @@ -0,0 +1,69 @@ +# Listening & responding to actions + +Your app can listen and respond to user actions, like button clicks, and menu selects, using the `action` method. + +## Listening to actions + +Actions can be filtered on an `action_id` parameter of type `str` or `re.Pattern`. The `action_id` parameter acts as a unique identifier for interactive components on the Slack platform. + +You'll notice in all `action()` examples, `ack()` is used. It is required to call the `ack()` function within an action listener to acknowledge that the request was received from Slack. This is discussed in the [acknowledging requests guide](/tools/bolt-python/concepts/acknowledge). + +Refer to [the module document](https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html) to learn the available listener arguments. + +```python +# Your listener will be called every time a block element with the action_id "approve_button" is triggered +@app.action("approve_button") +def update_message(ack): + ack() + # Update the message to reflect the action +``` + +### Listening to actions using a constraint object + +You can use a constraints object to listen to `block_id`s and `action_id`s (or any combination of them). Constraints in the object can be of type `str` or `re.Pattern`. + +```python +# Your function will only be called when the action_id matches 'select_user' AND the block_id matches 'assign_ticket' +@app.action({ + "block_id": "assign_ticket", + "action_id": "select_user" +}) +def update_message(ack, body, client): + ack() + + if "container" in body and "message_ts" in body["container"]: + client.reactions_add( + name="white_check_mark", + channel=body["channel"]["id"], + timestamp=body["container"]["message_ts"], + ) +``` + +## Responding to actions + +There are two main ways to respond to actions. The first (and most common) way is to use `say()`, which sends a message back to the conversation where the incoming request took place. + +The second way to respond to actions is using `respond()`, which is a utility to use the `response_url` associated with the action. + +Refer to [the module document](https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html) to learn the available listener arguments. + +```python +# Your listener will be called every time an interactive component with the action_id “approve_button” is triggered +@app.action("approve_button") +def approve_request(ack, say): + # Acknowledge action request + ack() + say("Request approved 👍") +``` + +### Using `respond()` method + +Since `respond()` is a utility for calling the `response_url`, it behaves in the same way. You can pass [all the message payload properties](/messaging/#payloads) as keyword arguments along with optional properties like `response_type` (which has a value of `"in_channel"` or `"ephemeral"`), `replace_original`, `delete_original`, `unfurl_links`, and `unfurl_media`. With that, your app can send a new message payload that will be published back to the source of the original interaction. + +```python +# Listens to actions triggered with action_id of “user_select” +@app.action("user_select") +def select_user(ack, action, respond): + ack() + respond(f"You selected <@{action['selected_user']}>") +``` \ No newline at end of file diff --git a/docs/content/advanced/adapters.md b/docs/english/concepts/adapters.md similarity index 56% rename from docs/content/advanced/adapters.md rename to docs/english/concepts/adapters.md index e8beb9c97..ad43a27da 100644 --- a/docs/content/advanced/adapters.md +++ b/docs/english/concepts/adapters.md @@ -1,18 +1,14 @@ ---- -title: Adapters -lang: en -slug: /concepts/adapters ---- +# Adapters +Adapters are responsible for handling and parsing incoming requests from Slack to conform to [`BoltRequest`](https://github.com/slackapi/bolt-python/blob/main/slack_bolt/request/request.py), then dispatching those requests to your Bolt app. -Adapters are responsible for handling and parsing incoming requests from Slack to conform to `BoltRequest`, then dispatching those requests to your Bolt app. - -By default, Bolt will use the built-in `HTTPServer` adapter. While this is okay for local development, it is not recommended for production. Bolt for Python includes a collection of built-in adapters that can be imported and used with your app. The built-in adapters support a variety of popular Python frameworks including Flask, Django, and Starlette among others. Adapters support the use of any production-ready web server of your choice. +By default, Bolt will use the built-in [`HTTPServer`](https://docs.python.org/3/library/http.server.html) adapter. While this is okay for local development, **it is not recommended for production**. Bolt for Python includes a collection of built-in adapters that can be imported and used with your app. The built-in adapters support a variety of popular Python frameworks including Flask, Django, and Starlette among others. Adapters support the use of any production-ready web server of your choice. To use an adapter, you'll create an app with the framework of your choosing and import its corresponding adapter. Then you'll initialize the adapter instance and call its function that handles and parses incoming requests. -The full list adapters, as well as configuration and sample usage, can be found within the repository's `examples` folder. +The full list adapters, as well as configuration and sample usage, can be found within the repository's [`examples`](https://github.com/slackapi/bolt-python/tree/main/examples) +## Example ```python from slack_bolt import App diff --git a/docs/english/concepts/adding-agent-features.md b/docs/english/concepts/adding-agent-features.md new file mode 100644 index 000000000..f420a219a --- /dev/null +++ b/docs/english/concepts/adding-agent-features.md @@ -0,0 +1,793 @@ +--- +sidebar_label: Adding agent features +--- + +# Adding agent features with Bolt for Python + +:::tip[Check out the Support Agent sample app] +The code snippets throughout this guide are from our [Support Agent sample app](https://github.com/slack-samples/bolt-python-support-agent), Casey, which supports integration with Pydantic, Anthropic, and OpenAI. + +View our [agent quickstart](/ai/agent-quickstart) to get up and running with Casey. Otherwise, read on for exploration and explanation of agent-focused Bolt features found within Casey. +::: + +Your agent can utilize features applicable to messages throughout Slack, like [chat streaming](#text-streaming) and [feedback buttons](#adding-and-handling-feedback). They can also [utilize the `Assistant` class](/tools/bolt-python/concepts/using-the-assistant-class) for a side-panel view designed with AI in mind. + +If you're unfamiliar with using these feature within Slack, you may want to read the [API docs on the subject](/ai/). Then come back here to implement them with Bolt! + +--- + +## Slack MCP Server {#slack-mcp-server} + +Casey can harness the [Slack MCP Server](https://docs.slack.dev/ai/slack-mcp-server/developing) when deployed via an HTTP Server with OAuth. + +To enable the Slack MCP Server: + +1. Install [ngrok](https://ngrok.com/download) and start a tunnel: + +```sh +ngrok http 3000 +``` + +2. Copy the `https://*.ngrok-free.app` URL from the ngrok output. + +3. Update `manifest.json` for HTTP mode: + - Set `socket_mode_enabled` to `false` + - Replace `ngrok-free.app` with your ngrok domain (e.g. `YOUR_NGROK_SUBDOMAIN.ngrok-free.app`) + +4. Create a new local dev app: + +```sh +slack install -E local +``` + +5. Enable MCP for your app: + - Run `slack app settings` to open your app's settings + - Navigate to **Agents** in the left-side navigation + - Toggle **Model Context Protocol** on + +6. Update your `.env` OAuth environment variables: + - Run `slack app settings` to open App Settings + - Copy **Client ID**, **Client Secret**, and **Signing Secret** + - Update `SLACK_REDIRECT_URI` in `.env` with your ngrok domain + +```sh +SLACK_CLIENT_ID=YOUR_CLIENT_ID +SLACK_CLIENT_SECRET=YOUR_CLIENT_SECRET +SLACK_REDIRECT_URI=https://YOUR_NGROK_SUBDOMAIN.ngrok-free.app/slack/oauth_redirect +SLACK_SIGNING_SECRET=YOUR_SIGNING_SECRET +``` + +7. Start the app: + +```sh +slack run app_oauth.py +``` + +8. Click the install URL printed in the terminal to install the app to your workspace via OAuth. + +Your agent can now access the Slack MCP server! + +--- + +## Listening for user invocation + +Agents can be invoked throughout Slack, such as via @mentions in channels, messaging the agent, and using the assistant side panel. + + + + +```python +import re +from logging import Logger + +from slack_bolt import BoltContext, Say, SayStream, SetStatus +from slack_sdk import WebClient + +from agent import CaseyDeps, run_casey +from thread_context import conversation_store +from listeners.views.feedback_builder import build_feedback_blocks + + +def handle_app_mentioned( + client: WebClient, + context: BoltContext, + event: dict, + logger: Logger, + say: Say, + say_stream: SayStream, + set_status: SetStatus, +): + """Handle @Casey mentions in channels.""" + try: + channel_id = context.channel_id + text = event.get("text", "") + thread_ts = event.get("thread_ts") or event["ts"] + user_id = context.user_id + + # Strip the bot mention from the text + cleaned_text = re.sub(r"<@[A-Z0-9]+>", "", text).strip() + + if not cleaned_text: + say( + text="Hey there! How can I help you? Describe your IT issue and I'll do my best to assist.", + thread_ts=thread_ts, + ) + return + + # Add eyes reaction only to the first message (not threaded replies) + if not event.get("thread_ts"): + client.reactions_add( + channel=channel_id, + timestamp=event["ts"], + name="eyes", + ) + ... +``` + + + + +```python +from logging import Logger + +from slack_bolt.context import BoltContext +from slack_bolt.context.say import Say +from slack_bolt.context.say_stream import SayStream +from slack_bolt.context.set_status import SetStatus +from slack_sdk import WebClient + +from agent import CaseyDeps, run_casey_agent +from thread_context import session_store +from listeners.views.feedback_builder import build_feedback_blocks + + +def handle_message( + client: WebClient, + context: BoltContext, + event: dict, + logger: Logger, + say: Say, + say_stream: SayStream, + set_status: SetStatus, +): + """Handle messages sent to Casey via DM or in threads the bot is part of.""" + # Issue submissions are posted by the bot with metadata so the message + # handler can run the agent on behalf of the original user. + is_issue_submission = ( + event.get("metadata", {}).get("event_type") == "issue_submission" + ) + + # Skip message subtypes (edits, deletes, etc.) and bot messages that + # are not issue submissions. + if event.get("subtype"): + return + if event.get("bot_id") and not is_issue_submission: + return + + is_dm = event.get("channel_type") == "im" + is_thread_reply = event.get("thread_ts") is not None + + if is_dm: + pass + elif is_thread_reply: + # Channel thread replies are handled only if the bot is already engaged + session = session_store.get_session(context.channel_id, event["thread_ts"]) + if session is None: + return + else: + # Top-level channel messages are handled by app_mentioned + return + + try: + channel_id = context.channel_id + text = event.get("text", "") + thread_ts = event.get("thread_ts") or event["ts"] + + # Get session ID for conversation context + existing_session_id = session_store.get_session(channel_id, thread_ts) + + # Add eyes reaction only to the first message (DMs only — channel + # threads already have the reaction from the initial app_mention) + if is_dm and not existing_session_id: + client.reactions_add( + channel=channel_id, + timestamp=event["ts"], + name="eyes", + ) + + ... +``` + + + + + +:::tip[Using the Assistant side panel] +The assistant messaging experience requires additional setup. See the [Assistant class guide](/tools/bolt-python/concepts/using-the-assistant-class). +::: + +How you greet a user and set suggested prompts when they open the agent's DM depends on which [messaging experience](/ai/developing-agents) your app uses: + +* The **agent messaging experience** (`agent_view`) is the default and the only experience available to apps going forward. Conversations happen in the app's **Messages** tab, so you listen for the [`app_home_opened`](/reference/events/app_home_opened) event and check for the `messages` tab to detect when a user opens the DM, then set suggested prompts at the top of the tab (no `thread_ts` required). Casey uses this approach. +* The **assistant messaging experience** (`assistant_view`) is the legacy experience, with separate **Chat** and **History** tabs. You listen for the [`assistant_thread_started`](/reference/events/assistant_thread_started) event to detect a thread, then set suggested prompts on it. Existing apps can continue to use this but should migrate to the agent messaging experience. + +Refer to the [agent messaging experience changelog entry](/changelog/2026/06/30/agent-messages-tab) for the full list of changes and a migration checklist. + + + + +```python +from logging import Logger + +from slack_bolt import BoltContext +from slack_sdk import WebClient + +SUGGESTED_PROMPTS = [ + {"title": "Reset Password", "message": "I need to reset my password"}, + {"title": "Request Access", "message": "I need access to a system or tool"}, + {"title": "Network Issues", "message": "I'm having network connectivity issues"}, +] + + +def handle_app_home_opened( + client: WebClient, event: dict, context: BoltContext, logger: Logger +): + """Handle app_home_opened events. + + Under agent_view, this event fires for both the Home tab and the Messages + tab (the agent DM). Branch on ``event["tab"]``. + """ + try: + if event.get("tab") == "messages": + # Suggested prompts pin to the top of the Messages tab; no thread_ts required. + client.assistant_threads_setSuggestedPrompts( + channel_id=event["channel"], + title="How can I help you today?", + prompts=SUGGESTED_PROMPTS, + ) + return + + # event["tab"] == "home": publish your App Home Block Kit view here + except Exception as e: + logger.exception(f"Failed to handle app_home_opened: {e}") +``` + + + + +```python +from logging import Logger + +from slack_bolt.context.set_suggested_prompts import SetSuggestedPrompts + +SUGGESTED_PROMPTS = [ + {"title": "Reset Password", "message": "I need to reset my password"}, + {"title": "Request Access", "message": "I need access to a system or tool"}, + {"title": "Network Issues", "message": "I'm having network connectivity issues"}, +] + + +def handle_assistant_thread_started( + set_suggested_prompts: SetSuggestedPrompts, logger: Logger +): + """Handle assistant thread started events by setting suggested prompts.""" + try: + set_suggested_prompts( + prompts=SUGGESTED_PROMPTS, + title="How can I help you today?", + ) + except Exception as e: + logger.exception(f"Failed to handle assistant thread started: {e}") +``` + + + + + + + +--- + +## Setting status {#setting-assistant-status} + +Your app can show actions are happening behind the scenes by setting its thread status. + +```python +def handle_app_mentioned( + set_status: SetStatus, + ... +): + set_status( + status="Thinking...", + loading_messages=[ + "Teaching the hamsters to type faster…", + "Untangling the internet cables…", + "Consulting the office goldfish…", + "Polishing up the response just for you…", + "Convincing the AI to stop overthinking…", + ], + ) +``` + +--- + +## Streaming messages {#text-streaming} + +You can have your app's messages stream in to replicate conventional agent behavior. Bolt for Python provides a `say_stream` utility as a listener argument available for `app.event` and `app.message` listeners. + +The `say_stream` utility streamlines calling the Python Slack SDK's [`WebClient.chat_stream`](https://docs.slack.dev/tools/python-slack-sdk/reference/web/client.html#slack_sdk.web.client.WebClient.chat_stream) helper utility by sourcing parameter values from the relevant event payload. + +| Parameter | Value | +|---|---| +| `channel_id` | Sourced from the event payload. +| `thread_ts` | Sourced from the event payload. Falls back to the `ts` value if available. +| `recipient_team_id` | Sourced from the event `team_id` (`enterprise_id` if the app is installed on an org). +| `recipient_user_id` | Sourced from the `user_id` of the event. + +If either `channel_id` or `thread_ts` cannot be sourced, the utility will be `None`. + +```python +streamer = say_stream() +streamer.append(markdown_text="Here's my response...") +streamer.append(markdown_text="And here's more...") +streamer.stop() +``` + +--- + +## Adding and handling feedback {#adding-and-handling-feedback} + +You can use the [feedback buttons block element](/reference/block-kit/block-elements/feedback-buttons-element/) to allow users to immediately provide feedback regarding the app's responses. Here's what the feedback buttons look like from the Support Agent sample app: + +```py title=".../listeners/views/feedback_builder.py" +from slack_sdk.models.blocks import ( + Block, + ContextActionsBlock, + FeedbackButtonObject, + FeedbackButtonsElement, +) + + +def build_feedback_blocks() -> list[Block]: + """Build feedback blocks with thumbs up/down buttons.""" + return [ + ContextActionsBlock( + elements=[ + FeedbackButtonsElement( + action_id="feedback", + positive_button=FeedbackButtonObject( + text="Good Response", + accessibility_label="Submit positive feedback on this response", + value="good-feedback", + ), + negative_button=FeedbackButtonObject( + text="Bad Response", + accessibility_label="Submit negative feedback on this response", + value="bad-feedback", + ), + ) + ] + ) + ] +``` + +That feedback block is then rendered at the bottom of your app's message via the `say_stream` utility. + +```py +... + # Stream response in thread with feedback buttons + streamer = say_stream() + streamer.append(markdown_text=result.output) + feedback_blocks = build_feedback_blocks() + streamer.stop(blocks=feedback_blocks) +... +``` + +You can also add a response for when the user provides feedback. + +```python title="...listeners/actions/feedback_button.py" +from logging import Logger + +from slack_bolt import Ack, BoltContext +from slack_sdk import WebClient + + +def handle_feedback_button( + ack: Ack, body: dict, client: WebClient, context: BoltContext, logger: Logger +): + """Handle thumbs up/down feedback on Casey's responses.""" + ack() + + try: + channel_id = context.channel_id + user_id = context.user_id + message_ts = body["message"]["ts"] + feedback_value = body["actions"][0]["value"] + + if feedback_value == "good-feedback": + client.chat_postEphemeral( + channel=channel_id, + user=user_id, + thread_ts=message_ts, + text="Glad that was helpful! :tada:", + ) + else: + client.chat_postEphemeral( + channel=channel_id, + user=user_id, + thread_ts=message_ts, + text="Sorry that wasn't helpful. :slightly_frowning_face: Try rephrasing your question or I can create a support ticket for you.", + ) + + logger.debug( + f"Feedback received: value={feedback_value}, message_ts={message_ts}" + ) + except Exception as e: + logger.exception(f"Failed to handle feedback: {e}") +``` + +--- + +## Full example + +Putting all those concepts together results in a dynamic agent ready to helpfully respond. + + +
+Full example + + + +```python title="app_mentioned.py" +import re +from logging import Logger + +from slack_bolt import BoltContext, Say, SayStream, SetStatus +from slack_sdk import WebClient + +from agent import CaseyDeps, run_casey +from thread_context import conversation_store +from listeners.views.feedback_builder import build_feedback_blocks + + +def handle_app_mentioned( + client: WebClient, + context: BoltContext, + event: dict, + logger: Logger, + say: Say, + say_stream: SayStream, + set_status: SetStatus, +): + """Handle @Casey mentions in channels.""" + try: + channel_id = context.channel_id + text = event.get("text", "") + thread_ts = event.get("thread_ts") or event["ts"] + user_id = context.user_id + + # Strip the bot mention from the text + cleaned_text = re.sub(r"<@[A-Z0-9]+>", "", text).strip() + + if not cleaned_text: + say( + text="Hey there! How can I help you? Describe your IT issue and I'll do my best to assist.", + thread_ts=thread_ts, + ) + return + + # Add eyes reaction only to the first message (not threaded replies) + if not event.get("thread_ts"): + client.reactions_add( + channel=channel_id, + timestamp=event["ts"], + name="eyes", + ) + + # Set assistant thread status with loading messages + set_status( + status="Thinking...", + loading_messages=[ + "Teaching the hamsters to type faster…", + "Untangling the internet cables…", + "Consulting the office goldfish…", + "Polishing up the response just for you…", + "Convincing the AI to stop overthinking…", + ], + ) + + # Get conversation history + history = conversation_store.get_history(channel_id, thread_ts) + + # Run the agent + deps = CaseyDeps( + client=client, + user_id=user_id, + channel_id=channel_id, + thread_ts=thread_ts, + message_ts=event["ts"], + user_token=context.user_token, + ) + result = run_casey(cleaned_text, deps, message_history=history) + + # Stream response in thread with feedback buttons + streamer = say_stream() + streamer.append(markdown_text=result.output) + feedback_blocks = build_feedback_blocks() + streamer.stop(blocks=feedback_blocks) + + # Store conversation history + conversation_store.set_history(channel_id, thread_ts, result.all_messages()) + + except Exception as e: + logger.exception(f"Failed to handle app mention: {e}") + say( + text=f":warning: Something went wrong! ({e})", + thread_ts=event.get("thread_ts") or event["ts"], + ) +``` + + + + +```python title="app_mentioned.py" +import re +from logging import Logger + +from slack_bolt.context import BoltContext +from slack_bolt.context.say import Say +from slack_bolt.context.say_stream import SayStream +from slack_bolt.context.set_status import SetStatus +from slack_sdk import WebClient + +from agent import CaseyDeps, run_casey_agent +from thread_context import session_store +from listeners.views.feedback_builder import build_feedback_blocks + + +def handle_app_mentioned( + client: WebClient, + context: BoltContext, + event: dict, + logger: Logger, + say: Say, + say_stream: SayStream, + set_status: SetStatus, +): + """Handle @Casey mentions in channels.""" + try: + channel_id = context.channel_id + text = event.get("text", "") + thread_ts = event.get("thread_ts") or event["ts"] + + # Strip the bot mention from the text + cleaned_text = re.sub(r"<@[A-Z0-9]+>", "", text).strip() + + if not cleaned_text: + say( + text="Hey there! How can I help you? Describe your IT issue and I'll do my best to assist.", + thread_ts=thread_ts, + ) + return + + # Add eyes reaction only to the first message (not threaded replies) + if not event.get("thread_ts"): + client.reactions_add( + channel=channel_id, + timestamp=event["ts"], + name="eyes", + ) + + # Set assistant thread status with loading messages + set_status( + status="Thinking...", + loading_messages=[ + "Teaching the hamsters to type faster…", + "Untangling the internet cables…", + "Consulting the office goldfish…", + "Polishing up the response just for you…", + "Convincing the AI to stop overthinking…", + ], + ) + + # Get session ID for conversation context + existing_session_id = session_store.get_session(channel_id, thread_ts) + + # Run the agent with deps for tool access + deps = CaseyDeps( + client=client, + user_id=context.user_id, + channel_id=channel_id, + thread_ts=thread_ts, + message_ts=event["ts"], + user_token=context.user_token, + ) + response_text, new_session_id = run_casey_agent( + cleaned_text, session_id=existing_session_id, deps=deps + ) + + # Stream response in thread with feedback buttons + streamer = say_stream() + streamer.append(markdown_text=response_text) + feedback_blocks = build_feedback_blocks() + streamer.stop(blocks=feedback_blocks) + + # Store session ID for future context + if new_session_id: + session_store.set_session(channel_id, thread_ts, new_session_id) + + except Exception as e: + logger.exception(f"Failed to handle app mention: {e}") + say( + text=f":warning: Something went wrong! ({e})", + thread_ts=event.get("thread_ts") or event["ts"], + ) +``` + + + +```python title="app_mentioned.py" +import re +from logging import Logger + +from slack_bolt import BoltContext, Say, SayStream, SetStatus +from slack_sdk import WebClient + +from agent import CaseyDeps, run_casey +from thread_context import conversation_store +from listeners.views.feedback_builder import build_feedback_blocks + + +def handle_app_mentioned( + client: WebClient, + context: BoltContext, + event: dict, + logger: Logger, + say: Say, + say_stream: SayStream, + set_status: SetStatus, +): + """Handle @Casey mentions in channels.""" + try: + channel_id = context.channel_id + text = event.get("text", "") + thread_ts = event.get("thread_ts") or event["ts"] + user_id = context.user_id + + # Strip the bot mention from the text + cleaned_text = re.sub(r"<@[A-Z0-9]+>", "", text).strip() + + if not cleaned_text: + say( + text="Hey there! How can I help you? Describe your IT issue and I'll do my best to assist.", + thread_ts=thread_ts, + ) + return + + # Add eyes reaction only to the first message (not threaded replies) + if not event.get("thread_ts"): + client.reactions_add( + channel=channel_id, + timestamp=event["ts"], + name="eyes", + ) + + # Set assistant thread status with loading messages + set_status( + status="Thinking...", + loading_messages=[ + "Teaching the hamsters to type faster…", + "Untangling the internet cables…", + "Consulting the office goldfish…", + "Polishing up the response just for you…", + "Convincing the AI to stop overthinking…", + ], + ) + + # Get conversation history + history = conversation_store.get_history(channel_id, thread_ts) + + # Build input for the agent + if history: + input_items = history + [{"role": "user", "content": cleaned_text}] + else: + input_items = cleaned_text + + # Run the agent + deps = CaseyDeps( + client=client, + user_id=user_id, + channel_id=channel_id, + thread_ts=thread_ts, + message_ts=event["ts"], + user_token=context.user_token, + ) + result = run_casey(input_items, deps) + + # Stream response in thread with feedback buttons + streamer = say_stream() + streamer.append(markdown_text=result.final_output) + feedback_blocks = build_feedback_blocks() + streamer.stop(blocks=feedback_blocks) + + # Store conversation history + conversation_store.set_history(channel_id, thread_ts, result.to_input_list()) + + except Exception as e: + logger.exception(f"Failed to handle app mention: {e}") + say( + text=f":warning: Something went wrong! ({e})", + thread_ts=event.get("thread_ts") or event["ts"], + ) +``` + + + +
+ +--- + +## Onward: adding custom tools + +Casey comes with test tools and simulated systems. You can extend it with custom tools to make it a fully functioning Slack agent. + +In this example, we'll add a tool that makes live calls to check the GitHub status. + +1. Create `agent/tools/{tool-name}.py` and define the tool with the `@tool` decorator: + +```python title="agent/tools/check_github_status.py" +from claude_agent_sdk import tool +import httpx + +@tool( + name="check_github_status", + description="Check GitHub's current operational status", + input_schema={}, +) +async def check_github_status_tool(args): + """Check if GitHub is operational.""" + async with httpx.AsyncClient() as client: + response = await client.get("https://www.githubstatus.com/api/v2/status.json") + data = response.json() + status = data["status"]["indicator"] + description = data["status"]["description"] + + return { + "content": [ + { + "type": "text", + "text": f"**GitHub Status** — {status}\n{description}", + } + ] + } +``` + +2. Import the tool in `agent/casey.py`: + +```python title="agent/casey.py" +from agent.tools import check_github_status_tool +``` + +3. Register in `casey_tools_server`: + +```python title="agent/casey.py" +casey_tools_server = create_sdk_mcp_server( + name="casey-tools", + version="1.0.0", + tools=[ + check_github_status_tool, # Add here + # ... other tools + ], +) +``` + +4. Add to `CASEY_TOOLS`: + +```python title="agent/casey.py" +CASEY_TOOLS = [ + "check_github_status", # Add here + # ... other tools +] +``` + +Use this example as a jumping off point for building out an agent with the capabilities you need! \ No newline at end of file diff --git a/docs/content/basic/app-home.md b/docs/english/concepts/app-home.md similarity index 53% rename from docs/content/basic/app-home.md rename to docs/english/concepts/app-home.md index a7a6a1f02..f4f15337f 100644 --- a/docs/content/basic/app-home.md +++ b/docs/english/concepts/app-home.md @@ -1,14 +1,13 @@ ---- -title: Publishing views to App Home -lang: en -slug: /concepts/app-home ---- +# Publishing views to App Home -Home tabs are customizable surfaces accessible via the sidebar and search that allow apps to display views on a per-user basis. After enabling App Home within your app configuration, home tabs can be published and updated by passing a `user_id` and view payload to the `views.publish` method. +[Home tabs](/surfaces/app-home) are customizable surfaces accessible via the sidebar and search that allow apps to display views on a per-user basis. After enabling App Home within your app configuration, home tabs can be published and updated by passing a `user_id` and [view payload](/reference/interaction-payloads/view-interactions-payload/#view_submission) to the [`views.publish`](/reference/methods/views.publish) method. -You can subscribe to the `app_home_opened` event to listen for when users open your App Home. +You can subscribe to the [`app_home_opened`](/reference/events/app_home_opened) event to listen for when users open your App Home. + +Refer to [the module document](https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html) to learn the available listener arguments. + +## Example -Refer to [the module document](https://tools.slack.dev/bolt-python/api-docs/slack_bolt/kwargs_injection/args.html) to learn the available listener arguments. ```python @app.event("app_home_opened") def update_home_tab(client, event, logger): @@ -32,7 +31,7 @@ def update_home_tab(client, event, logger): "type": "section", "text": { "type": "mrkdwn", - "text": "Learn how home tabs can be more useful and interactive ." + "text": "Learn how home tabs can be more useful and interactive ." } } ] diff --git a/docs/content/advanced/async.md b/docs/english/concepts/async.md similarity index 80% rename from docs/content/advanced/async.md rename to docs/english/concepts/async.md index 9544ad810..e6ae28fc6 100644 --- a/docs/content/advanced/async.md +++ b/docs/english/concepts/async.md @@ -1,14 +1,8 @@ ---- -title: Using async (asyncio) -lang: en -slug: /concepts/async ---- +# Using async (asyncio) +To use the async version of Bolt, you can import and initialize an `AsyncApp` instance (rather than `App`). `AsyncApp` relies on [AIOHTTP](https://docs.aiohttp.org) to make API requests, which means you'll need to install `aiohttp` (by adding to `requirements.txt` or running `pip install aiohttp`). -To use the async version of Bolt, you can import and initialize an `AsyncApp` instance (rather than `App`). `AsyncApp` relies on AIOHTTP to make API requests, which means you'll need to install `aiohttp` (by adding to `requirements.txt` or running `pip install aiohttp`). - -Sample async projects can be found within the repository's `examples` folder. - +Sample async projects can be found within the repository's [examples](https://github.com/slackapi/bolt-python/tree/main/examples) folder. ```python # Requirement: install aiohttp @@ -28,12 +22,7 @@ if __name__ == "__main__": app.start(3000) ``` -
- -Using other frameworks - - - +## Using other frameworks Internally `AsyncApp#start()` implements a [`AIOHTTP`](https://docs.aiohttp.org/) web server. If you prefer, you can use a framework other than `AIOHTTP` to handle incoming requests. @@ -48,7 +37,6 @@ pip install slack_bolt sanic uvicorn uvicorn async_app:api --reload --port 3000 --log-level debug ``` - ```python from slack_bolt.async_app import AsyncApp app = AsyncApp() @@ -76,5 +64,4 @@ async def endpoint(req: Request): if __name__ == "__main__": api.run(host="0.0.0.0", port=int(os.environ.get("PORT", 3000))) -``` -
+``` \ No newline at end of file diff --git a/docs/content/basic/authenticating-oauth.md b/docs/english/concepts/authenticating-oauth.md similarity index 88% rename from docs/content/basic/authenticating-oauth.md rename to docs/english/concepts/authenticating-oauth.md index 321803497..4ce9b205b 100644 --- a/docs/content/basic/authenticating-oauth.md +++ b/docs/english/concepts/authenticating-oauth.md @@ -1,18 +1,14 @@ ---- -title: Authenticating with OAuth -lang: en -slug: /concepts/authenticating-oauth ---- +# Authenticating with OAuth -Slack apps installed on multiple workspaces will need to implement OAuth, then store installation information (like access tokens) securely. By providing `client_id`, `client_secret`, `scopes`, `installation_store`, and `state_store` when initializing App, Bolt for Python will handle the work of setting up OAuth routes and verifying state. If you're implementing a custom adapter, you can make use of our [OAuth library](https://tools.slack.dev/python-slack-sdk/oauth/), which is what Bolt for Python uses under the hood. +Slack apps installed on multiple workspaces will need to implement OAuth, then store installation information (like access tokens) securely. By providing `client_id`, `client_secret`, `scopes`, `installation_store`, and `state_store` when initializing App, Bolt for Python will handle the work of setting up OAuth routes and verifying state. If you're implementing a custom adapter, you can make use of our [OAuth library](/tools/python-slack-sdk/oauth/), which is what Bolt for Python uses under the hood. Bolt for Python will create a **Redirect URL** `slack/oauth_redirect`, which Slack uses to redirect users after they complete your app's installation flow. You will need to add this **Redirect URL** in your app configuration settings under **OAuth and Permissions**. This path can be configured in the `OAuthSettings` argument described below. Bolt for Python will also create a `slack/install` route, where you can find an **Add to Slack** button for your app to perform direct installs of your app. If you need any additional authorizations (user tokens) from users inside a team when your app is already installed or a reason to dynamically generate an install URL, you can pass your own custom URL generator to `oauth_settings` as `authorize_url_generator`. -Bolt for Python automatically includes support for [org wide installations](https://api.slack.com/enterprise/apps) in version `1.1.0+`. Org wide installations can be enabled in your app configuration settings under **Org Level Apps**. +Bolt for Python automatically includes support for [org wide installations](/enterprise) in version `1.1.0+`. Org wide installations can be enabled in your app configuration settings under **Org Level Apps**. -To learn more about the OAuth installation flow with Slack, [read the API documentation](https://api.slack.com/authentication/oauth-v2). +To learn more about the OAuth installation flow with Slack, [read the API documentation](/authentication/installing-with-oauth). ```python import os @@ -35,10 +31,7 @@ app = App( ) ``` -
- -Customizing OAuth defaults - +## Customizing OAuth defaults You can override the default OAuth using `oauth_settings`, which can be passed in during the initialization of App. You can override the following: @@ -90,6 +83,4 @@ app = App( callback_options=callback_options, ), ) -``` - -
\ No newline at end of file +``` \ No newline at end of file diff --git a/docs/content/advanced/authorization.md b/docs/english/concepts/authorization.md similarity index 55% rename from docs/content/advanced/authorization.md rename to docs/english/concepts/authorization.md index 7a67d79ca..9d0086ca7 100644 --- a/docs/content/advanced/authorization.md +++ b/docs/english/concepts/authorization.md @@ -1,15 +1,11 @@ ---- -title: Authorization -lang: en -slug: /concepts/authorization ---- - +# Authorization Authorization is the process of determining which Slack credentials should be available while processing an incoming Slack request. -Apps installed on a single workspace can simply pass their bot token into the `App` constructor using the `token` parameter. However, if your app will be installed on multiple workspaces, you have two options. The easier option is to use the built-in OAuth support. This will handle setting up OAuth routes and verifying state. Read the section on [authenticating with OAuth](/concepts/authenticating-oauth) for details. +Apps installed on a single workspace can pass their bot token into the `App` constructor using the `token` parameter. However, if your app will be installed on multiple workspaces, you have two options: -For a more custom solution, you can set the `authorize` parameter to a function upon `App` instantiation. The `authorize` function should return [an instance of `AuthorizeResult`](https://github.com/slackapi/bolt-python/blob/main/slack_bolt/authorization/authorize_result.py), which contains information about who and where the request is coming from. +* Use the built-in OAuth support. This will handle setting up OAuth routes and verifying state. See [authenticating with OAuth](/tools/bolt-python/concepts/authenticating-oauth) for more details. +* Set the `authorize` parameter to a function upon `App` instantiation. The `authorize` function should return [an instance of `AuthorizeResult`](https://github.com/slackapi/bolt-python/blob/main/slack_bolt/authorization/authorize_result.py), which contains information about who and where the request is coming from. `AuthorizeResult` should have a few specific properties, all of type `str`: - Either **`bot_token`** (xoxb) *or* **`user_token`** (xoxp) are **required**. Most apps will use `bot_token` by default. Passing a token allows built-in functions (like `say()`) to work. @@ -17,6 +13,7 @@ For a more custom solution, you can set the `authorize` parameter to a function - **`enterprise_id`** and **`team_id`**, which can be found in requests sent to your app. - **`user_id`** only when using `user_token`. +## Example ```python import os @@ -66,3 +63,11 @@ app = App( authorize=authorize ) ``` + +## Handling failed token lookups {#handling-failed-token-lookups} + +In the event that you receive events from disconnected teams, make sure to gracefully drop these unauthorized payloads by returning `None` to silently drop the payload as follows. + +In the custom `authorize` callback, if a token lookup fails due to an `unknown team_id`, you should return `None` rather than raising an exception (such as `BoltUnauthorizedError`). Bolt's authorization middleware will recognize the `None` response as an authorization failure and immediately halt execution, silently dropping the payload without generating server error logs. + +Additionally, the `user_facing_authorize_error_message` parameter strictly controls the ephemeral message sent back to the end user via the Slack UI. It does not suppress internal server logs or exceptions. To achieve both the desired UI behavior and clean server logs, pair this parameter with returning `None` in your authorize function. diff --git a/docs/content/basic/commands.md b/docs/english/concepts/commands.md similarity index 73% rename from docs/content/basic/commands.md rename to docs/english/concepts/commands.md index 010ef32d2..cd772c57b 100644 --- a/docs/content/basic/commands.md +++ b/docs/english/concepts/commands.md @@ -1,18 +1,17 @@ ---- -title: Listening and responding to commands -lang: en -slug: /concepts/commands ---- +# Listening & responding to commands Your app can use the `command()` method to listen to incoming slash command requests. The method requires a `command_name` of type `str`. Commands must be acknowledged with `ack()` to inform Slack your app has received the request. -There are two ways to respond to slash commands. The first way is to use `say()`, which accepts a string or JSON payload. The second is `respond()` which is a utility for the `response_url`. These are explained in more depth in the [responding to actions](/concepts/action-respond) section. +There are two ways to respond to slash commands. The first way is to use `say()`, which accepts a string or JSON payload. The second is `respond()` which is a utility for the `response_url`. These are explained in more depth in the [responding to actions](/tools/bolt-python/concepts/actions) section. When setting up commands within your app configuration, you'll append `/slack/events` to your request URL. -Refer to [the module document](https://tools.slack.dev/bolt-python/api-docs/slack_bolt/kwargs_injection/args.html) to learn the available listener arguments. +Refer to [the module document](https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html) to learn the available listener arguments. + +## Example + ```python # The echo command simply echoes on command @app.command("/echo") @@ -20,4 +19,4 @@ def repeat_text(ack, respond, command): # Acknowledge command request ack() respond(f"{command['text']}") -``` \ No newline at end of file +``` diff --git a/docs/content/advanced/context.md b/docs/english/concepts/context.md similarity index 96% rename from docs/content/advanced/context.md rename to docs/english/concepts/context.md index 34d7819f7..46684ea28 100644 --- a/docs/content/advanced/context.md +++ b/docs/english/concepts/context.md @@ -1,14 +1,10 @@ ---- -title: Adding context -lang: en -slug: /concepts/context ---- - +# Adding context All listeners have access to a `context` dictionary, which can be used to enrich requests with additional information. Bolt automatically attaches information that is included in the incoming request, like `user_id`, `team_id`, `channel_id`, and `enterprise_id`. `context` is just a dictionary, so you can directly modify it. +## Example ```python # Listener middleware to fetch tasks from external system using user ID diff --git a/docs/content/advanced/custom-adapters.md b/docs/english/concepts/custom-adapters.md similarity index 92% rename from docs/content/advanced/custom-adapters.md rename to docs/english/concepts/custom-adapters.md index 313173750..21f7f33e0 100644 --- a/docs/content/advanced/custom-adapters.md +++ b/docs/english/concepts/custom-adapters.md @@ -1,11 +1,6 @@ ---- -title: Custom adapters -lang: en -slug: /concepts/custom-adapters ---- +# Custom adapters - -[Adapters](/concepts/adapters) are flexible and can be adjusted based on the framework you prefer. There are two necessary components of adapters: +[Adapters](/tools/bolt-python/concepts/adapters) are flexible and can be adjusted based on the framework you prefer. There are two necessary components of adapters: - `__init__(app: App)`: Constructor that accepts and stores an instance of the Bolt `App`. - `handle(req: Request)`: Function (typically named `handle()`) that receives incoming Slack requests, parses them to conform to an instance of [`BoltRequest`](https://github.com/slackapi/bolt-python/blob/main/slack_bolt/request/request.py), then dispatches them to the stored Bolt app. @@ -23,6 +18,7 @@ Your adapter will return [an instance of `BoltResponse`](https://github.com/slac For more in-depth examples of custom adapters, look at the implementations of the [built-in adapters](https://github.com/slackapi/bolt-python/tree/main/slack_bolt/adapter). +## Example ```python # Necessary imports for Flask diff --git a/docs/english/concepts/custom-steps-dynamic-options.md b/docs/english/concepts/custom-steps-dynamic-options.md new file mode 100644 index 000000000..9a152daa0 --- /dev/null +++ b/docs/english/concepts/custom-steps-dynamic-options.md @@ -0,0 +1,247 @@ +# Custom Steps dynamic options for Workflow Builder + +## Background {#background} + +[Legacy steps from apps](/changelog/2023-08-workflow-steps-from-apps-step-back) previously enabled Slack apps to create and process custom workflow steps, which could then be shared and used by anyone in Workflow Builder. To support your transition away from them, custom steps used as dynamic options are available. These allow you to use data defined when referencing the step in Workflow Builder as inputs to the step. + +## Example use case {#use-case} + +Let's say a builder wants to add a custom step in Workflow Builder that creates an issue in an external issue-tracking system. First, they'll need to specify a project. Once a project is selected, a project-specific list of fields can be presented to them to choose from when creating the issue. + +As a developer, dynamic options allow you to supply data to input parameters of custom steps so that you can provide builders with varying sets of fields based on the builders' selections. + +In this example, the primary step would invoke a separate project selection step that retrieves the list of available projects. The builder-selected item from the retrieved list would then be used as the input to the secondary issue creation step. + +There are two parts necessary for Slack apps to support dynamic options: custom step definitions, and handling custom step dynamic options. We'll take a look at both in the following sections. + +## Custom step definitions {#custom-step-definitions} + +When defining an input to a custom step intended to be dynamic (rather than explicitly defining a set of input parameters up front), you'll define a `dynamic_options` property that points to another custom step designed to return the set of dynamic elements once this step is added to a workflow from Workflow Builder. + +An input parameter for a custom step can reference a different custom step that defines what data is available for it to return. One Slack app could even use another Slack app’s custom step to define dynamic options for one of its inputs. + +The following code snippet from our issue creation example discussed above shows a `create-issue` custom step that will be used as a workflow step. Another custom step, the `get-projects` step, will dynamically populate the project input parameter to be configured by a builder. This `get-projects` step provides an `array` containing projects fetched dynamically from the external issue-tracking system. + +```js + "functions": { + "create-issue": { + "title": "Create Issue", + "description": "", + "input_parameters": { + "support_channel": { + "type": "slack#/types/channel_id", + "title": "Support Channel", + "description": "", + "name": "support_channel" + }, + "project": { + "type": "string", + "title": "Project", + "description": "A project from the issue tracking system", + "is_required": true, + "dynamic_options": { + "function": "#/functions/get-projects", + "inputs": {} + } + }, + }, + "output_parameters": {} + }, + "get-projects": { + "title": "Get Projects", + "description": "Get the available project from the issue tracking system", + "input_parameters": {}, + "output_parameters": { + "options": { + "type": "slack#/types/options_select", + "title": "Project Options", + } + } + } + }, +``` +### Defining the `function` and `inputs` attributes {#define-attributes} + +Defining the `function` and `inputs` attributes of the `dynamic_options` property would look as follows: + +``` +"dynamic_options": { + "function": "#/functions/get-projects", + "inputs": {} +} +``` + +The `function` attribute specifies the step reference used to resolve the options of the input parameter. For example: `"#/functions/get-projects"`. + +The `inputs` attribute defines the parameters to be passed as inputs to the step referenced by the `function` attribute. For example: + +``` +"inputs": { + "selected_user_id": { + "value": "{{input_parameters.user_id}}" + }, + "query": { + "value": "{{client.query}}" + } +} +``` + +The following format can be used to reference any input parameter defined by the step: `{{input_parameters.}}`. + +In addition, the `{{client.query}}` parameter can be used as a placeholder for an input value. The `{{client.builder_context}}` parameter will inject the [`slack#/types/user_context`](/tools/deno-slack-sdk/reference/slack-types/#usercontext) of the user building the workflow as the value to the input parameter. + +### Types of dynamic options UIs {#dynamic-option-UIs} + +The above example demonstrates one possible UI to be rendered for builders: a single-select drop-down menu of dynamic options. However, dynamic options in Workflow Builder can be rendered in one of two ways: as a drop-down menu (single-select or multi-select), or as a set of fields. + +The type is dictated by the output parameter of the custom step used as a dynamic option. In order to use a custom step in a dynamic option context, its output must adhere to a defined interface, that is, it must have an `options` parameter of type [`options_select`](/tools/deno-slack-sdk/reference/slack-types#options_select) or [`options_field`](/tools/deno-slack-sdk/reference/slack-types#options_field), as shown in the following code snippet. + +```js +"output_parameters": { + "options": { + "type": "slack#/types/options_select" or "slack#/types/options_field", + "title": "Custom Options", + "description": "Options to be used in a dynamic context", + } + ... +} +``` + +#### Drop-down menus {#drop-down} + +Your dynamic input parameter can be rendered as a drop-down menu, which will use the options obtained from a custom step with an `options` output parameter of the type [`options_select`](/tools/deno-slack-sdk/reference/slack-types#options_select). + +The drop-down menu UI component can be rendered in two ways: single-select, or multi-select. To render the dynamic input as a single-select menu, the input parameter defining the dynamic option must be of the type [`string`](/tools/deno-slack-sdk/reference/slack-types#string). + +```js +"step-with-dynamic-input": { + "title": "Step that uses a dynamic input", + "description": "This step uses a dynamic input rendered as a single-select menu", + "input_parameters": { + "dynamic_single_select": { + "type": "string", // this must be of type string for single-select + "title": "dynamic single select drop-down menu", + "description": "A dynamically-populated single-select drop-down menu", + "is_required": true, + "dynamic_options": { + "function": "#/functions/get-options", + "inputs": {}, + }, + } + }, + "output_parameters": {} +} +``` + +To render the dynamic input as a multi-select menu, the input parameter defining the dynamic option must be of the type [`array`](/tools/deno-slack-sdk/reference/slack-types#array), and its `items` must be of type [`string`](/tools/deno-slack-sdk/reference/slack-types#string). + +```js +"step-with-dynamic-input": { + "title": "Step that uses a dynamic input", + "description": "This step uses a dynamic input rendered as a multi-select menu", + "input_parameters": { + "dynamic_multi_select": { + "type": "array", // this must be of type array for multi-select + "items": { + "type": "string" + }, + "title": "dynamic single select drop-down menu", + "description": "A dynamically-populated multi-select drop-down menu", + "dynamic_options": { + "function": "#/functions/get-options", + "inputs": {}, + }, + } + }, + "output_parameters": {} +} +``` + +#### Fields {#fields} + +In the code snippet below, the input parameter is rendered as a set of fields with keys and values. The option fields are obtained from a custom step with an `options` output parameter of type [`options_field`](/tools/deno-slack-sdk/reference/slack-types#options_field). + +The input parameter that defines the dynamic option must be of type [`object`](/tools/deno-slack-sdk/reference/slack-types#object), as the completed set of fields in Workflow Builder will be passed to the custom step as an [untyped object](/tools/deno-slack-sdk/reference/slack-types#untyped-object) during workflow execution. + +```js +"test-field-dynamic-options": { + "title": "Test dynamic field options", + "description": "", + "input_parameters": { + "dynamic_fields": { + "type": "object", + "title": "Dynamic custom field options", + "description": "A dynamically-populated section of input fields", + "dynamic_options": { + "function": "#/functions/get-field-options", + "inputs": {} + "selection_type": "key-value", + } + } + }, + "output_parameters": {} +} +``` + +### Dynamic option types {#dynamic-option-types} + +As mentioned earlier, in order to use a custom step as a dynamic option, its output must adhere to a defined interface: it must have an `options` output parameter of the type either [`options_select`](/tools/deno-slack-sdk/reference/slack-types#options_select) or [`options_field`](/tools/deno-slack-sdk/reference/slack-types#options_field). + +To take a look at these in more detail, refer to our [Options Slack type](/tools/deno-slack-sdk/reference/slack-types#options) documentation. + +## Dynamic options handler {#dynamic-option-handler} + +Each custom step defined in the manifest needs a corresponding handler in your Slack app. Although implemented similarly to existing function execution event handlers, there are two key differences between regular custom step invocations and those used for dynamic options: + +* The custom step must have an `options` output parameter that is of type [`options_select`](/tools/deno-slack-sdk/reference/slack-types#options_select) or [`options_field`](/tools/deno-slack-sdk/reference/slack-types#options_field). +* The [`function_executed`](/reference/events/function_executed) event must be handled synchronously. This optimizes the response time of returned dynamic options and provides a crisp builder experience. + +### Asynchronous event handling {#async} + +By default, the Bolt family of frameworks handles `function_executed` events asynchronously. + +For example, the various modal-related API methods provide two ways to update a view: synchronously using a `response_action` HTTP response, or asynchronously using a separate HTTP API call. Using the asynchronous approach allows developers to handle events free of timeouts, but this isn't desired for dynamic options as it introduces delays and violates our stated goal of providing a crisp builder experience. + +### Synchronous event handling {#sync} + +Dynamic options support synchronous handling of `function_executed` events. By ensuring that the function execution’s state is complete with output parameters provided before responding to the `function_executed` event, Slack can quickly provide Workflow Builder with the requisite dynamic options. + +### Implementation {#implementation} + +To optimize the response time of dynamic options, you must acknowledge the incoming event after calling the [`function.completeSuccess`](/reference/methods/functions.completeSuccess) or [`function.completeError`](/reference/methods/functions.completeError) API methods, minimizing asynchronous latency. The `function.completeSuccess` and `function.completeError` API methods are invoked in the complete and fail helper functions. ([For example](https://github.com/slackapi/bolt-python?tab=readme-ov-file#making-things-happen)). + +A new `auto_acknowledge` flag allows you more granular control over whether specific event handlers should operate in synchronous or asynchronous response modes in order to enable a smooth dynamic options experience. + +#### Example {#bolt-py} + +In [Bolt for Python](https://docs.slack.dev/tools/bolt-python/), you can set `auto_acknowledge=False` on a specific function decorator. This allows you to manually control when the `ack()` event acknowledgement helper function is executed. It flips Bolt to synchronous `function_executed` event handling mode for the specific handler. + +```py +@app.function("get-projects", auto_acknowledge=False) +def handle_get_projects(ack: Ack, complete: Complete): + try: + complete( + outputs={ + "options": [ + { + "text": { + "type": "plain_text", + "text": "Secret Squirrel Project", + }, + "value": "p1", + }, + { + "text": { + "type": "plain_text", + "text": "Public Kangaroo Project", + }, + "value": "p2", + }, + ] + } + ) + finally: + ack() +``` + +✨ **To learn more about the Bolt family of frameworks and tools**, check out our [Slack Developer Tools](/tools). diff --git a/docs/content/basic/custom-steps.md b/docs/english/concepts/custom-steps.md similarity index 86% rename from docs/content/basic/custom-steps.md rename to docs/english/concepts/custom-steps.md index 1eedf9f5d..720c53421 100644 --- a/docs/content/basic/custom-steps.md +++ b/docs/english/concepts/custom-steps.md @@ -1,17 +1,17 @@ --- -title: Listening and responding to custom steps -lang: en -slug: /concepts/custom-steps +sidebar_label: Custom steps --- -Your app can use the `function()` method to listen to incoming [custom step requests](https://api.slack.com/automation/functions/custom-bolt). Custom steps are used in Workflow Builder to build workflows. The method requires a step `callback_id` of type `str`. This `callback_id` must also be defined in your [Function](https://api.slack.com/concepts/manifests#functions) definition. Custom steps must be finalized using the `complete()` or `fail()` listener arguments to notify Slack that your app has processed the request. +# Listening and responding to custom steps + +Your app can use the `function()` method to listen to incoming [custom step requests](/workflows/workflow-steps). Custom steps are used in Workflow Builder to build workflows. The method requires a step `callback_id` of type `str`. This `callback_id` must also be defined in your [Function](/reference/app-manifest#functions) definition. Custom steps must be finalized using the `complete()` or `fail()` listener arguments to notify Slack that your app has processed the request. * `complete()` requires **one** argument: `outputs` of type `dict`. It ends your custom step **successfully** and provides a dictionary containing the outputs of your custom step as per its definition. * `fail()` requires **one** argument: `error` of type `str`. It ends your custom step **unsuccessfully** and provides a message containing information regarding why your custom step failed. You can reference your custom step's inputs using the `inputs` listener argument of type `dict`. -Refer to [the module document](https://tools.slack.dev/bolt-python/api-docs/slack_bolt/kwargs_injection/args.html) to learn about the available listener arguments. +Refer to [the module document](https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html) to learn about the available listener arguments. ```python # This sample custom step formats an input and outputs it @@ -68,13 +68,13 @@ Example app manifest definition Your app's custom steps may create interactivity points for users, for example: Post a message with a button. -If such interaction points originate from a custom step execution, the events sent to your app representing the end-user interaction with these points are considered to be _function-scoped interactivity events_. These interactivity events can be handled by your app using the same concepts we covered earlier, such as [Listening to actions](/concepts/action-listening). +If such interaction points originate from a custom step execution, the events sent to your app representing the end-user interaction with these points are considered to be _function-scoped interactivity events_. These interactivity events can be handled by your app using the same concepts we covered earlier, such as [Listening to actions](/tools/bolt-python/concepts/actions). _function-scoped interactivity events_ will contain data related to the custom step (`function_executed` event) they were spawned from, such as custom step `inputs` and access to `complete()` and `fail()` listener arguments. Your app can skip calling `complete()` or `fail()` in the `function()` handler method if the custom step creates an interaction point that requires user interaction before the step can end. However, in the relevant interactivity handler method, your app must invoke `complete()` or `fail()` to notify Slack that the custom step has been processed. -You’ll notice in all interactivity handler examples, `ack()` is used. It is required to call the `ack()` function within an interactivity listener to acknowledge that the request was received from Slack. This is discussed in the [acknowledging requests section](/concepts/acknowledge). +You’ll notice in all interactivity handler examples, `ack()` is used. It is required to call the `ack()` function within an interactivity listener to acknowledge that the request was received from Slack. This is discussed in the [acknowledging requests section](/tools/bolt-python/concepts/acknowledge). ```python # This sample custom step posts a message with a button @@ -150,4 +150,4 @@ Example app manifest definition -Learn more about responding to interactivity, see the [Slack API documentation](https://api.slack.com/automation/functions/custom-bolt#interactivity). +Learn more about responding to interactivity, see the [Slack API documentation](/interactivity/handling-user-interaction). diff --git a/docs/content/advanced/errors.md b/docs/english/concepts/errors.md similarity index 90% rename from docs/content/advanced/errors.md rename to docs/english/concepts/errors.md index 59601f47b..7b40adb7f 100644 --- a/docs/content/advanced/errors.md +++ b/docs/english/concepts/errors.md @@ -1,14 +1,10 @@ ---- -title: Handling errors -lang: en -slug: /concepts/errors ---- - +# Handling errors If an error occurs in a listener, you can handle it directly using a try/except block. Errors associated with your app will be of type `BoltError`. Errors associated with calling Slack APIs will be of type `SlackApiError`. By default, the global error handler will log all non-handled exceptions to the console. To handle global errors yourself, you can attach a global error handler to your app using the `app.error(fn)` function. +## Example ```python @app.error diff --git a/docs/content/basic/event-listening.md b/docs/english/concepts/event-listening.md similarity index 58% rename from docs/content/basic/event-listening.md rename to docs/english/concepts/event-listening.md index 95c6e84ea..d7b8e5930 100644 --- a/docs/content/basic/event-listening.md +++ b/docs/english/concepts/event-listening.md @@ -1,14 +1,10 @@ ---- -title: Listening to events -lang: en -slug: /concepts/event-listening ---- +# Listening to events -You can listen to [any Events API event](https://api.slack.com/events) using the `event()` method after subscribing to it in your app configuration. This allows your app to take action when something happens in a workspace where it's installed, like a user reacting to a message or joining a channel. +You can listen to [any Events API event](/reference/events) using the `event()` method after subscribing to it in your app configuration. This allows your app to take action when something happens in a workspace where it's installed, like a user reacting to a message or joining a channel. The `event()` method requires an `eventType` of type `str`. -Refer to [the module document](https://tools.slack.dev/bolt-python/api-docs/slack_bolt/kwargs_injection/args.html) to learn the available listener arguments. +Refer to [the module document](https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html) to learn the available listener arguments. ```python # When a user joins the workspace, send a message in a predefined channel asking them to introduce themselves @app.event("team_join") @@ -18,15 +14,12 @@ def ask_for_introduction(event, say): text = f"Welcome to the team, <@{user_id}>! 🎉 You can introduce yourself in this channel." say(text=text, channel=welcome_channel_id) ``` -
- - Filtering on message subtypes - +## Filtering on message subtypes The `message()` listener is equivalent to `event("message")`. -You can filter on subtypes of events by passing in the additional key `subtype`. Common message subtypes like `bot_message` and `message_replied` can be found [on the message event page](https://api.slack.com/events/message#subtypes). +You can filter on subtypes of events by passing in the additional key `subtype`. Common message subtypes like `bot_message` and `message_replied` can be found [on the message event page](/reference/events/message#subtypes). You can explicitly filter for events without a subtype by explicitly setting `None`. ```python @@ -38,5 +31,4 @@ You can explicitly filter for events without a subtype by explicitly setting `No def log_message_change(logger, event): user, text = event["user"], event["text"] logger.info(f"The user {user} changed the message to {text}") -``` -
\ No newline at end of file +``` \ No newline at end of file diff --git a/docs/content/advanced/global-middleware.md b/docs/english/concepts/global-middleware.md similarity index 82% rename from docs/content/advanced/global-middleware.md rename to docs/english/concepts/global-middleware.md index 61aa97066..7b7bdb059 100644 --- a/docs/content/advanced/global-middleware.md +++ b/docs/english/concepts/global-middleware.md @@ -1,17 +1,13 @@ ---- -title: Global middleware -lang: en -slug: /concepts/global-middleware ---- - +# Global middleware Global middleware is run for all incoming requests, before any listener middleware. You can add any number of global middleware to your app by passing middleware functions to `app.use()`. Middleware functions are called with the same arguments as listeners, with an additional `next()` function. Both global and listener middleware must call `next()` to pass control of the execution chain to the next middleware. +Refer to [the module document](https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html) to learn the available listener arguments. +## Example -Refer to [the module document](https://tools.slack.dev/bolt-python/api-docs/slack_bolt/kwargs_injection/args.html) to learn the available listener arguments. ```python @app.use def auth_acme(client, context, logger, payload, next): diff --git a/docs/content/advanced/lazy-listeners.md b/docs/english/concepts/lazy-listeners.md similarity index 93% rename from docs/content/advanced/lazy-listeners.md rename to docs/english/concepts/lazy-listeners.md index e179c6afc..d775106b9 100644 --- a/docs/content/advanced/lazy-listeners.md +++ b/docs/english/concepts/lazy-listeners.md @@ -1,9 +1,4 @@ ---- -title: Lazy listeners (FaaS) -lang: en -slug: /concepts/lazy-listeners ---- - +# Lazy listeners (FaaS) Lazy Listeners are a feature which make it easier to deploy Slack apps to FaaS (Function-as-a-Service) environments. Please note that this feature is only available in Bolt for Python, and we are not planning to add the same to other Bolt frameworks. @@ -15,7 +10,6 @@ To allow you to still run more time-consuming processes as part of your handler, * `ack: Callable`: Responsible for calling `ack()` within 3 seconds * `lazy: List[Callable]`: Responsible for handling time-consuming processes related to the request. The lazy function does not have access to `ack()`. - ```python def respond_to_slack_within_3_seconds(body, ack): text = body.get("text") @@ -37,13 +31,9 @@ app.command("/start-process")( ) ``` -
- -Example with AWS Lambda - - +## Example with AWS Lambda -This example deploys the code to [AWS Lambda](https://aws.amazon.com/lambda/). There are more examples within the [`examples` folder](https://github.com/slackapi/bolt-python/tree/main/examples/aws_lambda). +This example deploys the code to [AWS Lambda](https://aws.amazon.com/lambda/). There are more examples within the [`examples`](https://github.com/slackapi/bolt-python/tree/main/examples/aws_lambda) folder. ```bash pip install slack_bolt @@ -61,7 +51,6 @@ echo 'slack_bolt' > requirements.txt lambda deploy --config-file config.yaml --requirements requirements.txt ``` - ```python from slack_bolt import App from slack_bolt.adapter.aws_lambda import SlackRequestHandler @@ -108,5 +97,4 @@ Please note that the following IAM permissions would be required for running thi } ] } -``` -
+``` \ No newline at end of file diff --git a/docs/content/advanced/listener-middleware.md b/docs/english/concepts/listener-middleware.md similarity index 68% rename from docs/content/advanced/listener-middleware.md rename to docs/english/concepts/listener-middleware.md index 338cb0d4f..dd020373f 100644 --- a/docs/content/advanced/listener-middleware.md +++ b/docs/english/concepts/listener-middleware.md @@ -1,21 +1,18 @@ ---- -title: Listener middleware -lang: en -slug: /concepts/listener-middleware ---- +# Listener middleware Listener middleware is only run for the listener in which it's passed. You can pass any number of middleware functions to the listener using the `middleware` parameter, which must be a list that contains one to many middleware functions. If your listener middleware is a quite simple one, you can use a listener matcher, which returns `bool` value (`True` for proceeding) instead of requiring `next()` method call. -Refer to [the module document](https://tools.slack.dev/bolt-python/api-docs/slack_bolt/kwargs_injection/args.html) to learn the available listener arguments. +Refer to [the module document](https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html) to learn the available listener arguments. + +## Example ```python -# Listener middleware which filters out messages with "bot_message" subtype +# Listener middleware which filters out messages from a bot def no_bot_messages(message, next): - subtype = message.get("subtype") - if subtype != "bot_message": - next() + if "bot_id" not in message: + next() # This listener only receives messages from humans @app.event(event="message", middleware=[no_bot_messages]) @@ -24,10 +21,10 @@ def log_message(logger, event): # Listener matchers: simplified version of listener middleware def no_bot_messages(message) -> bool: - return message.get("subtype") != "bot_message" + return "bot_id" not in message @app.event( - event="message", + event="message", matchers=[no_bot_messages] # or matchers=[lambda message: message.get("subtype") != "bot_message"] ) diff --git a/docs/content/advanced/logging.md b/docs/english/concepts/logging.md similarity index 94% rename from docs/content/advanced/logging.md rename to docs/english/concepts/logging.md index 900484b7a..599431550 100644 --- a/docs/content/advanced/logging.md +++ b/docs/english/concepts/logging.md @@ -1,14 +1,10 @@ ---- -title: Logging -lang: en -slug: /concepts/logging ---- - +# Logging By default, Bolt will log information from your app to the output destination. After you've imported the `logging` module, you can customize the root log level by passing the `level` parameter to `basicConfig()`. The available log levels in order of least to most severe are `debug`, `info`, `warning`, `error`, and `critical`. Outside of a global context, you can also log a single message corresponding to a specific level. Because Bolt uses Python’s [standard logging module](https://docs.python.org/3/library/logging.html), you can use any its features. +## Example ```python import logging diff --git a/docs/content/basic/message-listening.md b/docs/english/concepts/message-listening.md similarity index 54% rename from docs/content/basic/message-listening.md rename to docs/english/concepts/message-listening.md index 0243b1537..be6e74678 100644 --- a/docs/content/basic/message-listening.md +++ b/docs/english/concepts/message-listening.md @@ -1,16 +1,9 @@ ---- -title: Listening to messages -lang: en -slug: /concepts/message-listening ---- - -To listen to messages that [your app has access to receive](https://api.slack.com/messaging/retrieving#permissions), you can use the `message()` method which filters out events that aren't of type `message`. +# Listening to messages +To listen to messages that [your app has access to receive](/messaging/retrieving-messages), you can use the `message()` method which filters out events that aren't of type `message`. `message()` accepts an argument of type `str` or `re.Pattern` object that filters out any messages that don't match the pattern. -:::info - -Refer to [the module document](https://tools.slack.dev/bolt-python/api-docs/slack_bolt/kwargs_injection/args.html) to learn the available listener arguments. +:::info[Refer to [the module document](https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html) to learn the available listener arguments.] ::: @@ -22,10 +15,7 @@ def say_hello(message, say): say(f"Hi there, <@{user}>!") ``` -
- -Using a regular expression pattern - +## Using a regular expression pattern The `re.compile()` method can be used instead of a string for more granular matching. @@ -37,6 +27,4 @@ def say_hello_regex(say, context): # regular expression matches are inside of context.matches greeting = context['matches'][0] say(f"{greeting}, how are you?") -``` - -
\ No newline at end of file +``` \ No newline at end of file diff --git a/docs/english/concepts/message-sending.md b/docs/english/concepts/message-sending.md new file mode 100644 index 000000000..c4d1b0467 --- /dev/null +++ b/docs/english/concepts/message-sending.md @@ -0,0 +1,119 @@ +# Sending messages + +Within your listener function, `say()` is available whenever there is an associated conversation (for example, a conversation where the event or action which triggered the listener occurred). `say()` accepts a string to post simple messages and JSON payloads to send more complex messages. The message payload you pass in will be sent to the associated conversation. + +In the case that you'd like to send a message outside of a listener or you want to do something more advanced (like handle specific errors), you can call `client.chat_postMessage` [using the client attached to your Bolt instance](/tools/bolt-python/concepts/web-api). + +Refer to [the module document](https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html) to learn the available listener arguments. + +```python +# Listens for messages containing "knock knock" and responds with an italicized "who's there?" +@app.message("knock knock") +def ask_who(message, say): + say("_Who's there?_") +``` + +## Sending a message with blocks + +`say()` accepts more complex message payloads to make it easy to add functionality and structure to your messages. + +To explore adding rich message layouts to your app, read through [the guide on our API site](/messaging/#structure) and look through templates of common app flows [in the Block Kit Builder](https://api.slack.com/tools/block-kit-builder?template=1). + +```python +# Sends a section block with datepicker when someone reacts with a 📅 emoji +@app.event("reaction_added") +def show_datepicker(event, say): + reaction = event["reaction"] + if reaction == "calendar": + blocks = [{ + "type": "section", + "text": {"type": "mrkdwn", "text": "Pick a date for me to remind you"}, + "accessory": { + "type": "datepicker", + "action_id": "datepicker_remind", + "initial_date": "2020-05-04", + "placeholder": {"type": "plain_text", "text": "Select a date"} + } + }] + say( + blocks=blocks, + text="Pick a date for me to remind you" + ) +``` + +## Streaming messages {#streaming-messages} + +You can have your app's messages stream in to replicate conventional agent behavior. Bolt for Python provides a `say_stream` utility as a listener argument available for `app.event` and `app.message` listeners. + +The `say_stream` utility streamlines calling the Python Slack SDK's [`WebClient.chat_stream`](https://docs.slack.dev/tools/python-slack-sdk/reference/web/client.html#slack_sdk.web.client.WebClient.chat_stream) helper utility by sourcing parameter values from the relevant event payload. + +| Parameter | Value | +|---|---| +| `channel_id` | Sourced from the event payload. +| `thread_ts` | Sourced from the event payload. Falls back to the `ts` value if available. +| `recipient_team_id` | Sourced from the event `team_id` (`enterprise_id` if the app is installed on an org). +| `recipient_user_id` | Sourced from the `user_id` of the event. + +If either `channel_id` or `thread_ts` cannot be sourced, the utility will be `None`. + +For information on calling the `chat_*Stream` API methods directly, see the [_Sending streaming messages_](/tools/python-slack-sdk/web#sending-streaming-messages) section of the Python Slack SDK docs. + +### Example {#example} + +```py +import os + +from slack_bolt import App, SayStream +from slack_bolt.adapter.socket_mode import SocketModeHandler +from slack_sdk import WebClient + +app = App(token=os.environ.get("SLACK_BOT_TOKEN")) + +@app.event("app_mention") +def handle_app_mention(client: WebClient, say_stream: SayStream): + stream = say_stream() + stream.append(markdown_text="Someone rang the bat signal!") + stream.stop() + +@app.message("") +def handle_message(client: WebClient, say_stream: SayStream): + stream = say_stream() + + stream.append(markdown_text="Let me consult my *vast knowledge database*...") + stream.stop() + +if __name__ == "__main__": + SocketModeHandler(app, os.environ.get("SLACK_APP_TOKEN")).start() +``` + +#### Adding feedback buttons after a stream + +You can pass a [feedback buttons](/reference/block-kit/block-elements/feedback-buttons-element) block element to `stream.stop` to provide feedback buttons to the user at the bottom of the message. Interaction with these buttons will send a block action event to your app to receive the feedback. + +```py +stream.stop(blocks=feedback_block) +``` + +```py +def create_feedback_block() -> List[Block]: + blocks: List[Block] = [ + ContextActionsBlock( + elements=[ + FeedbackButtonsElement( + action_id="feedback", + positive_button=FeedbackButtonObject( + text="Good Response", + accessibility_label="Submit positive feedback on this response", + value="good-feedback", + ), + negative_button=FeedbackButtonObject( + text="Bad Response", + accessibility_label="Submit negative feedback on this response", + value="bad-feedback", + ), + ) + ] + ) + ] + return blocks +``` \ No newline at end of file diff --git a/docs/content/basic/opening-modals.md b/docs/english/concepts/opening-modals.md similarity index 62% rename from docs/content/basic/opening-modals.md rename to docs/english/concepts/opening-modals.md index 9049a5bdb..01716f613 100644 --- a/docs/content/basic/opening-modals.md +++ b/docs/english/concepts/opening-modals.md @@ -1,16 +1,14 @@ ---- -title: Opening modals -lang: en -slug: /concepts/opening-modals ---- +# Opening modals -[Modals](https://api.slack.com/block-kit/surfaces/modal) are focused surfaces that allow you to collect user data and display dynamic information. You can open a modal by passing a valid `trigger_id` and a [view payload](https://api.slack.com/reference/block-kit/views) to the built-in client's [`views.open`](https://api.slack.com/methods/views.open) method. +[Modals](/surfaces/modals) are focused surfaces that allow you to collect user data and display dynamic information. You can open a modal by passing a valid `trigger_id` and a [view payload](/reference/interaction-payloads/view-interactions-payload/#view_submission) to the built-in client's [`views.open`](/reference/methods/views.open/) method. -Your app receives `trigger_id`s in payloads sent to your Request URL that are triggered by user invocations, like a shortcut, button press, or interaction with a select menu. +Your app receives `trigger_id` parameters in payloads sent to your Request URL triggered user invocation like a slash command, button press, or interaction with a select menu. -Read more about modal composition in the [API documentation](https://api.slack.com/surfaces/modals/using#composing_views). +Read more about modal composition in the [API documentation](/surfaces/modals#composing_views). -Refer to [the module document](https://tools.slack.dev/bolt-python/api-docs/slack_bolt/kwargs_injection/args.html) to learn the available listener arguments. +Refer to [the module document](https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html) to learn the available listener arguments. + +## Example ```python # Listen for a shortcut invocation diff --git a/docs/content/basic/options.md b/docs/english/concepts/select-menu-options.md similarity index 69% rename from docs/content/basic/options.md rename to docs/english/concepts/select-menu-options.md index e7c1e1243..8e6cbb9fe 100644 --- a/docs/content/basic/options.md +++ b/docs/english/concepts/select-menu-options.md @@ -1,19 +1,18 @@ ---- -title: Listening and responding to options -lang: en -slug: /concepts/options ---- +# Listening & responding to select menu options -The `options()` method listens for incoming option request payloads from Slack. [Similar to `action()`](/concepts/action-listening), +The `options()` method listens for incoming option request payloads from Slack. [Similar to `action()`](/tools/bolt-python/concepts/actions), an `action_id` or constraints object is required. In order to load external data into your select menus, you must provide an options load URL in your app configuration, appended with `/slack/events`. While it's recommended to use `action_id` for `external_select` menus, dialogs do not support Block Kit so you'll have to use the constraints object to filter on a `callback_id`. -To respond to options requests, you'll need to call `ack()` with a valid `options` or `option_groups` list. Both [external select response examples](https://api.slack.com/reference/messaging/block-elements#external-select) and [dialog response examples](https://api.slack.com/dialogs#dynamic_select_elements_external) can be found on our API site. +To respond to options requests, you'll need to call `ack()` with a valid `options` or `option_groups` list. Both [external select response examples](/reference/block-kit/block-elements/multi-select-menu-element#external_multi_select) and [dialog response examples](/reference/block-kit/block-elements/multi-select-menu-element#conversation_multi_select) can be found on our API site. -Additionally, you may want to apply filtering logic to the returned options based on user input. This can be accomplished by using the `payload` argument to your options listener and checking for the contents of the `value` property within it. Based on the `value` you can return different options. All listeners and middleware handlers in Bolt for Python have access to [many useful arguments](https://tools.slack.dev/bolt-python/api-docs/slack_bolt/kwargs_injection/args.html) - be sure to check them out! +Additionally, you may want to apply filtering logic to the returned options based on user input. This can be accomplished by using the `payload` argument to your options listener and checking for the contents of the `value` property within it. Based on the `value` you can return different options. All listeners and middleware handlers in Bolt for Python have access to [many useful arguments](https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html) - be sure to check them out! + +Refer to [the module document](https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html) to learn the available listener arguments. + +## Example -Refer to [the module document](https://tools.slack.dev/bolt-python/api-docs/slack_bolt/kwargs_injection/args.html) to learn the available listener arguments. ```python # Example of responding to an external_select options request @app.options("external_action") diff --git a/docs/content/basic/shortcuts.md b/docs/english/concepts/shortcuts.md similarity index 71% rename from docs/content/basic/shortcuts.md rename to docs/english/concepts/shortcuts.md index 6f469c20f..b28f0b352 100644 --- a/docs/content/basic/shortcuts.md +++ b/docs/english/concepts/shortcuts.md @@ -1,22 +1,18 @@ ---- -title: Listening and responding to shortcuts -lang: en -slug: /concepts/shortcuts ---- +# Listening & responding to shortcuts -The `shortcut()` method supports both [global shortcuts](https://api.slack.com/interactivity/shortcuts/using#global_shortcuts) and [message shortcuts](https://api.slack.com/interactivity/shortcuts/using#message_shortcuts). +The `shortcut()` method supports both [global shortcuts](/interactivity/implementing-shortcuts#global) and [message shortcuts](/interactivity/implementing-shortcuts#messages). Shortcuts are invokable entry points to apps. Global shortcuts are available from within search and text composer area in Slack. Message shortcuts are available in the context menus of messages. Your app can use the `shortcut()` method to listen to incoming shortcut requests. The method requires a `callback_id` parameter of type `str` or `re.Pattern`. Shortcuts must be acknowledged with `ack()` to inform Slack that your app has received the request. -Shortcuts include a `trigger_id` which an app can use to [open a modal](/concepts/opening-modals) that confirms the action the user is taking. +Shortcuts include a `trigger_id` which an app can use to [open a modal](/tools/bolt-python/concepts/opening-modals) that confirms the action the user is taking. When setting up shortcuts within your app configuration, as with other URLs, you'll append `/slack/events` to your request URL. -⚠️ Note that global shortcuts do **not** include a channel ID. If your app needs access to a channel ID, you may use a [`conversations_select`](https://api.slack.com/reference/block-kit/block-elements#conversation_select) element within a modal. Message shortcuts do include a channel ID. +⚠️ Note that global shortcuts do **not** include a channel ID. If your app needs access to a channel ID, you may use a [`conversations_select`](/reference/block-kit/block-elements/multi-select-menu-element#conversation_multi_select) element within a modal. Message shortcuts do include a channel ID. -Refer to [the module document](https://tools.slack.dev/bolt-python/api-docs/slack_bolt/kwargs_injection/args.html) to learn the available listener arguments. +Refer to [the module document](https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html) to learn the available listener arguments. ```python # The open_modal shortcut listens to a shortcut with the callback_id "open_modal" @app.shortcut("open_modal") @@ -36,7 +32,7 @@ def open_modal(ack, shortcut, client): "type": "section", "text": { "type": "mrkdwn", - "text": "About the simplest modal you could conceive of :smile:\n\nMaybe or ." + "text": "About the simplest modal you could conceive of :smile:\n\nMaybe or ." } }, { @@ -53,11 +49,9 @@ def open_modal(ack, shortcut, client): ) ``` -
- - Listening to shortcuts using a constraint object - - You can use a constraints object to listen to `callback_id`s, and `type`s. Constraints in the object can be of type `str` or `re.Pattern`. +## Listening to shortcuts using a constraint object + +You can use a constraints object to listen to `callback_id`s, and `type`s. Constraints in the object can be of type `str` or `re.Pattern`. ```python # Your listener will only be called when the callback_id matches 'open_modal' AND the type matches 'message_action' @@ -77,7 +71,7 @@ def open_modal(ack, shortcut, client): "type": "section", "text": { "type": "mrkdwn", - "text": "About the simplest modal you could conceive of :smile:\n\nMaybe or ." + "text": "About the simplest modal you could conceive of :smile:\n\nMaybe or ." } }, { @@ -92,5 +86,4 @@ def open_modal(ack, shortcut, client): ] } ) -``` -
\ No newline at end of file +``` \ No newline at end of file diff --git a/docs/content/basic/socket-mode.md b/docs/english/concepts/socket-mode.md similarity index 76% rename from docs/content/basic/socket-mode.md rename to docs/english/concepts/socket-mode.md index 61be345de..5156f6e13 100644 --- a/docs/content/basic/socket-mode.md +++ b/docs/english/concepts/socket-mode.md @@ -1,10 +1,6 @@ ---- -title: Using Socket Mode -lang: en -slug: /concepts/socket-mode ---- +# Using Socket Mode -With the introduction of [Socket Mode](https://api.slack.com/apis/connections/socket), Bolt for Python introduced support in version `1.2.0`. With Socket Mode, instead of creating a server with endpoints that Slack sends payloads too, the app will instead connect to Slack via a WebSocket connection and receive data from Slack over the socket connection. Make sure to enable Socket Mode in your app configuration settings. +With the introduction of [Socket Mode](/apis/events-api/using-socket-mode), Bolt for Python introduced support in version `1.2.0`. With Socket Mode, instead of creating a server with endpoints that Slack sends payloads too, the app will instead connect to Slack via a WebSocket connection and receive data from Slack over the socket connection. Make sure to enable Socket Mode in your app configuration settings. To use the Socket Mode, add `SLACK_APP_TOKEN` as an environment variable. You can get your App Token in your app configuration settings under the **Basic Information** section. @@ -17,7 +13,6 @@ While we recommend using [the built-in Socket Mode adapter](https://github.com/s |[aiohttp](https://pypi.org/project/aiohttp/) (asyncio-based)|[slack_bolt.adapter.socket_mode.aiohttp](https://github.com/slackapi/bolt-python/tree/main/slack_bolt/adapter/socket_mode/aiohttp)| |[websockets](https://pypi.org/project/websockets/) (asyncio-based)|[slack_bolt.adapter.socket_mode.websockets](https://github.com/slackapi/bolt-python/tree/main/slack_bolt/adapter/socket_mode/websockets)| - ```python import os from slack_bolt import App @@ -35,14 +30,11 @@ if __name__ == "__main__": handler.start() ``` -
- -Using Async (asyncio) - +## Using Async (asyncio) To use the asyncio-based adapters such as aiohttp, your whole app needs to be compatible with asyncio's async/await programming model. `AsyncSocketModeHandler` is available for running `AsyncApp` and its async middleware and listeners. -To learn how to use `AsyncApp`, checkout the [using Async](/concepts/async) document and relevant [examples](https://github.com/slackapi/bolt-python/tree/main/examples). +To learn how to use `AsyncApp`, checkout the [using Async](/tools/bolt-python/concepts/async) document and relevant [examples](https://github.com/slackapi/bolt-python/tree/main/examples). ```python from slack_bolt.app.async_app import AsyncApp @@ -60,5 +52,4 @@ async def main(): if __name__ == "__main__": import asyncio asyncio.run(main()) -``` -
\ No newline at end of file +``` \ No newline at end of file diff --git a/docs/content/advanced/token-rotation.md b/docs/english/concepts/token-rotation.md similarity index 73% rename from docs/content/advanced/token-rotation.md rename to docs/english/concepts/token-rotation.md index 1f278c3aa..96a41bb3c 100644 --- a/docs/content/advanced/token-rotation.md +++ b/docs/english/concepts/token-rotation.md @@ -1,15 +1,9 @@ ---- -title: Token rotation -lang: en -slug: /concepts/token-rotation ---- - +# Token rotation Supported in Bolt for Python as of [v1.7.0](https://github.com/slackapi/bolt-python/releases/tag/v1.7.0), token rotation provides an extra layer of security for your access tokens and is defined by the [OAuth V2 RFC](https://datatracker.ietf.org/doc/html/rfc6749#section-10.4). Instead of an access token representing an existing installation of your Slack app indefinitely, with token rotation enabled, access tokens expire. A refresh token acts as a long-lived way to refresh your access tokens. -Bolt for Python supports and will handle token rotation automatically so long as the [built-in OAuth](/concepts/authenticating-oauth) functionality is used. - -For more information about token rotation, please see the [documentation](https://api.slack.com/authentication/rotation). +Bolt for Python supports and will handle token rotation automatically so long as the [built-in OAuth](/tools/bolt-python/concepts/authenticating-oauth) functionality is used. +For more information about token rotation, please see the [documentation](/authentication/using-token-rotation). \ No newline at end of file diff --git a/docs/content/basic/updating-pushing-views.md b/docs/english/concepts/updating-pushing-views.md similarity index 57% rename from docs/content/basic/updating-pushing-views.md rename to docs/english/concepts/updating-pushing-views.md index 8cc45d49a..ce285c814 100644 --- a/docs/content/basic/updating-pushing-views.md +++ b/docs/english/concepts/updating-pushing-views.md @@ -1,22 +1,18 @@ ---- -title: Updating and pushing views -lang: en -slug: /concepts/updating-pushing-views ---- +# Updating & pushing views -Modals contain a stack of views. When you call `views_open`, you add the root view to the modal. After the initial call, you can dynamically update a view by calling `views_update`, or stack a new view on top of the root view by calling `views_push`. +Modals contain a stack of views. When you call [`views_open`](/reference/methods/views.open/), you add the root view to the modal. After the initial call, you can dynamically update a view by calling [`views_update`](/reference/methods/views.update/), or stack a new view on top of the root view by calling [`views_push`](/reference/methods/views.push/) -**`views_update`** +## The `views_update` method To update a view, you can use the built-in client to call `views_update` with the `view_id` that was generated when you opened the view, and a new `view` including the updated `blocks` list. If you're updating the view when a user interacts with an element inside of an existing view, the `view_id` will be available in the `body` of the request. -**`views_push`** +## The `views_push` method -To push a new view onto the view stack, you can use the built-in client to call `views_push` with a valid `trigger_id` a new view payload. The arguments for `views_push` is the same as opening modals. After you open a modal, you may only push two additional views onto the view stack. +To push a new view onto the view stack, you can use the built-in client to call `views_push` with a valid `trigger_id` a new [view payload](/reference/interaction-payloads/view-interactions-payload/#view_submission). The arguments for `views_push` is the same as [opening modals](/tools/bolt-python/concepts/opening-modals). After you open a modal, you may only push two additional views onto the view stack. -Learn more about updating and pushing views in our API documentation. +Learn more about updating and pushing views in our [API documentation](/surfaces/modals) -Refer to [the module document](https://tools.slack.dev/bolt-python/api-docs/slack_bolt/kwargs_injection/args.html) to learn the available listener arguments. +Refer to [the module document](https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html) to learn the available listener arguments. ```python # Listen for a button invocation with action_id `button_abc` (assume it's inside of a modal) @app.action("button_abc") diff --git a/docs/english/concepts/using-the-assistant-class.md b/docs/english/concepts/using-the-assistant-class.md new file mode 100644 index 000000000..55ca3d110 --- /dev/null +++ b/docs/english/concepts/using-the-assistant-class.md @@ -0,0 +1,333 @@ +# Using the Assistant class + +:::info[Some features within this guide require a paid plan] +If you don't have a paid workspace for development, you can join the [Developer Program](https://api.slack.com/developer-program) and provision a sandbox with access to all Slack features for free. +::: + +The `Assistant` class can be used to handle the incoming events expected from a user interacting with an app in Slack that has the **Agents** feature enabled. + +:::warning[The `Assistant` class targets the assistant messaging experience] +The `Assistant` class handles the [assistant messaging experience](/ai/developing-agents) (`assistant_view`), in which agent conversations happen in separate Chat and History tabs. Apps going forward use the agent messaging experience (`agent_view`) by default, where conversations happen in the Messages tab and you handle events such as [`app_home_opened`](/reference/events/app_home_opened) and [`message.im`](/reference/events/message.im) directly. Refer to [Adding agent features](/tools/bolt-python/concepts/adding-agent-features) and the [agent messaging experience changelog entry](/changelog/2026/06/30/agent-messages-tab) for the default experience and migration guidance. +::: + +A typical flow would look like: + +1. [The user starts a thread](#handling-new-thread). The `Assistant` class handles the incoming [`assistant_thread_started`](/reference/events/assistant_thread_started) event. +2. [The thread context may change at any point](#handling-thread-context-changes). The `Assistant` class can handle any incoming [`assistant_thread_context_changed`](/reference/events/assistant_thread_context_changed) events. The class also provides a default `context` store to keep track of thread context changes as the user moves through Slack. +3. [The user responds](#handling-user-response). The `Assistant` class handles the incoming [`message.im`](/reference/events/message.im) event. + + +```python +assistant = Assistant() + +# This listener is invoked when a human user opened an assistant thread +@assistant.thread_started +def start_assistant_thread( + say: Say, + get_thread_context: GetThreadContext, + set_suggested_prompts: SetSuggestedPrompts, + logger: logging.Logger, +): + try: + ... + +# This listener is invoked when the human user sends a reply in the assistant thread +@assistant.user_message +def respond_in_assistant_thread( + client: WebClient, + context: BoltContext, + get_thread_context: GetThreadContext, + logger: logging.Logger, + payload: dict, + say: Say, + set_status: SetStatus, +): + try: + ... + +# Enable this assistant middleware in your Bolt app +app.use(assistant) +``` + +:::info[Consider the following] +You _could_ go it alone and [listen](/tools/bolt-python/concepts/event-listening) for the `assistant_thread_started`, `assistant_thread_context_changed`, and `message.im` events in order to implement the AI features in your app. That being said, using the `Assistant` class will streamline the process. And we already wrote this nice guide for you! +::: + +While the `assistant_thread_started` and `assistant_thread_context_changed` events do provide Slack-client thread context information, the `message.im` event does not. Any subsequent user message events won't contain thread context data. For that reason, Bolt not only provides a way to store thread context — the `threadContextStore` property — but it also provides a `DefaultThreadContextStore` instance that is utilized by default. This implementation relies on storing and retrieving [message metadata](/messaging/message-metadata/) as the user interacts with the app. + +If you do provide your own `threadContextStore` property, it must feature `find` and `save` methods. + +:::tip[Refer to the [reference docs](https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html) to learn the available listener arguments.] +::: + +## Configuring your app to support the `Assistant` class {#configuring-assistant-class} + +1. Within [App Settings](https://api.slack.com/apps), enable the **Agents** feature. + +2. Within the App Settings **OAuth & Permissions** page, add the following scopes: + * [`assistant:write`](/reference/scopes/assistant.write) + * [`chat:write`](/reference/scopes/chat.write) + * [`im:history`](/reference/scopes/im.history) + +3. Within the App Settings **Event Subscriptions** page, subscribe to the following events: + * [`assistant_thread_started`](/reference/events/assistant_thread_started) + * [`assistant_thread_context_changed`](/reference/events/assistant_thread_context_changed) + * [`message.im`](/reference/events/message.im) + +## Handling a new thread {#handling-new-thread} + +When the user opens a new thread with your AI-enabled app, the [`assistant_thread_started`](/reference/events/assistant_thread_started) event will be sent to your app. + +:::tip[When a user opens an app thread while in a channel, the channel info is stored as the thread's `AssistantThreadContext` data.] + +You can grab that info by using the `get_thread_context` utility, as subsequent user message event payloads won't include the channel info. +::: + +```python +assistant = Assistant() + +@assistant.thread_started +def start_assistant_thread( + say: Say, + get_thread_context: GetThreadContext, + set_suggested_prompts: SetSuggestedPrompts, + logger: logging.Logger, +): + try: + say("How can I help you?") + + prompts: List[Dict[str, str]] = [ + { + "title": "Suggest names for my Slack app", + "message": "Can you suggest a few names for my Slack app? The app helps my teammates better organize information and plan priorities and action items.", + }, + ] + + thread_context = get_thread_context() + if thread_context is not None and thread_context.channel_id is not None: + summarize_channel = { + "title": "Summarize the referred channel", + "message": "Can you generate a brief summary of the referred channel?", + } + prompts.append(summarize_channel) + + set_suggested_prompts(prompts=prompts) + except Exception as e: + logger.exception(f"Failed to handle an assistant_thread_started event: {e}", e) + say(f":warning: Something went wrong! ({e})") +``` + +You can send more complex messages to the user — see [Sending Block Kit alongside messages](#block-kit-interactions) for more info. + +## Handling thread context changes {#handling-thread-context-changes} + +When the user switches channels, the [`assistant_thread_context_changed`](/reference/events/assistant_thread_context_changed) event will be sent to your app. + +If you use the built-in `Assistant` middleware without any custom configuration, the updated context data is automatically saved as [message metadata](/messaging/message-metadata/) of the first reply from the app. + +As long as you use the built-in approach, you don't need to store the context data within a datastore. The downside of this default behavior is the overhead of additional calls to the Slack API. These calls include those to `conversations.history`, which are used to look up the stored message metadata that contains the thread context (via `get_thread_context`). + +To store context elsewhere, pass a custom `AssistantThreadContextStore` implementation to the `Assistant` constructor. We provide `FileAssistantThreadContextStore`, which is a reference implementation that uses the local file system. Since this reference implementation relies on local files, it's not advised for use in production. For production apps, we recommend creating a class that inherits `AssistantThreadContextStore`. + +```python +from slack_bolt import FileAssistantThreadContextStore +assistant = Assistant(thread_context_store=FileAssistantThreadContextStore()) +``` + +## Handling the user response {#handling-user-response} + +When the user messages your app, the [`message.im`](/reference/events/message.im) event will be sent to your app. + +Messages sent to the app do not contain a [subtype](/reference/events/message#subtypes) and must be deduced based on their shape and any provided [message metadata](/messaging/message-metadata/). + +There are three utilities that are particularly useful in curating the user experience: +* [`say`](https://docs.slack.dev/tools/bolt-python/reference/#slack_bolt.Say) +* [`set_title`](https://docs.slack.dev/tools/bolt-python/reference/#slack_bolt.SetTitle) +* [`set_status`](https://docs.slack.dev/tools/bolt-python/reference/#slack_bolt.SetStatus) + +Within the `set_status` utility, you can cycle through strings passed into a `loading_messages` list. + +```python +# This listener is invoked when the human user sends a reply in the assistant thread +@assistant.user_message +def respond_in_assistant_thread( + client: WebClient, + context: BoltContext, + get_thread_context: GetThreadContext, + logger: logging.Logger, + payload: dict, + say: Say, + set_status: SetStatus, +): + try: + channel_id = payload["channel"] + team_id = payload["team"] + thread_ts = payload["thread_ts"] + user_id = payload["user"] + user_message = payload["text"] + + set_status( + status="thinking...", + loading_messages=[ + "Untangling the internet cables…", + "Consulting the office goldfish…", + "Convincing the AI to stop overthinking…", + ], + ) + + # Collect the conversation history with this user + replies = client.conversations_replies( + channel=context.channel_id, + ts=context.thread_ts, + oldest=context.thread_ts, + limit=10, + ) + messages_in_thread: List[Dict[str, str]] = [] + for message in replies["messages"]: + role = "user" if message.get("bot_id") is None else "assistant" + messages_in_thread.append({"role": role, "content": message["text"]}) + + returned_message = call_llm(messages_in_thread) + + # Post the result in the assistant thread + say(text=returned_message) + + except Exception as e: + logger.exception(f"Failed to respond to an inquiry: {e}") + # Don't forget sending a message telling the error + # Without this, the status 'is typing...' won't be cleared, therefore the end-user is unable to continue the chat + say(f":warning: Sorry, something went wrong during processing your request (error: {e})") + +# Enable this assistant middleware in your Bolt app +app.use(assistant) +``` + +## Sending Block Kit alongside messages {#block-kit-interactions} + +For advanced use cases, Block Kit buttons may be used instead of suggested prompts, as well as the sending of messages with structured [metadata](/messaging/message-metadata/) to trigger subsequent interactions with the user. + +For example, an app can display a button such as "Summarize the referring channel" in the initial reply. When the user clicks the button and submits detailed information (such as the number of messages, days to check, purpose of the summary, etc.), the app can handle that information and post a message that describes the request with structured metadata. + +By default, apps can't respond to their own bot messages (Bolt prevents infinite loops by default). However, if you pass `ignoring_self_assistant_message_events_enabled=False` to the `App` constructor and add a `bot_message` listener to your `Assistant` middleware, your app can continue processing the request as shown below: + +```python +app = App( + token=os.environ["SLACK_BOT_TOKEN"], + # This must be set to handle bot message events + ignoring_self_assistant_message_events_enabled=False, +) + +assistant = Assistant() + +@assistant.thread_started +def start_assistant_thread(say: Say): + say( + text=":wave: Hi, how can I help you today?", + blocks=[ + { + "type": "section", + "text": {"type": "mrkdwn", "text": ":wave: Hi, how can I help you today?"}, + }, + { + "type": "actions", + "elements": [ + # You can have multiple buttons here + { + "type": "button", + "action_id": "assistant-generate-random-numbers", + "text": {"type": "plain_text", "text": "Generate random numbers"}, + "value": "clicked", + }, + ], + }, + ], + ) + +# This listener is invoked when the above button is clicked +@app.action("assistant-generate-random-numbers") +def configure_random_number_generation(ack: Ack, client: WebClient, body: dict): + ack() + client.views_open( + trigger_id=body["trigger_id"], + view={ + "type": "modal", + "callback_id": "configure_assistant_summarize_channel", + "title": {"type": "plain_text", "text": "My Assistant"}, + "submit": {"type": "plain_text", "text": "Submit"}, + "close": {"type": "plain_text", "text": "Cancel"}, + # Relay the assistant thread information to app.view listener + "private_metadata": json.dumps( + { + "channel_id": body["channel"]["id"], + "thread_ts": body["message"]["thread_ts"], + } + ), + "blocks": [ + { + "type": "input", + "block_id": "num", + "label": {"type": "plain_text", "text": "# of outputs"}, + # You can have this kind of predefined input from a user instead of parsing human text + "element": { + "type": "static_select", + "action_id": "input", + "placeholder": {"type": "plain_text", "text": "How many numbers do you need?"}, + "options": [ + {"text": {"type": "plain_text", "text": "5"}, "value": "5"}, + {"text": {"type": "plain_text", "text": "10"}, "value": "10"}, + {"text": {"type": "plain_text", "text": "20"}, "value": "20"}, + ], + "initial_option": {"text": {"type": "plain_text", "text": "5"}, "value": "5"}, + }, + } + ], + }, + ) + +# This listener is invoked when the above modal is submitted +@app.view("configure_assistant_summarize_channel") +def receive_random_number_generation_details(ack: Ack, client: WebClient, payload: dict): + ack() + num = payload["state"]["values"]["num"]["input"]["selected_option"]["value"] + thread = json.loads(payload["private_metadata"]) + + # Post a bot message with structured input data + # The following assistant.bot_message will continue processing + # If you prefer processing this request within this listener, it also works! + # If you don't need bot_message listener, no need to set ignoring_self_assistant_message_events_enabled=False + client.chat_postMessage( + channel=thread["channel_id"], + thread_ts=thread["thread_ts"], + text=f"OK, you need {num} numbers. I will generate it shortly!", + metadata={ + "event_type": "assistant-generate-random-numbers", + "event_payload": {"num": int(num)}, + }, + ) + +# This listener is invoked whenever your app's bot user posts a message +@assistant.bot_message +def respond_to_bot_messages(logger: logging.Logger, set_status: SetStatus, say: Say, payload: dict): + try: + if payload.get("metadata", {}).get("event_type") == "assistant-generate-random-numbers": + # Handle the above random-number-generation request + set_status("is generating an array of random numbers...") + time.sleep(1) + nums: Set[str] = set() + num = payload["metadata"]["event_payload"]["num"] + while len(nums) < num: + nums.add(str(random.randint(1, 100))) + say(f"Here you are: {', '.join(nums)}") + else: + # nothing to do for this bot message + # If you want to add more patterns here, be careful not to cause infinite loop messaging + pass + + except Exception as e: + logger.exception(f"Failed to respond to an inquiry: {e}") +... +``` + +See the [_Creating agents: adding and handling feedback_](/tools/bolt-python/concepts/adding-agent-features#adding-and-handling-feedback) section for adding feedback buttons with Block Kit. + +Want to see the functionality described throughout this guide in action? We've created a [Starter Agent Template](https://github.com/slack-samples/bolt-python-starter-agent) repo for you to build from. \ No newline at end of file diff --git a/docs/content/basic/view_submissions.md b/docs/english/concepts/view-submissions.md similarity index 74% rename from docs/content/basic/view_submissions.md rename to docs/english/concepts/view-submissions.md index b1c3e7cef..b961e0376 100644 --- a/docs/content/basic/view_submissions.md +++ b/docs/english/concepts/view-submissions.md @@ -1,12 +1,6 @@ ---- -title: Listening for view submissions -lang: en -slug: /concepts/view_submissions ---- - +# Listening to views - -If a view payload contains any input blocks, you must listen to `view_submission` requests to receive their values. To listen to `view_submission` requests, you can use the built-in `view()` method. `view()` requires a `callback_id` of type `str` or `re.Pattern`. +If a [view payload](/reference/interaction-payloads/view-interactions-payload/#view_submission) contains any input blocks, you must listen to `view_submission` requests to receive their values. To listen to `view_submission` requests, you can use the built-in `view()` method. `view()` requires a `callback_id` of type `str` or `re.Pattern`. You can access the value of the `input` blocks by accessing the `state` object. `state` contains a `values` object that uses the `block_id` and unique `action_id` to store the input values. @@ -25,8 +19,9 @@ def handle_submission(ack, body): # https://app.slack.com/block-kit-builder/#%7B%22type%22:%22modal%22,%22callback_id%22:%22view_1%22,%22title%22:%7B%22type%22:%22plain_text%22,%22text%22:%22My%20App%22,%22emoji%22:true%7D,%22blocks%22:%5B%5D%7D ack(response_action="update", view=build_new_view(body)) ``` -Similarly, there are options for [displaying errors](https://api.slack.com/surfaces/modals/using#displaying_errors) in response to view submissions. -Read more about view submissions in our API documentation. +Similarly, there are options for [displaying errors](/surfaces/modals#displaying_errors) in response to view submissions. + +Read more about view submissions in our [API documentation](/surfaces/modals#interactions) --- @@ -34,7 +29,7 @@ Read more about view submissions in our API documentation for more information about view_closed. +See the [API documentation](/surfaces/modals#interactions) for more information about `view_closed`. ```python @@ -63,10 +58,7 @@ def handle_view_closed(ack, body, logger): logger.info(body) ``` - - - -Refer to [the module document](https://tools.slack.dev/bolt-python/api-docs/slack_bolt/kwargs_injection/args.html) to learn the available listener arguments. +Refer to [the module document](https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html) to learn the available listener arguments. ```python # Handle a view_submission request @app.view("view_1") @@ -98,6 +90,6 @@ def handle_submission(ack, body, client, view, logger): # Message the user try: client.chat_postMessage(channel=user, text=msg) - except e: + except Exception as e: logger.exception(f"Failed to post a message {e}") ``` diff --git a/docs/english/concepts/web-api.md b/docs/english/concepts/web-api.md new file mode 100644 index 000000000..81f7c9b60 --- /dev/null +++ b/docs/english/concepts/web-api.md @@ -0,0 +1,24 @@ +# Using the Web API + +You can call [any Web API method](/reference/methods) using the `WebClient` provided to your Bolt app as either `app.client` or `client` in middleware/listener arguments (given that your app has the appropriate scopes). When you call one the client's methods, it returns a `SlackResponse` which contains the response from Slack. + +The token used to initialize Bolt can be found in the `context` object, which is required to call most Web API methods. + +:::info[Refer to [the module document](https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html) to learn the available listener arguments.] + +::: + +## Example + +```python +@app.message("wake me up") +def say_hello(client, message): + # Unix Epoch time for September 30, 2020 11:59:59 PM + when_september_ends = 1601510399 + channel_id = message["channel"] + client.chat_scheduleMessage( + channel=channel_id, + post_at=when_september_ends, + text="Summer has come and passed" + ) +``` diff --git a/docs/english/creating-an-app.md b/docs/english/creating-an-app.md new file mode 100644 index 000000000..66e1febee --- /dev/null +++ b/docs/english/creating-an-app.md @@ -0,0 +1,482 @@ +--- +sidebar_label: Creating an app +--- + +# Creating an app with Bolt for Python + +This guide is meant to walk you through getting up and running with a Slack app using Bolt for Python. Along the way, we’ll create a new Slack app, set up your local environment, and develop an app that listens and responds to messages from a Slack workspace. + +When you're finished, you'll have created the [Getting Started app](https://github.com/slackapi/bolt-python/tree/main/examples/getting_started) to run, modify, and make your own. ⚡️ + +--- + +### Create a new app {#create-an-app} +First thing's first: before you start developing with Bolt, you'll want to [create a Slack app](https://api.slack.com/apps/new). + +:::tip[A place to test and learn] + +We recommend using a workspace where you won't disrupt real work getting done — [you can create a new one for free](https://slack.com/get-started#create). + +::: + +After you fill out an app name (_you can change it later_) and pick a workspace to install it to, hit the `Create App` button and you'll land on your app's **Basic Information** page. + +This page contains an overview of your app in addition to important credentials you'll need later. + +![Basic Information page](/img/bolt-python/basic-information-page.png "Basic Information page") + +Look around, add an app icon and description, and then let's start configuring your app 🔩 + +--- + +### Tokens and installing apps {#tokens-and-installing-apps} +Slack apps use [OAuth to manage access to Slack's APIs](/authentication/installing-with-oauth). When an app is installed, you'll receive a token that the app can use to call API methods. + +There are three main token types available to a Slack app: user (`xoxp`), bot (`xoxb`), and app-level (`xapp`) tokens. +- [User tokens](/authentication/tokens#user) allow you to call API methods on behalf of users after they install or authenticate the app. There may be several user tokens for a single workspace. +- [Bot tokens](/authentication/tokens#bot) are associated with bot users, and are only granted once in a workspace where someone installs the app. The bot token your app uses will be the same no matter which user performed the installation. Bot tokens are the token type that _most_ apps use. +- [App-level tokens](/authentication/tokens#app-level) represent your app across organizations, including installations by all individual users on all workspaces in a given organization and are commonly used for creating WebSocket connections to your app. + +We're going to use bot and app-level tokens for this guide. + +1. Navigate to **OAuth & Permissions** on the left sidebar and scroll down to the **Bot Token Scopes** section. Click **Add an OAuth Scope**. + +2. For now, we'll just add one scope: [`chat:write`](/reference/scopes/chat.write). This grants your app the permission to post messages in channels it's a member of. + +3. Scroll up to the top of the **OAuth & Permissions** page and click **Install App to Workspace**. You'll be led through Slack's OAuth UI, where you should allow your app to be installed to your development workspace. + +4. Once you authorize the installation, you'll land on the **OAuth & Permissions** page and see a **Bot User OAuth Access Token**. + +![OAuth Tokens](/img/bolt-python/bot-token.png "Bot OAuth Token") + +5. Head over to **Basic Information** and scroll down under the App Token section and click **Generate Token and Scopes** to generate an app-level token. Add the `connections:write` scope to this token and save the generated `xapp` token. + +6. Navigate to **Socket Mode** on the left side menu and toggle to enable. + +:::tip[Not sharing is sometimes caring] + +Treat your tokens like passwords and [keep them safe](/concepts/security). Your app uses tokens to post and retrieve information from Slack workspaces. + +::: + +--- + +### Setting up your project {#setting-up-your-project} + +With the initial configuration handled, it's time to set up a new Bolt project. This is where you'll write the code that handles the logic for your app. + +If you don’t already have a project, let’s create a new one. Create an empty directory: + +```sh +$ mkdir first-bolt-app +$ cd first-bolt-app +``` + +Next, we recommend using a [Python virtual environment](https://packaging.python.org/guides/installing-using-pip-and-virtual-environments/#creating-a-virtual-environment) to manage your project's dependencies. This is a great way to prevent conflicts with your system's Python packages. Let's create and activate a new virtual environment with [Python 3.7 or later](https://www.python.org/downloads/): + +```sh +$ python3 -m venv .venv +$ source .venv/bin/activate +$ pip install -r requirements.txt +``` + +We can confirm that the virtual environment is active by checking that the path to `python3` is _inside_ your project ([a similar command is available on Windows](https://packaging.python.org/guides/installing-using-pip-and-virtual-environments/#activating-a-virtual-environment)): + +```sh +$ which python3 +# Output: /path/to/first-bolt-app/.venv/bin/python3 +``` + +Before we install the Bolt for Python package to your new project, let's save the **bot token** and **app-level token** that were generated when you configured your app. + +1. **Copy your bot (xoxb) token from the OAuth & Permissions page** and store it in a new environment variable. The following example works on Linux and macOS; but [similar commands are available on Windows](https://superuser.com/questions/212150/how-to-set-env-variable-in-windows-cmd-line/212153#212153). + +```sh +$ export SLACK_BOT_TOKEN=xoxb- +``` + +2. **Copy your app-level (xapp) token from the Basic Information page** and then store it in a new environment variable. + +```sh +$ export SLACK_APP_TOKEN= +``` + +:::warning[Keep it secret. Keep it safe.] + +Remember to keep your tokens secure. At a minimum, you should avoid checking them into public version control, and access them via environment variables as we've done above. Check out the API documentation for more on [best practices for app security](/concepts/security). + +::: + +Now, let's create your app. Install the `slack_bolt` Python package to your virtual environment using the following command: + +```sh +$ pip install slack_bolt +``` + +Create a new file called `app.py` in this directory and add the following code: + +```python +import os +from slack_bolt import App +from slack_bolt.adapter.socket_mode import SocketModeHandler + +# Initializes your app with your bot token and socket mode handler +app = App(token=os.environ.get("SLACK_BOT_TOKEN")) + +# Start your app +if __name__ == "__main__": + SocketModeHandler(app, os.environ["SLACK_APP_TOKEN"]).start() +``` + +Your tokens are enough to create your first Bolt app. Save your `app.py` file then on the command line run the following: + +```sh +$ python3 app.py +``` + +Your app should let you know that it's up and running. 🎉 + +--- + +### Setting up events {#setting-up-events} +Your app behaves similarly to people on your team — it can post messages, add emoji reactions, and listen and respond to events. + +To listen for events happening in a Slack workspace (like when a message is posted or when a reaction is posted to a message) you'll use the [Events API to subscribe to event types](/apis/events-api/). + +For those just starting, we recommend using [Socket Mode](/apis/events-api/using-socket-mode). Socket Mode allows your app to use the Events API and interactive features without exposing a public HTTP Request URL. This can be helpful during development, or if you're receiving requests from behind a firewall. + +That being said, you're welcome to set up an app with a public HTTP Request URL. HTTP is more useful for apps being deployed to hosting environments to respond within a large corporate Slack workspaces/organization, or apps intended for distribution via the Slack Marketplace. + +We've provided instructions for both ways in this guide. + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + + + + +1. Head to your app's configuration page (click on the app [from your app settings page](https://api.slack.com/apps)). Navigate to **Socket Mode** on the left side menu and toggle to enable. + +2. Go to **Basic Information** and scroll down under the App-Level Tokens section and click **Generate Token and Scopes** to generate an app-level token. Add the `connections:write` scope to this token and save the generated `xapp` token, we'll use that in just a moment. + +3. Finally, it's time to tell Slack what events we'd like to listen for. Under **Event Subscriptions**, toggle the switch labeled **Enable Events**. + +When an event occurs, Slack will send your app some information about the event, like the user that triggered it and the channel it occurred in. Your app will process the details and can respond accordingly. + + + + +1. Go back to your app configuration page (click on the app [from your app management page](https://api.slack.com/apps)). Click **Event Subscriptions** on the left sidebar. Toggle the switch labeled **Enable Events**. + +2. Add your Request URL. Slack will send HTTP POST requests corresponding to events to this [Request URL](/apis/events-api/#subscribing) endpoint. Bolt uses the `/slack/events` path to listen to all incoming requests (whether shortcuts, events, or interactivity payloads). When configuring your Request URL within your app configuration, you'll append `/slack/events`, e.g. `https:///slack/events`. 💡 As long as your Bolt app is still running, your URL should become verified. + +:::tip[Using proxy services] + +For local development, you can use a proxy service like ngrok to create a public URL and tunnel requests to your development environment. Refer to [ngrok's getting started guide](https://ngrok.com/docs#getting-started-expose) on how to create this tunnel. And when you get to hosting your app, we've collected some of the most common hosting providers Slack developers use to host their apps [on our API site](/app-management/hosting-slack-apps). + +::: + + + + +Navigate to **Event Subscriptions** on the left sidebar and toggle to enable. Under **Subscribe to Bot Events**, you can add events for your bot to respond to. There are four events related to messages: +- [`message.channels`](/reference/events/message.channels) listens for messages in public channels that your app is added to. +- [`message.groups`](/reference/events/message.groups) listens for messages in 🔒 private channels that your app is added to. +- [`message.im`](/reference/events/message.im) listens for messages in your app's DMs with users. +- [`message.mpim`](/reference/events/message.mpim) listens for messages in multi-person DMs that your app is added to. + +If you want your bot to listen to messages from everywhere it is added to, choose all four message events. After you’ve selected the events you want your bot to listen to, click the green **Save Changes** button. + +--- + +### Listening and responding to a message {#listening-and-responding-to-a-message} +Your app is now ready for some logic. Let's start by using the `message()` method to attach a listener for messages. + +The following example listens and responds to all messages in channels/DMs where your app has been added that contain the word "hello": + + + + +```python +import os +from slack_bolt import App +from slack_bolt.adapter.socket_mode import SocketModeHandler + +# Initializes your app with your bot token and socket mode handler +app = App(token=os.environ.get("SLACK_BOT_TOKEN")) + +# Listens to incoming messages that contain "hello" +# To learn available listener arguments, +# visit https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html +@app.message("hello") +def message_hello(message, say): + # say() sends a message to the channel where the event was triggered + say(f"Hey there <@{message['user']}>!") + +# Start your app +if __name__ == "__main__": + SocketModeHandler(app, os.environ["SLACK_APP_TOKEN"]).start() +``` + + + + +```python +import os +from slack_bolt import App + +# Initializes your app with your bot token and signing secret +app = App( + token=os.environ.get("SLACK_BOT_TOKEN"), + signing_secret=os.environ.get("SLACK_SIGNING_SECRET") +) + +# Listens to incoming messages that contain "hello" +# To learn available listener arguments, +# visit https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html +@app.message("hello") +def message_hello(message, say): + # say() sends a message to the channel where the event was triggered + say(f"Hey there <@{message['user']}>!") + +# Start your app +if __name__ == "__main__": + app.start(port=int(os.environ.get("PORT", 3000))) +``` + + + + +If you restart your app, so long as your bot user has been added to the channel or DM conversation, when you send any message that contains "hello", it will respond. + +This is a basic example, but it gives you a place to start customizing your app based on your own goals. Let's try something a little more interactive by sending a button rather than plain text. + +--- + +### Sending and responding to actions {#sending-and-responding-to-actions} + +To use features like buttons, select menus, datepickers, modals, and shortcuts, you’ll need to enable interactivity. Head over to **Interactivity & Shortcuts** in your app configuration. + + + + +With Socket Mode on, basic interactivity is enabled by default, so no further action is needed. + + + + +Similar to events, you'll need to specify a URL for Slack to send the action (such as _user clicked a button_). Back on your app configuration page, click on **Interactivity & Shortcuts** on the left side. You'll see that there's another **Request URL** box. + +:::tip[By default, Bolt is configured to use the same endpoint for interactive components that it uses for events, so use the same request URL as above (for example, `https://8e8ec2d7.ngrok.io/slack/events`).] + +Press the **Save Changes** button in the lower right hand corner, and that's it. Your app is set up to handle interactivity! + +::: + + + + +When interactivity is enabled, interactions with shortcuts, modals, or interactive components (such as buttons, select menus, and datepickers) will be sent to your app as events. + +Now, let's go back to your app's code and add logic to handle those events: +- First, we'll send a message that contains an interactive component (in this case a button). +- Next, we'll listen for the action of a user clicking the button before responding. + +Below, the code from the last section is modified to send a message containing a button rather than just a string: + + + + +```python +import os +from slack_bolt import App +from slack_bolt.adapter.socket_mode import SocketModeHandler + +# Initializes your app with your bot token and socket mode handler +app = App( + token=os.environ.get("SLACK_BOT_TOKEN"), + # signing_secret=os.environ.get("SLACK_SIGNING_SECRET") # not required for socket mode +) + +# Listens to incoming messages that contain "hello" +@app.message("hello") +def message_hello(message, say): + # say() sends a message to the channel where the event was triggered + say( + blocks=[ + { + "type": "section", + "text": {"type": "mrkdwn", "text": f"Hey there <@{message['user']}>!"}, + "accessory": { + "type": "button", + "text": {"type": "plain_text", "text": "Click Me"}, + "action_id": "button_click" + } + } + ], + text=f"Hey there <@{message['user']}>!" + ) + +# Start your app +if __name__ == "__main__": + SocketModeHandler(app, os.environ["SLACK_APP_TOKEN"]).start() + +``` + + + + +```python +import os +from slack_bolt import App + +# Initializes your app with your bot token and signing secret +app = App( + token=os.environ.get("SLACK_BOT_TOKEN"), + signing_secret=os.environ.get("SLACK_SIGNING_SECRET") +) + +# Listens to incoming messages that contain "hello" +@app.message("hello") +def message_hello(message, say): + # say() sends a message to the channel where the event was triggered + say( + blocks=[ + { + "type": "section", + "text": {"type": "mrkdwn", "text": f"Hey there <@{message['user']}>!"}, + "accessory": { + "type": "button", + "text": {"type": "plain_text", "text": "Click Me"}, + "action_id": "button_click" + } + } + ], + text=f"Hey there <@{message['user']}>!" + ) + +# Start your app +if __name__ == "__main__": + app.start(port=int(os.environ.get("PORT", 3000))) +``` + + + + +The value inside of `say()` is now an object that contains an array of `blocks`. Blocks are the building components of a Slack message and can range from text to images to datepickers. In this case, your app will respond with a section block that includes a button as an accessory. Since we're using `blocks`, the `text` is a fallback for notifications and accessibility. + +You'll notice in the button `accessory` object, there is an `action_id`. This will act as a unique identifier for the button so your app can specify which action it wants to respond to. + +:::tip[Using Block Kit Builder] + +The [Block Kit Builder](https://app.slack.com/block-kit-builder) is an simple way to prototype your interactive messages. The builder lets you (or anyone on your team) mock up messages and generates the corresponding JSON that you can paste directly in your app. + +::: + +Now, if you restart your app and say "hello" in a channel your app is in, you'll see a message with a button. But if you click the button, nothing happens (_yet!_). + +Let's add a handler to send a follow-up message when someone clicks the button: + + + + +```python +import os +from slack_bolt import App +from slack_bolt.adapter.socket_mode import SocketModeHandler + +# Initializes your app with your bot token and socket mode handler +app = App(token=os.environ.get("SLACK_BOT_TOKEN")) + +# Listens to incoming messages that contain "hello" +@app.message("hello") +def message_hello(message, say): + # say() sends a message to the channel where the event was triggered + say( + blocks=[ + { + "type": "section", + "text": {"type": "mrkdwn", "text": f"Hey there <@{message['user']}>!"}, + "accessory": { + "type": "button", + "text": {"type": "plain_text", "text": "Click Me"}, + "action_id": "button_click" + } + } + ], + text=f"Hey there <@{message['user']}>!" + ) + +@app.action("button_click") +def action_button_click(body, ack, say): + # Acknowledge the action + ack() + say(f"<@{body['user']['id']}> clicked the button") + +# Start your app +if __name__ == "__main__": + SocketModeHandler(app, os.environ["SLACK_APP_TOKEN"]).start() +``` + + + + +```python +import os +from slack_bolt import App + +# Initializes your app with your bot token and signing secret +app = App( + token=os.environ.get("SLACK_BOT_TOKEN"), + signing_secret=os.environ.get("SLACK_SIGNING_SECRET") +) + +# Listens to incoming messages that contain "hello" +@app.message("hello") +def message_hello(message, say): + # say() sends a message to the channel where the event was triggered + say( + blocks=[ + { + "type": "section", + "text": {"type": "mrkdwn", "text": f"Hey there <@{message['user']}>!"}, + "accessory": { + "type": "button", + "text": {"type": "plain_text", "text": "Click Me"}, + "action_id": "button_click" + } + } + ], + text=f"Hey there <@{message['user']}>!" + ) + +@app.action("button_click") +def action_button_click(body, ack, say): + # Acknowledge the action + ack() + say(f"<@{body['user']['id']}> clicked the button") + +# Start your app +if __name__ == "__main__": + app.start(port=int(os.environ.get("PORT", 3000))) +``` + + + + +You can see that we used `app.action()` to listen for the `action_id` that we named `button_click`. If you restart your app and click the button, you'll see a new message from your app that says you clicked the button. + +--- + +### Next steps {#next-steps} +You just built your first [Bolt for Python app](https://github.com/slackapi/bolt-python/tree/main/examples/getting_started)! 🎉 + +Now that you have a basic app up and running, you can start exploring how to make your Bolt app stand out. Here are some ideas about what to explore next: + +* Read through the concepts pages to learn about the different methods and features your Bolt app has access to. + +* Explore the different events your bot can listen to with the [`app.event()`](/tools/bolt-python/concepts/event-listening) method. View the full events reference docs [here](/reference/events). + +* Bolt allows you to [call Web API methods](/tools/bolt-python/concepts/web-api) with the client attached to your app. There are over 200 methods; view them [here](/reference/methods). + +* Learn more about the different token types in the [tokens guide](/authentication/tokens). Your app may need different tokens depending on the actions you want it to perform. \ No newline at end of file diff --git a/docs/english/experiments.md b/docs/english/experiments.md new file mode 100644 index 000000000..443a334be --- /dev/null +++ b/docs/english/experiments.md @@ -0,0 +1,11 @@ +# Experiments + +Bolt for Python occasionally includes experimental features still under active development. These features may be fleeting, may not be perfectly polished, and should be thought of as available for use "at your own risk." + +Experimental features are categorized as `semver:patch` until the experimental status is removed. + +We love feedback from our community, so we encourage you to explore and interact with the [GitHub repo](https://github.com/slackapi/bolt-python). Contributions, bug reports, and any feedback are all helpful; let us nurture the Slack CLI together to help make building Slack apps more pleasant for everyone. + +## Available experiments + +There are currently no active experiments. We're steadily staying stable. diff --git a/docs/english/getting-started.md b/docs/english/getting-started.md new file mode 100644 index 000000000..8cfd7faf8 --- /dev/null +++ b/docs/english/getting-started.md @@ -0,0 +1,288 @@ +--- +sidebar_label: Quickstart +title: Quickstart guide with Bolt for Python +--- + +This quickstart guide aims to help you get a Slack app using Bolt for Python up and running as soon as possible! + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +When complete, you'll have a local environment configured with a customized [app](https://github.com/slack-samples/bolt-python-getting-started-app) running to modify and make your own. + +:::tip[Reference for readers] + +In search of the complete guide to building an app from scratch? Check out the [building an app](/tools/bolt-python/creating-an-app) guide. + +::: + +#### Prerequisites + +A few tools are needed for the following steps. We recommend using the [**Slack CLI**](/tools/slack-cli/) for the smoothest experience, but other options remain available. + +You can also begin by installing git and downloading [Python 3.7 or later](https://www.python.org/downloads/), or the latest stable version of Python. Refer to [Python's setup and building guide](https://devguide.python.org/getting-started/setup-building/) for more details. + +Install the latest version of the Slack CLI to get started: + +- [Slack CLI for macOS & Linux](/tools/slack-cli/guides/installing-the-slack-cli-for-mac-and-linux) +- [Slack CLI for Windows](/tools/slack-cli/guides/installing-the-slack-cli-for-windows) + +Then confirm a successful installation with the following command: + +```sh +$ slack version +``` + +An authenticated login is also required if this hasn't been done before: + +```sh +$ slack login +``` + +:::info[A place to belong] + +A workspace where development can happen is also needed. + +We recommend using [developer sandboxes](/tools/developer-sandboxes) to avoid disruptions where real work gets done. + +::: + +## Creating a project {#creating-a-project} + +With the toolchain configured, it's time to set up a new Bolt project. This contains the code that handles logic for your app. + +If you don’t already have a project, let’s create a new one! + + + + +A starter template can be used to start with project scaffolding: + +```sh +$ slack create first-bolt-app --template slack-samples/bolt-python-getting-started-app +$ cd first-bolt-app +``` + +After a project is created you'll have a `requirements.txt` file for app dependencies and a `.slack` directory for Slack CLI configuration. + +A few other files exist too, but we'll visit these later. + + + + +A starter template can be cloned to start with project scaffolding: + +```sh +$ git clone https://github.com/slack-samples/bolt-python-getting-started-app first-bolt-app +$ cd first-bolt-app +``` + +Outlines of a project are taking shape, so we can move on to running the app! + + + + +We recommend using a [Python virtual environment](https://packaging.python.org/guides/installing-using-pip-and-virtual-environments/#creating-a-virtual-environment) to manage your project's dependencies. This is a great way to prevent conflicts with your system's Python packages. Let's create and activate a new virtual environment with [Python 3.7 or later](https://www.python.org/downloads/): + +```sh +$ python3 -m venv .venv +$ source .venv/bin/activate +$ pip install -r requirements.txt +``` + +Confirm the virtual environment is active by checking that the path to `python3` is _inside_ your project ([a similar command is available on Windows](https://packaging.python.org/guides/installing-using-pip-and-virtual-environments/#activating-a-virtual-environment)): + +```sh +$ which python3 +# Output: /path/to/first-bolt-app/.venv/bin/python3 +``` + +## Running the app {#running-the-app} + +Before you can start developing with Bolt, you will want a running Slack app. + + + + +The getting started app template contains a `manifest.json` file with details about an app that we will use to get started. Use the following command and select "Create a new app" to install the app to the team of choice: + +```sh +$ slack run +... +⚡️ Bolt app is running! +``` + +With the app running, you can test it out with the following steps in Slack: + +1. Open a direct message with your app or invite the bot `@first-bolt-app (local)` to a public channel. +2. Send "hello" to the current conversation and wait for a response. +3. Click the attached button labelled "Click Me" to post another reply. + +After confirming the app responds, celebrate, then interrupt the process by pressing `CTRL+C` in the terminal to stop your app from running. + + + + +Navigate to your list of apps and [create a new Slack app](https://api.slack.com/apps/new) using the "from a manifest" option: + +1. Select the workspace to develop your app in. +2. Copy and paste the `manifest.json` file contents to create your app. +3. Confirm the app features and click "Create". + +You'll then land on your app's **Basic Information** page, which is an overview of your app and which contains important credentials: + +![Basic Information page](/img/bolt-python/basic-information-page.png "Basic Information page") + +To listen for events happening in Slack (such as a new posted message) without opening a port or exposing an endpoint, we will use [Socket Mode](/tools/bolt-python/concepts/socket-mode). This connection requires a specific app token: + +1. On the **Basic Information** page, scroll to the **App-Level Tokens** section and click **Generate Token and Scopes**. +2. Name the token "Development" or something similar and add the `connections:write` scope, then click **Generate**. +3. Save the generated `xapp` token as an environment variable within your project: + +```sh +$ export SLACK_APP_TOKEN= +``` + +The above command works on Linux and macOS but [similar commands are available on Windows](https://superuser.com/questions/212150/how-to-set-env-variable-in-windows-cmd-line/212153#212153). + +:::warning[Keep it secret. Keep it safe.] + +Treat your tokens like a password and [keep it safe](/concepts/security). Your app uses these to retrieve and send information to Slack. + +::: + +A bot token is also needed to interact with the Web API methods as your app's bot user. We can gather this as follows: + +1. Navigate to the **OAuth & Permissions** on the left sidebar and install your app to your workspace to generate a token. +2. After authorizing the installation, you'll return to the **OAuth & Permissions** page and find a **Bot User OAuth Token**: + +![OAuth Tokens](/img/bolt-python/bot-token.png "Bot OAuth Token") + +3. Copy the bot token beginning with `xoxb` from the **OAuth & Permissions page** and then store it in a new environment variable: + +```sh +$ export SLACK_BOT_TOKEN=xoxb- +``` + +After saving tokens for the app you created, it is time to run it: + +```sh +$ python3 app.py +... +⚡️ Bolt app is running! +``` + +With the app running, you can test it out with the following steps in Slack: + +1. Open a direct message with your app or invite the bot `@BoltApp` to a public channel. +2. Send "hello" to the current conversation and wait for a response. +3. Click the attached button labelled "Click Me" to post another reply. + +After confirming the app responds, celebrate, then interrupt the process by pressing `CTRL+C` in the terminal to stop your app from running. + + + + +## Updating the app + +At this point, you've successfully run the getting started Bolt for Python [app](https://github.com/slack-samples/bolt-python-getting-started-app)! + +The defaults included leave opportunities abound, so to personalize this app let's now edit the code to respond with a kind farewell. + +#### Responding to a farewell + +Chat is a common thing apps do and responding to various types of messages can make conversations more interesting. + +Using an editor of choice, open the `app.py` file and add the following import to the top of the file, and message listener after the "hello" handler: + +```python +import random + +@app.message("goodbye") +def message_goodbye(say): + responses = ["Adios", "Au revoir", "Farewell"] + parting = random.choice(responses) + say(f"{parting}!") +``` + +Once the file is updated, save the changes and then we'll make sure those changes are being used. + + + + +Run the following command and select the app created earlier to start, or restart, your app with the latest changes: + +```sh +$ slack run +... +⚡️ Bolt app is running! +``` + +After finding the above output appears, open Slack to perform these steps: + +1. Return to the direct message or public channel with your bot. +2. Send "goodbye" to the conversation. +3. Receive a parting response from before and repeat "goodbye" to find another one. + +Your app can be stopped again by pressing `CTRL+C` in the terminal to end these chats. + + + + +Run the following command to start, or restart, your app with the latest changes: + +```sh +$ python3 app.py +... +⚡️ Bolt app is running! +``` + +After finding the above output appears, open Slack to perform these steps: + +1. Return to the direct message or public channel with your bot. +2. Send "goodbye" to the conversation. +3. Receive a parting response from before and repeat "goodbye" to find another one. + +Your app can be stopped again by pressing `CTRL+C` in the terminal to end these chats. + + + + +#### Customizing app settings + +The created app will have some placeholder values and a small set of [scopes](/reference/scopes) to start, but we recommend exploring the customizations possible on app settings. + + + + +Open app settings for your app with the following command: + +```sh +$ slack app settings +``` + +This will open the following page in a web browser: + +![Basic Information page](/img/bolt-python/basic-information-page.png "Basic Information page") + + + + +Browse to https://api.slack.com/apps and select your app "Getting Started Bolt App" from the list. + +This will open the following page: + +![Basic Information page](/img/bolt-python/basic-information-page.png "Basic Information page") + + + + +On these pages you're free to make changes such as updating your app icon, configuring app features, and perhaps even distributing your app! + +## Next steps {#next-steps} + +You can now continue customizing your app with various features to make it right for whatever job's at hand. Here are some ideas about what to explore next: + +- Follow along with the steps that went into making this app on the [creating an app](/tools/bolt-python/creating-an-app) guide for an educational overview. +- Check out the [Agent quickstart](/ai/agent-quickstart) to get up and running with an agent. +- Browse our [curated catalog of samples](/samples) for more apps to use as a starting point for development. \ No newline at end of file diff --git a/docs/content/index.md b/docs/english/index.md similarity index 73% rename from docs/content/index.md rename to docs/english/index.md index 6c15345b5..60df4ec1c 100644 --- a/docs/content/index.md +++ b/docs/english/index.md @@ -1,8 +1,8 @@ # Bolt for Python -Bolt for Python is a Python framework to build Slack apps with the latest Slack platform features. Read the [Getting Started Guide](/getting-started) to set up and run your first Bolt app. +Bolt for Python is a Python framework to build Slack apps with the latest Slack platform features. Read the [Getting Started Guide](/tools/bolt-python/getting-started) to set up and run your first Bolt app. -Then, head over to the Concepts guides. They'll help you build a Bolt app for whatever use case you may have. +Then, explore the rest of the pages within the Guides section. The documentation there will help you build a Bolt app for whatever use case you may have. ## Getting help @@ -13,6 +13,10 @@ If you otherwise get stuck, we're here to help. The following are the best ways * [Issue Tracker](http://github.com/slackapi/bolt-python/issues) for questions, bug reports, feature requests, and general discussion related to Bolt for Python. Try searching for an existing issue before creating a new one. * [Email](mailto:support@slack.com) our developer support team: `support@slack.com`. +## Release notes + +Check out the [Bolt for Python release notes](https://github.com/slackapi/bolt-python/releases) for all the latest happenings. + ## Contributing These docs live within the [Bolt-Python](https://github.com/slackapi/bolt-python/) repository and are open source. diff --git a/docs/english/legacy/steps-from-apps.md b/docs/english/legacy/steps-from-apps.md new file mode 100644 index 000000000..bced20f9e --- /dev/null +++ b/docs/english/legacy/steps-from-apps.md @@ -0,0 +1,195 @@ +# Steps from apps + +:::danger[Steps from Apps is a deprecated feature.] + +Steps from Apps are different than, and not interchangeable with, Slack automation workflows. We encourage those who are currently publishing steps from apps to consider the new [Slack automation features](/workflows/), such as [custom steps for Bolt](/workflows/workflow-steps). + +Please [read the Slack API changelog entry](/changelog/2023-08-workflow-steps-from-apps-step-back) for more information. + +::: + +Steps from apps allow your app to create and process steps that users can add using [Workflow Builder](/workflows/workflow-builder). + +Steps from apps are made up of three distinct user events: + +- Adding or editing the step in a Workflow +- Saving or updating the step's configuration +- The end user's execution of the step + +All three events must be handled for a step from app to function. + +Read more about steps from apps in the [API documentation](/workflows/workflow-steps). + +## Creating steps from apps + +To create a step from app, Bolt provides the `WorkflowStep` class. + +When instantiating a new `WorkflowStep`, pass in the step's `callback_id` and a configuration object. + +The configuration object contains three keys: `edit`, `save`, and `execute`. Each of these keys must be a single callback or a list of callbacks. All callbacks have access to a `step` object that contains information about the step from app event. + +After instantiating a `WorkflowStep`, you can pass it into `app.step()`. Behind the scenes, your app will listen and respond to the step’s events using the callbacks provided in the configuration object. + +Alternatively, steps from apps can also be created using the `WorkflowStepBuilder` class alongside a decorator pattern. For more information, including an example of this approach, [refer to the documentation](https://docs.slack.dev/tools/bolt-python/reference/workflows/step/step.html#slack_bolt.workflows.step.step.WorkflowStepBuilder). + +Refer to the module documents ([common](https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html) / [step-specific](https://docs.slack.dev/tools/bolt-python/reference/workflows/step/utilities/index.html)) to learn the available arguments. + +```python +import os +from slack_bolt import App +from slack_bolt.workflows.step import WorkflowStep + +# Initiate the Bolt app as you normally would +app = App( + token=os.environ.get("SLACK_BOT_TOKEN"), + signing_secret=os.environ.get("SLACK_SIGNING_SECRET") +) + +def edit(ack, step, configure): + pass + +def save(ack, view, update): + pass + +def execute(step, complete, fail): + pass + +# Create a new WorkflowStep instance +ws = WorkflowStep( + callback_id="add_task", + edit=edit, + save=save, + execute=execute, +) + +# Pass Step to set up listeners +app.step(ws) +``` + +## Adding or editing steps from apps + +When a builder adds (or later edits) your step in their workflow, your app will receive a `workflow_step_edit` event. The `edit` callback in your `WorkflowStep` configuration will be run when this event is received. + +Whether a builder is adding or editing a step, you need to send them a step from app configuration modal. This modal is where step-specific settings are chosen, and it has more restrictions than typical modals—most notably, it cannot include `title`, `submit`, or `close` properties. By default, the configuration modal's `callback_id` will be the same as the step from app. + +Within the `edit` callback, the `configure()` utility can be used to easily open your step's configuration modal by passing in the view's blocks with the corresponding `blocks` argument. To disable saving the configuration before certain conditions are met, you can also pass in `submit_disabled` with a value of `True`. + +Refer to the module documents ([common](https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html) / [step-specific](https://docs.slack.dev/tools/bolt-python/reference/workflows/step/utilities/index.html)) to learn the available arguments. + +```python +def edit(ack, step, configure): + ack() + + blocks = [ + { + "type": "input", + "block_id": "task_name_input", + "element": { + "type": "plain_text_input", + "action_id": "name", + "placeholder": {"type": "plain_text", "text": "Add a task name"}, + }, + "label": {"type": "plain_text", "text": "Task name"}, + }, + { + "type": "input", + "block_id": "task_description_input", + "element": { + "type": "plain_text_input", + "action_id": "description", + "placeholder": {"type": "plain_text", "text": "Add a task description"}, + }, + "label": {"type": "plain_text", "text": "Task description"}, + }, + ] + configure(blocks=blocks) + +ws = WorkflowStep( + callback_id="add_task", + edit=edit, + save=save, + execute=execute, +) +app.step(ws) +``` + +## Saving step configurations + +After the configuration modal is opened, your app will listen for the `view_submission` event. The `save` callback in your `WorkflowStep` configuration will be run when this event is received. + +Within the `save` callback, the `update()` method can be used to save the builder's step configuration by passing in the following arguments: + +- `inputs` is a dictionary representing the data your app expects to receive from the user upon step execution. +- `outputs` is a list of objects containing data that your app will provide upon the step's completion. Outputs can then be used in subsequent steps of the workflow. +- `step_name` overrides the default Step name +- `step_image_url` overrides the default Step image + +Refer to the module documents ([common](https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html) / [step-specific](https://docs.slack.dev/tools/bolt-python/reference/workflows/step/utilities/index.html)) to learn the available arguments. + +```python +def save(ack, view, update): + ack() + + values = view["state"]["values"] + task_name = values["task_name_input"]["name"] + task_description = values["task_description_input"]["description"] + + inputs = { + "task_name": {"value": task_name["value"]}, + "task_description": {"value": task_description["value"]} + } + outputs = [ + { + "type": "text", + "name": "task_name", + "label": "Task name", + }, + { + "type": "text", + "name": "task_description", + "label": "Task description", + } + ] + update(inputs=inputs, outputs=outputs) + +ws = WorkflowStep( + callback_id="add_task", + edit=edit, + save=save, + execute=execute, +) +app.step(ws) +``` + +## Executing steps from apps + +When your step from app is executed by an end user, your app will receive a `workflow_step_execute` event. The `execute` callback in your `WorkflowStep` configuration will be run when this event is received. + +Using the `inputs` from the `save` callback, this is where you can make third-party API calls, save information to a database, update the user's Home tab, or decide the outputs that will be available to subsequent steps from apps by mapping values to the `outputs` object. + +Within the `execute` callback, your app must either call `complete()` to indicate that the step's execution was successful, or `fail()` to indicate that the step's execution failed. + +Refer to the module documents ([common](https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html) / [step-specific](https://docs.slack.dev/tools/bolt-python/reference/workflows/step/utilities/index.html)) to learn the available arguments. + +```python +def execute(step, complete, fail): + inputs = step["inputs"] + # if everything was successful + outputs = { + "task_name": inputs["task_name"]["value"], + "task_description": inputs["task_description"]["value"], + } + complete(outputs=outputs) + + # if something went wrong + error = {"message": "Just testing step failure!"} + fail(error=error) + +ws = WorkflowStep( + callback_id="add_task", + edit=edit, + save=save, + execute=execute, +) +app.step(ws) +``` diff --git a/docs/static/img/ai-chatbot/1.png b/docs/english/tutorial/ai-chatbot/1.png similarity index 100% rename from docs/static/img/ai-chatbot/1.png rename to docs/english/tutorial/ai-chatbot/1.png diff --git a/docs/static/img/ai-chatbot/2.png b/docs/english/tutorial/ai-chatbot/2.png similarity index 100% rename from docs/static/img/ai-chatbot/2.png rename to docs/english/tutorial/ai-chatbot/2.png diff --git a/docs/static/img/ai-chatbot/3.png b/docs/english/tutorial/ai-chatbot/3.png similarity index 100% rename from docs/static/img/ai-chatbot/3.png rename to docs/english/tutorial/ai-chatbot/3.png diff --git a/docs/static/img/ai-chatbot/4.png b/docs/english/tutorial/ai-chatbot/4.png similarity index 100% rename from docs/static/img/ai-chatbot/4.png rename to docs/english/tutorial/ai-chatbot/4.png diff --git a/docs/static/img/ai-chatbot/5.png b/docs/english/tutorial/ai-chatbot/5.png similarity index 100% rename from docs/static/img/ai-chatbot/5.png rename to docs/english/tutorial/ai-chatbot/5.png diff --git a/docs/static/img/ai-chatbot/6.png b/docs/english/tutorial/ai-chatbot/6.png similarity index 100% rename from docs/static/img/ai-chatbot/6.png rename to docs/english/tutorial/ai-chatbot/6.png diff --git a/docs/static/img/ai-chatbot/7.png b/docs/english/tutorial/ai-chatbot/7.png similarity index 100% rename from docs/static/img/ai-chatbot/7.png rename to docs/english/tutorial/ai-chatbot/7.png diff --git a/docs/static/img/ai-chatbot/8.png b/docs/english/tutorial/ai-chatbot/8.png similarity index 100% rename from docs/static/img/ai-chatbot/8.png rename to docs/english/tutorial/ai-chatbot/8.png diff --git a/docs/content/tutorial/ai-chatbot.md b/docs/english/tutorial/ai-chatbot/ai-chatbot.md similarity index 55% rename from docs/content/tutorial/ai-chatbot.md rename to docs/english/tutorial/ai-chatbot/ai-chatbot.md index 9fec871a0..2fcc16e9a 100644 --- a/docs/content/tutorial/ai-chatbot.md +++ b/docs/english/tutorial/ai-chatbot/ai-chatbot.md @@ -1,98 +1,142 @@ # AI Chatbot -In this tutorial, you'll learn how to bring the power of AI into your Slack workspace using a chatbot called Bolty that uses Anthropic or OpenAI. Here's what we'll do with this sample app: - -1. Create your app from an app manifest and clone a starter template -2. Set up and run your local project -3. Create a workflow using Workflow Builder to summarize messages in conversations -4. Select your preferred API and model to customize Bolty's responses -5. Interact with Bolty via direct message, the `/ask-bolty` slash command, or by mentioning the app in conversations +In this tutorial, you'll learn how to bring the power of AI into your Slack workspace using a chatbot called Bolty that uses Anthropic or OpenAI. + +With Bolty, users can: + +- send direct messages to Bolty and get AI-powered responses in response, +- use the `/ask-bolty` slash command to ask Bolty questions, and +- receive channel summaries when joining new channels. + +Intrigued? First, grab your tools by following the three steps below. + +import QuickstartGuide from '@site/src/components/QuickstartGuide'; + + + +
## Prerequisites {#prereqs} -Before getting started, you will need the following: - -* a development workspace where you have permissions to install apps. If you don’t have a workspace, go ahead and set that up now—you can [go here](https://slack.com/get-started#create) to create one, or you can join the [Developer Program](https://api.slack.com/developer-program) and provision a sandbox with access to all Slack features for free. -* a development environment with [Python 3.6](https://www.python.org/downloads/) or later. -* an Anthropic or OpenAI account with sufficient credits, and in which you have generated a secret key. - -**Skip to the code** -If you'd rather skip the tutorial and just head straight to the code, you can use our [Bolt for Python AI Chatbot sample](https://github.com/slack-samples/bolt-python-ai-chatbot) as a template. +You will also need the following: -## Creating your app {#create-app} - -1. Navigate to the [app creation page](https://api.slack.com/apps/new) and select **From a manifest**. -2. Select the workspace you want to install the application in. -3. Copy the contents of the [`manifest.json`](https://github.com/slack-samples/bolt-python-ai-chatbot/blob/main/manifest.json) file into the text box that says **Paste your manifest code here** (within the **JSON** tab) and click **Next**. -4. Review the configuration and click **Create**. -5. You're now in your app configuration's **Basic Information** page. Navigate to the **Install App** link in the left nav and click **Install to Workspace*, then **Allow** on the screen that follows. +- a development workspace where you have permissions to install apps. If you don’t have a workspace you can join the [Developer Program](https://api.slack.com/developer-program) and provision a sandbox with access to all Slack features for free. +- a development environment with [Python 3.7](https://www.python.org/downloads/) or later. +- an Anthropic or OpenAI account with sufficient credits, and in which you have generated a secret key. ### Obtaining and storing your environment variables {#environment-variables} Before you'll be able to successfully run the app, you'll need to first obtain and set some environment variables. -1. On the **Install App** page, copy your **Bot User OAuth Token**. You will store this in your environment as `SLACK_BOT_TOKEN` (we'll get to that next). -2. Navigate to **Basic Information** and in the **App-Level Tokens** section , click **Generate Token and Scopes**. Add the [`connections:write`](https://api.slack.com/scopes/connections:write) scope, name the token, and click **Generate**. (For more details, refer to [understanding OAuth scopes for bots](https://api.slack.com/tutorials/tracks/understanding-oauth-scopes-bot)). Copy this token. You will store this in your environment as `SLACK_APP_TOKEN`. +#### Provider tokens {#provider-tokens} + +Models from different AI providers are available if the corresponding environment variable is added as shown in the sections below. + + + -To store your tokens and environment variables, run the following commands in the terminal. Replace the placeholder values with your bot and app tokens collected above, as well as the key or keys for the AI provider or providers you want to use: +To interact with Anthropic models, navigate to your Anthropic account dashboard to [create an API key](https://console.anthropic.com/settings/keys), then export the key as follows: -**For macOS** ```bash -export SLACK_BOT_TOKEN= -export SLACK_APP_TOKEN= -export OPENAI_API_KEY= export ANTHROPIC_API_KEY= ``` -**For Windows** -```bash -set SLACK_BOT_TOKEN= -set SLACK_APP_TOKEN= -set OPENAI_API_KEY= -set ANTHROPIC_API_KEY= -``` + + -## Setting up and running your local project {#configure-project} +To use Google Cloud Vertex AI, [follow this quick start](https://cloud.google.com/vertex-ai/generative-ai/docs/start/quickstarts/quickstart-multimodal#expandable-1) to create a project for sending requests to the Gemini API, then gather [Application Default Credentials](https://cloud.google.com/docs/authentication/provide-credentials-adc) with the strategy to match your development environment. -Clone the starter template onto your machine by running the following command: +Once your project and credentials are configured, export environment variables to select from Gemini models: ```bash -git clone https://github.com/slack-samples/bolt-python-ai-chatbot.git +export VERTEX_AI_PROJECT_ID= +export VERTEX_AI_LOCATION= ``` -Change into the new project directory: +The project location can be located under the **Region** on the [Vertex AI](https://console.cloud.google.com/vertex-ai) dashboard, as well as more details about available Gemini models. + + + + +Unlock the OpenAI models from your OpenAI account dashboard by clicking [create a new secret key](https://platform.openai.com/api-keys), then export the key like so: ```bash -cd bolt-python-ai-chatbot +export OPENAI_API_KEY= ``` + + + +## Setting up and running your local project {#configure-project} + + Start your Python virtual environment: -**For macOS** + + + ```bash python3 -m venv .venv source .venv/bin/activate ``` -**For Windows** + + + ```bash py -m venv .venv .venv\Scripts\activate ``` + + + Install the required dependencies: ```bash pip install -r requirements.txt ``` -Start your local server: +Run your app locally: ```bash -python app.py +slack run ``` -If your app is up and running, you'll see a message that says "⚡️ Bolt app is running!" +If your app is indeed up and running, you'll see a message that says "⚡️ Bolt app is running!" ## Choosing your provider {#provider} @@ -100,7 +144,7 @@ Navigate to the Bolty **App Home** and select a provider from the drop-down menu If you don't see Bolty listed under **Apps** in your workspace right away, never fear! You can mention **@Bolty** in a public channel to add the app, then navigate to your **App Home**. -![Choose your AI provider](/img/ai-chatbot/6.png) +![Choose your AI provider](6.png) ## Setting up your workflow {#workflow} @@ -108,11 +152,11 @@ Within your development workspace, open Workflow Builder by clicking on your wor Click **Untitled Workflow** at the top to rename your workflow. For this tutorial, we'll call the workflow **Welcome to the channel**. Enter a description, such as _Summarizes channels for new members_, and click **Save**. -![Setting up a new workflow](/img/ai-chatbot/1.png) +![Setting up a new workflow](1.png) Select **Choose an event** under **Start the workflow...**, and then choose **When a person joins a channel**. Select the channel name from the drop-down menu and click **Save**. -![Start the workflow](/img/ai-chatbot/2.png) +![Start the workflow](2.png) Under **Then, do these things**, click **Add steps** and complete the following: @@ -121,20 +165,20 @@ Under **Then, do these things**, click **Add steps** and complete the following: 3. Under **Add a message**, enter a short message, such as _Hi! Welcome to `{}The channel that the user joined`. Would you like a summary of the recent conversation?_ Note that the _`{}The channel that the user joined`_ is a variable; you can insert it by selecting **{}Insert a variable** at the bottom of the message text box. 4. Select the **Add Button** button, and name the button _Yes, give me a summary_. Click **Done**. -![Send a message](/img/ai-chatbot/3.png) +![Send a message](3.png) -We'll add two more steps under the **Then, do these things** section. +We'll add two more steps under the **Then, do these things** section. First, scroll to the bottom of the list of steps and choose **Custom**, then choose **Bolty** and **Bolty Custom Function**. In the **Channel** drop-down menu, select **Channel that the user joined**. Click **Save**. -![Bolty custom function](/img/ai-chatbot/4.png) +![Bolty custom function](4.png) For the final step, complete the following: 1. Choose **Messages** and then **Send a message to a person**. Under **Select a member**, choose **Person who clicked the button** from the drop-down menu. 2. Under **Add a message**, click **Insert a variable** and choose **`{}Summary`** under the **Bolty Custom Function** section in the list that appears. Click **Save**. -![Summary](/img/ai-chatbot/5.png) +![Summary](5.png) When finished, click **Finish Up**, then click **Publish** to make the workflow available in your workspace. @@ -142,16 +186,16 @@ When finished, click **Finish Up**, then click **Publish** to make the workflow ### Summarizing recent conversations {#summarize} -In order for Bolty to provide summaries of recent conversation in a channel, Bolty _must_ be a member of that channel. +In order for Bolty to provide summaries of recent conversation in a channel, Bolty _must_ be a member of that channel. -1. Invite Bolty to a channel that you are able to leave and rejoin (for example, not the **#general** channel or a private channel someone else created) by mentioning the app in the channel—i.e., tagging **@Bolty** in the channel and sending your message. +1. Invite Bolty to a channel that you are able to leave and rejoin (for example, not the **#general** channel or a private channel someone else created) by mentioning the app in the channel — i.e., tagging **@Bolty** in the channel and sending your message. 2. Slackbot will prompt you to either invite Bolty to the channel, or do nothing. Click **Invite Them**. Now when new users join the channel, the workflow you just created will be kicked off. To test this, leave the channel you just invited Bolty to and rejoin it. This will kick off your workflow and you'll receive a direct message from **Welcome to the channel**. Click the **Yes, give me a summary** button, and Bolty will summarize the recent conversations in the channel you joined. -![Channel summary](/img/ai-chatbot/7.png) +![Channel summary](7.png) -The central part of this functionality is shown in the following code snippet. Note the use of the [`user_context`](https://api.slack.com/automation/types#usercontext) object, a Slack type that represents the user who is interacting with our workflow, as well as the `history` of the channel that will be summarized, which includes the ten most recent messages. +The central part of this functionality is shown in the following code snippet. Note the use of the [`user_context`](/tools/deno-slack-sdk/reference/slack-types#usercontext) object, a Slack type that represents the user who is interacting with our workflow, as well as the `history` of the channel that will be summarized, which includes the ten most recent messages. ```python from ai.providers import get_provider_response @@ -167,7 +211,6 @@ It retrieves the conversation history, parses it, generates a summary using an A and completes the workflow with the summary or fails if an error occurs. """ - def handle_summary_function_callback( ack: Ack, inputs: dict, fail: Fail, logger: Logger, client: WebClient, complete: Complete ): @@ -190,14 +233,13 @@ def handle_summary_function_callback( To ask Bolty a question, you can chat with Bolty in any channel the app is in. Use the `\ask-bolty` slash command to provide a prompt for Bolty to answer. Note that Bolty is currently not supported in threads. -You can also navigate to **Bolty** in your **Apps** list and select the **Messages** tab to chat with Bolty directly. +You can also navigate to **Bolty** in your **Apps** list and select the **Messages** tab to chat with Bolty directly. -![Ask Bolty](/img/ai-chatbot/8.png) +![Ask Bolty](8.png) ## Next steps {#next-steps} Congratulations! You've successfully integrated the power of AI into your workspace. Check out these links to take the next steps in your Bolt for Python journey. -* To learn more about Bolt for Python, refer to the [Getting started](../getting-started) documentation. -* For more details about creating workflow steps using the Bolt SDK, refer to the [workflow steps for Bolt](https://api.slack.com/automation/functions/custom-bolt) guide. -* To use the Bolt for Python SDK to develop on the automations platform, refer to the [Create a workflow step for Workflow Builder: Bolt for Python](https://api.slack.com/tutorials/tracks/bolt-custom-function) tutorial. +- To learn more about Bolt for Python, refer to the [Getting started](/tools/bolt-python/getting-started) documentation. +- For more details about creating workflow steps using the Bolt SDK, refer to the [workflow steps for Bolt](/workflows/workflow-steps) guide. \ No newline at end of file diff --git a/docs/static/img/custom-steps-jira/1.png b/docs/english/tutorial/custom-steps-for-jira/1.png similarity index 100% rename from docs/static/img/custom-steps-jira/1.png rename to docs/english/tutorial/custom-steps-for-jira/1.png diff --git a/docs/static/img/custom-steps-jira/2.png b/docs/english/tutorial/custom-steps-for-jira/2.png similarity index 100% rename from docs/static/img/custom-steps-jira/2.png rename to docs/english/tutorial/custom-steps-for-jira/2.png diff --git a/docs/static/img/custom-steps-jira/3.png b/docs/english/tutorial/custom-steps-for-jira/3.png similarity index 100% rename from docs/static/img/custom-steps-jira/3.png rename to docs/english/tutorial/custom-steps-for-jira/3.png diff --git a/docs/static/img/custom-steps-jira/4.png b/docs/english/tutorial/custom-steps-for-jira/4.png similarity index 100% rename from docs/static/img/custom-steps-jira/4.png rename to docs/english/tutorial/custom-steps-for-jira/4.png diff --git a/docs/static/img/custom-steps-jira/5.png b/docs/english/tutorial/custom-steps-for-jira/5.png similarity index 100% rename from docs/static/img/custom-steps-jira/5.png rename to docs/english/tutorial/custom-steps-for-jira/5.png diff --git a/docs/static/img/custom-steps-jira/6.png b/docs/english/tutorial/custom-steps-for-jira/6.png similarity index 100% rename from docs/static/img/custom-steps-jira/6.png rename to docs/english/tutorial/custom-steps-for-jira/6.png diff --git a/docs/static/img/custom-steps-jira/7.png b/docs/english/tutorial/custom-steps-for-jira/7.png similarity index 100% rename from docs/static/img/custom-steps-jira/7.png rename to docs/english/tutorial/custom-steps-for-jira/7.png diff --git a/docs/content/tutorial/custom-steps-for-jira.md b/docs/english/tutorial/custom-steps-for-jira/custom-steps-for-jira.md similarity index 82% rename from docs/content/tutorial/custom-steps-for-jira.md rename to docs/english/tutorial/custom-steps-for-jira/custom-steps-for-jira.md index 5da362a83..d74b82b8e 100644 --- a/docs/content/tutorial/custom-steps-for-jira.md +++ b/docs/english/tutorial/custom-steps-for-jira/custom-steps-for-jira.md @@ -11,8 +11,8 @@ In this tutorial, you'll learn how to configure custom steps for use with JIRA. Before getting started, you will need the following: -* a development workspace where you have permissions to install apps. If you don’t have a workspace, go ahead and set that up now—you can [go here](https://slack.com/get-started#create) to create one, or you can join the [Developer Program](https://api.slack.com/developer-program) and provision a sandbox with access to all Slack features for free. -* a development environment with [Python 3.6](https://www.python.org/downloads/) or later. +* a development workspace where you have permissions to install apps. If you don’t have a workspace, go ahead and set that up now—you can [go here](https://slack.com/get-started#create) to create one, or you can join the [Developer Program](https://api.slack.com/developer-program) and provision a sandbox with access to all Slack features for free. +* a development environment with [Python 3.7](https://www.python.org/downloads/) or later. **Skip to the code** If you'd rather skip the tutorial and just head straight to the code, you can use our [Bolt for Python JIRA functions sample](https://github.com/slack-samples/bolt-python-jira-functions) as a template. @@ -21,7 +21,7 @@ If you'd rather skip the tutorial and just head straight to the code, you can us 1. Navigate to the [app creation page](https://api.slack.com/apps/new) and select **From a manifest**. 2. Select the workspace you want to install the application in, then click **Next**. -3. Copy the contents of the [`manifest.json`](https://github.com/slack-samples/bolt-python-ai-chatbot/blob/main/manifest.json) file below into the text box that says **Paste your manifest code here** (within the **JSON** tab), then click **Next**: +3. Copy the contents of the [`manifest.json`](https://github.com/slack-samples/bolt-python-jira-functions/blob/main/manifest.json) file below into the text box that says **Paste your manifest code here** (within the **JSON** tab), then click **Next**: ```js reference title="manifest.json" https://github.com/slack-samples/bolt-python-jira-functions/blob/main/manifest.json @@ -35,7 +35,7 @@ https://github.com/slack-samples/bolt-python-jira-functions/blob/main/manifest.j Before you'll be able to successfully run the app, you'll need to obtain and set some environment variables. 1. Once you have installed the app to your workspace, copy the **Bot User OAuth Token** from the **Install App** page. You will store this in your environment as `SLACK_BOT_TOKEN` (we'll get to that next). -2. Navigate to **Basic Information** and in the **App-Level Tokens** section , click **Generate Token and Scopes**. Add the [`connections:write`](https://api.slack.com/scopes/connections:write) scope, name the token, and click **Generate**. (For more details, refer to [understanding OAuth scopes for bots](https://api.slack.com/tutorials/tracks/understanding-oauth-scopes-bot)). Copy this token. You will store this in your environment as `SLACK_APP_TOKEN`. +2. Navigate to **Basic Information** and in the **App-Level Tokens** section , click **Generate Token and Scopes**. Add the [`connections:write`](/reference/scopes/connections.write) scope, name the token, and click **Generate**. Copy this token. You will store this in your environment as `SLACK_APP_TOKEN`. 3. Follow [these instructions](https://confluence.atlassian.com/adminjiraserver0909/configure-an-incoming-link-1251415519.html) to create an external app link and to generate its redirect URL (the base of which will be stored as your APP_BASE_URL variable below), client ID, and client secret. 4. Run the following commands in your terminal to store your environment variables, client ID, and client secret. 5. You'll also need to know your team ID (found by opening your Slack instance in a web browser and copying the value within the link that starts with the letter **T**) and your app ID (found under **Basic Information**). @@ -127,21 +127,21 @@ If your app is up and running, you'll see a message noting that the app is start 2. Select **New Workflow** > **Build Workflow**. 3. Click **Untitled Workflow** at the top of the pane to rename your workflow. We'll call it **Create Issue**. For the description, enter _Creates a new issue_, then click **Save**. -![Workflow details](/img/custom-steps-jira/1.png) +![Workflow details](1.png) 4. Select **Choose an event** under **Start the workflow...**, and then select **From a link in Slack**. Click **Continue**. -![Start the workflow](/img/custom-steps-jira/2.png) +![Start the workflow](2.png) 5. Under **Then, do these things** click **Add steps** to add the custom step. Your custom step will be the function defined in the [`create_issue.py`](https://github.com/slack-samples/bolt-python-jira-functions/blob/main/listeners/functions/create_issue.py) file. Scroll down to the bottom of the list on the right-hand pane and select **Custom**, then **BoltPy Jira Functions** > **Create an issue**. Enter the project details, issue type (optional), summary (optional), and description (optional). Click **Save**. -![Custom function](/img/custom-steps-jira/3.png) +![Custom function](3.png) 6. Add another step and select **Messages** > **Send a message to a channel**. Select **Channel where the workflow was used** from the drop-down list and then select **Insert a variable** and **Issue url**. Click **Save**. -![Insert variable for issue URL](/img/custom-steps-jira/4.png) +![Insert variable for issue URL](4.png) 7. Click **Publish** to make the workflow available to your workspace. @@ -150,16 +150,16 @@ If your app is up and running, you'll see a message noting that the app is start 1. Copy your workflow link. 2. Navigate to your app's home tab and click **Connect an Account** to connect your JIRA account to the app. -![Connect account](/img/custom-steps-jira/5.png) +![Connect account](5.png) 3. Click **Allow** on the screen that appears. -![Allow connection](/img/custom-steps-jira/6.png) +![Allow connection](6.png) 4. In any channel, post the workflow link you copied. 5. Click **Start Workflow** and observe as the link to a new JIRA ticket is posted in the channel. Click the link to be directed to the newly-created issue within your JIRA project. -![JIRA issue](/img/custom-steps-jira/7.png) +![JIRA issue](7.png) When finished, you can click the **Disconnect Account** button in the home tab to disconnect your app from your JIRA account. @@ -167,6 +167,6 @@ When finished, you can click the **Disconnect Account** button in the home tab t Congratulations! You've successfully customized your workspace with custom steps in Workflow Builder. Check out these links to take the next steps in your journey. -* To learn more about Bolt for Python, refer to the [getting started](/getting-started) documentation. -* For more details about creating workflow steps using the Bolt SDK, refer to the [workflow steps for Bolt](https://api.slack.com/automation/functions/custom-bolt) guide. -* For information about custom steps dynamic options, refer to [custom steps dynamic options in Workflow Builder](https://api.slack.com/automation/runonslack/custom-steps-dynamic-options). +* To learn more about Bolt for Python, refer to the [getting started](/tools/bolt-python/getting-started) documentation. +* For more details about creating workflow steps using the Bolt SDK, refer to the [workflow steps for Bolt](/workflows/workflow-steps) guide. +* For information about custom steps dynamic options, refer to [custom steps dynamic options in Workflow Builder](/tools/bolt-python/concepts/custom-steps-dynamic-options). diff --git a/docs/english/tutorial/custom-steps-workflow-builder-existing/add-step.png b/docs/english/tutorial/custom-steps-workflow-builder-existing/add-step.png new file mode 100644 index 000000000..81b32d5e0 Binary files /dev/null and b/docs/english/tutorial/custom-steps-workflow-builder-existing/add-step.png differ diff --git a/docs/english/tutorial/custom-steps-workflow-builder-existing/app-message.png b/docs/english/tutorial/custom-steps-workflow-builder-existing/app-message.png new file mode 100644 index 000000000..a8420a6b5 Binary files /dev/null and b/docs/english/tutorial/custom-steps-workflow-builder-existing/app-message.png differ diff --git a/docs/english/tutorial/custom-steps-workflow-builder-existing/custom-steps-workflow-builder-existing.md b/docs/english/tutorial/custom-steps-workflow-builder-existing/custom-steps-workflow-builder-existing.md new file mode 100644 index 000000000..9a5e3ee50 --- /dev/null +++ b/docs/english/tutorial/custom-steps-workflow-builder-existing/custom-steps-workflow-builder-existing.md @@ -0,0 +1,280 @@ +# Custom Steps for Workflow Builder (existing app) + +:::info[This feature requires a paid plan] +If you don't have a paid workspace for development, you can join the [Developer Program](https://api.slack.com/developer-program) and provision a sandbox with access to all Slack features for free. +::: + +If you followed along with our [create a custom step for Workflow Builder: new app](/tools/bolt-python/tutorial/custom-steps-workflow-builder-new) tutorial, you have seen how to add custom steps to a brand new app. But what if you have an app up and running currently to which you'd like to add custom steps? You've come to the right place! + +In this tutorial we will: +- Start with an existing Bolt app +- Add a custom **workflow step** in the [app settings](https://api.slack.com/apps) +- Wire up the new step to a **function listener** in our project, using the [Bolt for Python](https://docs.slack.dev/tools/bolt-python/) framework +- See the step as a custom workflow step in Workflow Builder + +## Prerequisites {#prereqs} + +The custom steps feature is compatible with Bolt version 1.20.0 and above. First, update your `requirements.txt` file to reflect version 1.20.0 of Bolt (e.g., `slack-bolt>=1.20.0`), then run the following commands in your terminal: + +```sh +python3 -m venv .venv +source .venv/bin/activate +pip install -r requirements.txt +``` + +In order to add custom workflow steps to an app, the app also needs to be org-ready. To do this, navigate to your [app settings page](https://api.slack.com/apps) and select your Bolt app. + +Navigate to **Org Level Apps** in the left nav and click **Opt-In**, then confirm **Yes, Opt-In**. + +![Make your app org-ready](org-ready.png) + +## Adding a new workflow step {#add-step} + +Before we can add the new workflow step, we first need to ensure the workflow step is listening for the `function_executed` event so that our app knows when the workflow step is executed. + +### Adding the `function_executed` event subscription {#event-subscription} + +Navigate to **App Manifest** in the left nav and add the `function_executed` event subscription, then click **Save Changes**: + +```json +... + "settings": { + "event_subscriptions": { + "bot_events": [ + ... + "function_executed" + ] + }, + } +``` + +### Adding the workflow step {#add-step} + +Navigate to **Workflow Steps** in the left nav and click **Add Step**. This is where we'll configure our step's inputs, outputs, name, and description. + +![Add step](add-step.png) + +For illustration purposes in this tutorial, we're going to write a custom step called Request Time Off. When the step is invoked, a message will be sent to the provided manager with an option to approve or deny the time-off request. When the manager takes an action (approves or denies the request), a message is posted with the decision and the manager who made the decision. The step will take two user IDs as inputs, representing the requesting user and their manager, and it will output both of those user IDs as well as the decision made. + +Add the pertinent details to the step: + +![Define step](define-step.png) + +Remember this `callback_id`. We will use this later when implementing a function listener. Then add the input and output parameters: + +![Add inputs](inputs.png) + +![Add outputs](outputs.png) + +Save your changes. + +### Viewing our updates in the App Manifest {#view-updates} + +Navigate to **App Manifest** and notice your new step reflected in the `functions` property! Exciting. It should look like this: + +```json +"functions": { + "request_time_off": { + "title": "Request time off", + "description": "Submit a request to take time off", + "input_parameters": { + "manager_id": { + "type": "slack#/types/user_id", + "title": "Manager", + "description": "Approving manager", + "is_required": true, + "hint": "Select a user in the workspace", + "name": "manager_id" + }, + "submitter_id": { + "type": "slack#/types/user_id", + "title": "Submitting user", + "description": "User that submitted the request", + "is_required": true, + "name": "submitter_id" + } + }, + "output_parameters": { + "manager_id": { + "type": "slack#/types/user_id", + "title": "Manager", + "description": "Approving manager", + "is_required": true, + "name": "manager_id" + }, + "request_decision": { + "type": "boolean", + "title": "Request decision", + "description": "Decision to the request for time off", + "is_required": true, + "name": "request_decision" + }, + "submitter_id": { + "type": "slack#/types/user_id", + "title": "Submitting user", + "description": "User that submitted the request", + "is_required": true, + "name": "submitter_id" + } + } + } + } +``` + +Next, we'll define a function listener to handle what happens when the workflow step is used. + +## Adding function and action listeners {#adding-listeners} + +### Implementing the function listener {#function-listener} + +Direct your attention back to your app project in VSCode or your preferred code editor. Here we'll add logic that your app will execute when the custom step is executed. + +Open your `app.py` file and add the following function listener code for the `request_time_off` step. + +```py +@app.function("request_time_off") +def handle_request_time_off(inputs: dict, fail: Fail, logger: logging.Logger, say: Say): + + submitter_id = inputs["submitter_id"] + manager_id = inputs["manager_id"] + + try: + say( + channel=manager_id, + text=f"<@{submitter_id}> requested time off! What say you?", + blocks=[ + { + "type": 'section', + "text": { + "type": 'mrkdwn', + "text": f"<@{submitter_id}> requested time off! What say you?", + }, + }, + { + 'type': 'actions', + 'elements': [ + { + 'type': 'button', + 'text': { + 'type': 'plain_text', + 'text': 'Approve', + 'emoji': True, + }, + 'value': 'approve', + 'action_id': 'approve_button', + }, + { + 'type': 'button', + 'text': { + 'type': 'plain_text', + 'text': 'Deny', + 'emoji': True, + }, + 'value': 'deny', + 'action_id': 'deny_button', + }, + ], + }, + ], + ) + except Exception as e: + logger.exception(e) + fail(f"Failed to handle a function request (error: {e})") +``` + +#### Anatomy of a `.function()` listener {#function-listener-anatomy} + +The function decorator (`function()`) accepts an argument of type `str` and is the unique callback ID of the step. For our custom step, we’re using `request_time_off`. Every custom step you implement in an app needs to have a unique callback ID. + +The callback function is where we define the logic that will run when Slack tells the app that a user in the Slack client started a workflow that contains the `request_time_off` custom step. + +The callback function offers various utilities that can be used to take action when a function execution event is received. The ones we’ll be using here are: + +* `inputs` provides access to the workflow variables passed into the step when the workflow was started +* `fail` indicates when the step invoked for the current workflow step has an error +* `logger` provides a Python standard logger instance +* `say` calls the `chat.Postmessage` API method + +### Implementing the action listener {#action-listener} + +This custom step also requires an action listener to respond to the action of a user clicking a button. + +In that same `app.py` file, add the following action listener: + +```py +@app.action(re.compile("(approve_button|deny_button)")) +def manager_resp_handler(ack: Ack, action, body: dict, client: WebClient, complete: Complete, fail: Fail, logger: logging.Logger): + + ack() + + try: + inputs = body['function_data']['inputs'] + manager_id = inputs['manager_id'] + submitter_id = inputs['submitter_id'] + request_decision = action['value'] + + client.chat_update( + channel=body['channel']['id'], + ts=body["message"]["ts"], + text=f"Request {'approved' if request_decision == 'approve' else 'denied'}!" + ) + + complete({ + 'manager_id': manager_id, + 'submitter_id': submitter_id, + 'request_decision': request_decision == 'approve' + }) + + except Exception as e: + logger.exception(e) + fail(f"Failed to handle a function request (error: {e})") +``` + +#### Anatomy of an `.action()` listener {#action-listener-anatomy} + +Similar to a function listener, the action listener registration method (`.action()`) takes two arguments: + +- The first argument is the unique callback ID of the action that your app will respond to. In our case, because we want to execute the same logic for both buttons, we’re using a little bit of RegEx magic to listen for two callback IDs at the same time — `approve_button` and `deny_button`. +- The second argument is an asynchronous callback function, where we define the logic that will run when Slack tells our app that the manager has clicked or tapped the Approve button or the Deny button. + +Just like the function listener’s callback function, the action listener’s callback function offers various utilities that can be used to take action when an action event is received. The ones we’ll be using here are: +- `client`, which provides access to Slack API methods +- `action`, which provides the action’s event payload +- `complete`, which is a utility method indicating to Slack that the step behind the workflow step that was just invoked has completed successfully +- `fail`, which is a utility method for indicating that the step invoked for the current workflow step had an error + +Slack will send an action event payload to your app when one of the buttons is clicked or tapped. In the action listener, we’ll extract all the information we can use, and if all goes well, let Slack know the step was successful by invoking complete. We’ll also handle cases where something goes wrong and produces an error. + +Now that the custom step has been added to the app and we've defined step and action listeners for it, we're ready to see the step in action in Workflow Builder. Go ahead and run your app to pick up the changes. + +### Creating a workflow with the new step {#add-new-step} + +Turn your attention to the Slack client where your app is installed. + +Open Workflow Builder by clicking on the workspace name, then **Tools**, then **Workflow Builder**. + +Click the button to create a **New Workflow**, then **Build Workflow**. Choose to start your workflow **from a link in Slack**. + +In the **Steps** pane to the right, search for your app name and locate the **Request time off** step we created. + +![Find step](find-step.png) + +Select the step and choose the desired inputs and click **Save**. + +![Step inputs](step-inputs.png) + +Next, click **Finish Up**, give your workflow a name and description, then click **Publish**. Copy the link for your workflow on the next screen, then click **Done**. + +### Running the workflow {#run-workflow} + +In any channel where your app is installed, paste the link you copied and send it as a message. The link will unfurl into a button to start the workflow. Click the button to start the workflow. If you set yourself up as the manager, you will then see a message from your app. Pressing either button will return a confirmation or denial of your time off request. + +![Message](app-message.png) + +## Next steps {#next-steps} + +Nice work! Now that you've added a workflow step to your Bolt app, a world of possibilities is open to you! Create and share workflow steps across your organization to optimize Slack users' time and make their working lives more productive. + +If you're looking to create a brand new Bolt app with custom workflow steps, check out [the tutorial here](/tools/bolt-python/tutorial/custom-steps-workflow-builder-new). + +If you're interested in exploring how to create custom steps to use in Workflow Builder as steps with our Deno Slack SDK, too, that tutorial can be found [here](/tools/deno-slack-sdk/tutorials/workflow-builder-custom-step/). diff --git a/docs/english/tutorial/custom-steps-workflow-builder-existing/define-step.png b/docs/english/tutorial/custom-steps-workflow-builder-existing/define-step.png new file mode 100644 index 000000000..32b578576 Binary files /dev/null and b/docs/english/tutorial/custom-steps-workflow-builder-existing/define-step.png differ diff --git a/docs/english/tutorial/custom-steps-workflow-builder-existing/find-step.png b/docs/english/tutorial/custom-steps-workflow-builder-existing/find-step.png new file mode 100644 index 000000000..54e2741c6 Binary files /dev/null and b/docs/english/tutorial/custom-steps-workflow-builder-existing/find-step.png differ diff --git a/docs/english/tutorial/custom-steps-workflow-builder-existing/inputs.png b/docs/english/tutorial/custom-steps-workflow-builder-existing/inputs.png new file mode 100644 index 000000000..77434e44d Binary files /dev/null and b/docs/english/tutorial/custom-steps-workflow-builder-existing/inputs.png differ diff --git a/docs/english/tutorial/custom-steps-workflow-builder-existing/org-ready.png b/docs/english/tutorial/custom-steps-workflow-builder-existing/org-ready.png new file mode 100644 index 000000000..e3abd5c7f Binary files /dev/null and b/docs/english/tutorial/custom-steps-workflow-builder-existing/org-ready.png differ diff --git a/docs/english/tutorial/custom-steps-workflow-builder-existing/outputs.png b/docs/english/tutorial/custom-steps-workflow-builder-existing/outputs.png new file mode 100644 index 000000000..3b7d326c5 Binary files /dev/null and b/docs/english/tutorial/custom-steps-workflow-builder-existing/outputs.png differ diff --git a/docs/english/tutorial/custom-steps-workflow-builder-existing/step-inputs.png b/docs/english/tutorial/custom-steps-workflow-builder-existing/step-inputs.png new file mode 100644 index 000000000..bf8fc7871 Binary files /dev/null and b/docs/english/tutorial/custom-steps-workflow-builder-existing/step-inputs.png differ diff --git a/docs/english/tutorial/custom-steps-workflow-builder-new/app-token.png b/docs/english/tutorial/custom-steps-workflow-builder-new/app-token.png new file mode 100644 index 000000000..c500bb003 Binary files /dev/null and b/docs/english/tutorial/custom-steps-workflow-builder-new/app-token.png differ diff --git a/docs/english/tutorial/custom-steps-workflow-builder-new/bot-token.png b/docs/english/tutorial/custom-steps-workflow-builder-new/bot-token.png new file mode 100644 index 000000000..2d624117d Binary files /dev/null and b/docs/english/tutorial/custom-steps-workflow-builder-new/bot-token.png differ diff --git a/docs/english/tutorial/custom-steps-workflow-builder-new/custom-steps-workflow-builder-new.md b/docs/english/tutorial/custom-steps-workflow-builder-new/custom-steps-workflow-builder-new.md new file mode 100644 index 000000000..75be7aa32 --- /dev/null +++ b/docs/english/tutorial/custom-steps-workflow-builder-new/custom-steps-workflow-builder-new.md @@ -0,0 +1,358 @@ +# Custom Steps for Workflow Builder (new app) + +:::info[This feature requires a paid plan] +If you don't have a paid workspace for development, you can join the [Developer Program](https://api.slack.com/developer-program) and provision a sandbox with access to all Slack features for free. +::: + +Adding a workflow step to your app and implementing a corresponding function listener is how you define a custom Workflow Builder step. In this tutorial, you'll use [Bolt for Python](/tools/bolt-python/) to add your workflow step, then wire it up in [Workflow Builder](https://slack.com/help/articles/360035692513-Guide-to-Workflow-Builder). + +When finished, you'll be ready to build scalable and innovative workflow steps for anyone using Workflow Builder in your workspace. + +## What are we building? {#what-are-we-building} + +In this tutorial, you'll be wiring up a sample app with a sample step and corresponding function listener to be used as a workflow step in Workflow Builder. Here's how it works: + +* When someone starts the workflow, Slack will notify your app that your custom step was invoked as part of a workflow. +* Your app will send a message to the requestor, along with a button to complete the step. +* When the user clicks or taps the button, Slack will let your app know, and your app will respond by changing the message. + +:::info[Skip to the code] +If you'd rather skip the tutorial and just head straight to the code, create a new app and use our [Bolt Python custom step sample](https://github.com/slack-samples/bolt-python-custom-step-template) as a template. The sample custom step provided in the template will be a good place to start exploring! +::: + +## Prerequisites {#prereqs} + +Before we begin, let's make sure you're set up for success. Ensure you have a development workspace where you have permission to install apps. We recommend setting up your own space used for exploration and testing in a [developer sandbox](https://api.slack.com/developer-program). + +## Cloning the sample project {#clone} + +For this tutorial, We'll use `boltstep` as the app name. For your app, be sure to use a unique name that will be easy for you to find: then, use that name wherever you see `boltstep` in this tutorial. The app will be named "Bolt Custom Step", as that is defined in the `manifest.json` file of the sample app code. + +Let's start by opening a terminal and cloning the starter template repository: + +```sh +git clone https://github.com/slack-samples/bolt-python-custom-step-template boltstep +``` + +Once the terminal is finished cloning the template, change directories into your newly prepared app project: + +```sh +cd boltstep +``` + +If you're using VSCode (highly recommended), you can enter `code .` from your project's directory and VSCode will open your new project. + +You can also open a terminal window from inside VSCode like this: `Ctrl` + `~` + +Once in VSCode, open the terminal. Let's install our package dependencies: run the following command(s) in the terminal inside VSCode: + +```sh +python3 -m venv .venv +source .venv/bin/activate +pip install -r requirements.txt +``` + +We now have a Bolt app ready for development! Open the `manifest.json` file and copy its contents; you'll need this in the next step. + +## Creating your app from a manifest {#create-app} + +Open a browser and navigate to [your apps page](https://api.slack.com/apps). This is where we will create a new app with our previously copied manifest details. Click the **Create New App** button, then select **From an app manifest** when prompted to choose how you'd like to configure your app's settings. + +![Create app from manifest](manifest.png) + +Next, select a workspace where you have permissions to install apps, and click **Next**. Select the **JSON** tab and clear the existing contents. Paste the contents of the `manifest.json` file you previously copied. + +Click **Next** again. You will be shown a brief overview of the features your app includes. You'll see we are creating an app with a `chat:write` bot scope, an App Home and Bot User, as well as Socket Mode, Interactivity, an Event Subscription, and Org Deploy. We'll get into these details later. Click **Create**. + +### App settings {#app-settings} + +All of your app's settings can be configured within these screens. By creating an app from an existing manifest, you will notice many settings have already been configured. Navigate to **Org Level Apps** and notice that we've already opted in. This is a requirement for adding workflow steps to an app. + +Navigate to **Event Subscriptions** and expand **Subscribe to bot events** to see that we have subscribed to the `function_executed` event. This is also a requirement for adding workflow steps to our app, as it lets our app know when a step has been triggered, allowing our app to respond to it. + +Another configuration setting to note is **Socket Mode**. We have turned this on for our local development, but socket mode is not intended for use in a production environment. When you are satisfied with your app and ready to deploy it to a production environment, you should switch to using public HTTP request URLs. Read more about getting started with HTTP in [Bolt for Python here](/tools/bolt-python/getting-started). + +Clicking on **Workflow Steps** in the left nav will show you that one workflow step has been added! This reflects the `function` defined in our manifest: functions are workflow steps. We will get to this step's implementation later. + +![Workflow step](workflow-step.png) + +### Tokens {#tokens} + +In order to connect our app here with the logic of our sample code set up locally, we need to obtain two tokens, a bot token and an app token. + +* **Bot tokens** are associated with bot users, and are only granted once in a workspace where someone installs the app. The bot token your app uses will be the same no matter which user performed the installation. Bot tokens are the token type that most apps use. +* **App-level** tokens represent your app across organizations, including installations by all individual users on all workspaces in a given organization and are commonly used for creating websocket connections to your app. + +To generate an app token, navigate to **Basic Information** and scroll down to **App-Level Token**. + +![App token](app-token.png) + +Click **Generate Token and Scopes**, then **Add Scope** and choose `connections:write`. Choose a name for your token and click **Generate**. Copy that value, save it somewhere accessible, and click **Done** to close out of the modal. + +Next up is the bot token. We can only get this token by installing the app into the workspace. Navigate to **Install App** and click the button to install, choosing **Allow** at the next screen. + +![Install app](install.png) + +You will then have a bot token. Again, copy that value and save it somewhere accessible. + +![Bot token](bot-token.png) + +💡 Treat your tokens like passwords and keep them safe. Your app uses them to post and retrieve information from Slack workspaces. Minimally, do NOT commit them to version control. + +## Starting your local development server {#local} + +While building your app, you can see your changes appear in your workspace in real-time with `python app.py`. Soon we'll start our local development server and see what our sample code is all about! But first, we need to store those tokens we gathered as environment variables. + +Navigate back to VSCode. Rename the `.env.sample` file to `.env`. Open this file and update `SLACK_APP_TOKEN` and `SLACK_BOT_TOKEN` with the values you previously saved. It will look like this, with your actual token values where you see `` and ``: + +```sh +SLACK_APP_TOKEN= +SLACK_BOT_TOKEN= +``` + +Now save the file and try starting your app: + +```sh +python app.py +``` + +You'll know the local development server is up and running successfully when it emits a bunch of `[DEBUG]` statements to your terminal, the last one containing `connected:ready`. + +With your development server running, continue to the next step. + +:::info[If you need to stop running the local development server, press `` + `c` to end the process.] +::: + +## Wiring up the sample step in Workflow Builder {#wfb} + +The starter project you cloned contains a sample custom step lovingly titled “Sample step". Let’s see how a custom step defined in Bolt appears in Workflow Builder. + +In the Slack Client of your development workspace, open Workflow Builder by clicking on the workspace name, **Tools**, then **Workflow Builder**. Create a new workflow, then select **Build Workflow**: + +![Creating a new workflow](wfb-1.png) + +Select **Choose an event** under **Start the workflow...**, then **From a link in Slack** to configure this workflow to start when someone clicks its shortcut link: + +![Starting a new workflow from a shortcut link](wfb-2.png) + +Click the **Continue** button to confirm that this is workflow should start with a shortcut link: + +![Confirming a new shortcut workflow setup](wfb-3.png) + +Find the sample step provided in the template by either searching for the name of your app (e.g., `Bolt Custom Step`) or the name of your step (e.g. `Sample step`) in the Steps search bar. + +If you search by app name, any custom step that your app has defined will be listed. + +Add the “Sample step" in the search results to the workflow: + +![Adding the sample step to the workflow](wfb-4.png) + +As soon as you add the “Sample step" to the workflow, a modal will appear to configure the step's input—in this case, a user variable: + +![Configuring the sample step's inputs](wfb-5.png) + +Configure the user input to be “Person who used this workflow”, then click the **Save** button: + +![Saving the sample step after configuring the user input](wfb-6.png) + +Click the **Finish Up** button, then provide a name and description for your workflow. + +Finally, click the **Publish** button: + +![Publishing a workflow](wfb-7.png) + +Copy the shortcut link, then exit Workflow Builder and paste the link to a message in any channel you’re in: + +![Copying a workflow link](wfb-8.png) + +After you send a message containing the shortcut link, the link will unfurl and you’ll see a **Start Workflow** button. + +Click the **Start Workflow** button: + +![Starting your new workflow](wfb-9.png) + +You should see a new direct message from your app: + +![A new direct message from your app](wfb-10.png) + +The message from your app asks you to click the **Complete step** button: + +![A new direct message from your app](wfb-11.png) + +Once you click the button, the direct message to you will be updated to let you know that the step interaction was successfully completed: + +![Sample step finished successfully](wfb-12.png) + +Now that we’ve gotten a feel for how we will use the custom step, let’s learn more about how function listeners work. + +## Discovering listeners {#listeners} + +Now that we’ve seen how custom steps are used in Workflow Builder, let’s understand how the function listener code works to respond to an event when the step is triggered. + +We’ll first review the step definition in the `manifest.json`, then we’ll look at the two listener functions in our app code: one to let us know when the step starts, and one to let us know when someone clicks or taps one of the buttons we sent over. + +### Defining the custom step {#define-custom-step} + +Opening the `manifest.json` file included in the sample app shows a `functions` property that includes a definition for our `sample_step`: + +```json +// manifest.json +... + "functions": { + "sample_step": { + "title": "Sample step", + "description": "Runs sample step", + "input_parameters": { + "user_id": { + "type": "slack#/types/user_id", + "title": "User", + "description": "Message recipient", + "is_required": true, + "hint": "Select a user in the workspace", + "name": "user_id" + } + }, + "output_parameters": { + "user_id": { + "type": "slack#/types/user_id", + "title": "User", + "description": "User that completed the step", + "is_required": true, + "name": "user_id" + } + } + } + } +``` + +From the step definition, we can see an input parameter and an output parameter defined. + +### Inputs and outputs {#inputs-outputs} + +The custom step will take the following input: Message recipient (as a Slack User ID). + +The custom step will produce the following output: The user that completed the step. + +* When the step is invoked, a message will be sent to the user who invoked the workflow with a button to complete the step. +* When the button is clicked, a message is posted indicating the step's completion. + +### Implementing the function listener {#function-listener} + +The first thing we’ll do when adding a custom workflow step to our Bolt app is register a new **function listener**. In Bolt, a function listener allows developers to execute custom code in response to specific Slack events or actions by registering a method that handles predefined requests or commands. We register a function listener via the `function` method provided by our app instance. + +1. Open your project’s `app.py` file in your code editor. +2. Between the initialization code for the app instance and the `sample_step` registration, you'll see a listener defined for our custom step: + +```py +# app.py +... +@app.function("sample_step") +def handle_sample_step_event(inputs: dict, say: Say, fail: Fail, logger: logging.Logger): + user_id = inputs["user_id"] + + try: + say( + channel=user_id, # sending a DM to this user + text="Click the button to signal the step has completed", + blocks=[ + { + "type": "section", + "text": {"type": "mrkdwn", "text": "Click the button to signal the step has completed"}, + "accessory": { + "type": "button", + "text": {"type": "plain_text", "text": "Complete step"}, + "action_id": "sample_click", + }, + } + ], + ) + except Exception as e: + logger.exception(e) + fail(f"Failed to handle a step request (error: {e})") + + +``` + +#### Anatomy of a `.function()` listener {#function-listener-anatomy} + +The function decorator (`function()`) accepts an argument of type `str` and is the unique callback ID of the step. For our custom step, we’re using `sample_step`. Every custom step you implement in an app needs to have a unique callback ID. + +The callback function is where we define the logic that will run when Slack tells the app that a user in the Slack client started a workflow that contains the `sample_step` custom step. + +The callback function offers various utilities that can be used to take action when a step execution event is received. The ones we’ll be using here are: + +* `inputs` provides access to the workflow variables passed into the step when the workflow was started +* `fail` indicates when the step invoked for the current workflow step has an error +* `logger` provides a Python standard logger instance +* `say` calls the `chat.Postmessage` API method + +#### Understanding the function listener's callback logic {#function-listener-callback-logic} + +When our step is executed, we want a message to be sent to the invoking user. That message should include a button that prompts the user to complete the step. + +When Slack tells your Bolt app that the `sample_step` step was invoked, this step uses `chat.postMessage` to send a message to the `user_id` channel (which means this will be sent as a DM to the Slack user whose ID == `user_id`) with some text and blocks. The Block Kit element being sent as part of the message is a button, labeled 'Complete step' (which sends the `sample_click` action ID). + +Once the message is sent, your Bolt app will wait until the user has clicked the button. As soon as they click or tap the button, Slack will send back the action ID associated with the button to your Bolt app. + +In order for your Bolt app to listen for these actions, we’ll now define an action listener. + +### Implementing the action listener {#action-listener} + +The message we send to the user will include the button prompting them to complete the step. + +To listen for and respond to this button click, you'll see an `.action()` listener to `app.py`, right after the function listener definition: + +```py +# app.py +... +@app.action("sample_click") +def handle_sample_click( + ack: Ack, body: dict, context: BoltContext, client: WebClient, complete: Complete, fail: Fail, logger: logging.Logger +): + ack() + + try: + # Since the button no longer works, we should remove it + client.chat_update( + channel=context.channel_id, + ts=body["message"]["ts"], + text="Congrats! You clicked the button", + ) + + # Signal that the step completed successfully + complete({"user_id": context.actor_user_id}) + except Exception as e: + logger.exception(e) + fail(f"Failed to handle a step request (error: {e})") + +``` + +#### Anatomy of an `.action()` listener {#action-listener-anatomy} + +Similar to a function listener, the action listener registration method (`.action()`) takes two arguments: + +* The first argument is the unique callback ID of the action that your app will respond to. +* The second argument is an asynchronous callback function, where we define the logic that will run when Slack tells our app that the user has clicked or tapped the button. + +Just like the function listener’s callback function, the action listener’s callback function offers various utilities that can be used to take action when an action event is received. The ones we’ll be using here are: + +* `client`, which provides access to Slack API methods +* `action`, which provides the action’s event payload +* `complete`, which is a utility method indicating to Slack that the step behind the workflow step that was just invoked has completed successfully +* `fail`, which is a utility method for indicating that the step invoked for the current workflow step had an error + +#### Understanding the action listener's callback logic {#action-listener-callback-logic} + +Recall that we sent over a message with the button back in the function listener. + +When the button is pressed, we want to complete the step, update the message, and define `outputs` that can be used for subsequent steps in Workflow Builder. + +Slack will send an action event payload to your app when the button is clicked or tapped. In the action listener, we extract all the information we can use, and if all goes well, let Slack know the step was successful by invoking `complete`. We also handle cases where something goes wrong and produces an error. + +## Next steps {#next-steps} + +That's it — we hope you learned a lot! + +In this tutorial, we added custom steps via the manifest, but if you'd like to see how to add custom steps in the [app settings](https://api.slack.com/apps) to an existing app, follow along with the [Create a custom step for Workflow Builder: existing Bolt app](/tools/bolt-python/tutorial/custom-steps-workflow-builder-existing) tutorial. + +If you're interested in exploring how to create custom steps to use in Workflow Builder as steps with our Deno Slack SDK, too, that tutorial can be found [here](/tools/deno-slack-sdk/tutorials/workflow-builder-custom-step/). diff --git a/docs/english/tutorial/custom-steps-workflow-builder-new/install.png b/docs/english/tutorial/custom-steps-workflow-builder-new/install.png new file mode 100644 index 000000000..bbfc83c13 Binary files /dev/null and b/docs/english/tutorial/custom-steps-workflow-builder-new/install.png differ diff --git a/docs/english/tutorial/custom-steps-workflow-builder-new/manifest.png b/docs/english/tutorial/custom-steps-workflow-builder-new/manifest.png new file mode 100644 index 000000000..013c0f7be Binary files /dev/null and b/docs/english/tutorial/custom-steps-workflow-builder-new/manifest.png differ diff --git a/docs/english/tutorial/custom-steps-workflow-builder-new/wfb-1.png b/docs/english/tutorial/custom-steps-workflow-builder-new/wfb-1.png new file mode 100644 index 000000000..566a11224 Binary files /dev/null and b/docs/english/tutorial/custom-steps-workflow-builder-new/wfb-1.png differ diff --git a/docs/english/tutorial/custom-steps-workflow-builder-new/wfb-10.png b/docs/english/tutorial/custom-steps-workflow-builder-new/wfb-10.png new file mode 100644 index 000000000..859c4bf1a Binary files /dev/null and b/docs/english/tutorial/custom-steps-workflow-builder-new/wfb-10.png differ diff --git a/docs/english/tutorial/custom-steps-workflow-builder-new/wfb-11.png b/docs/english/tutorial/custom-steps-workflow-builder-new/wfb-11.png new file mode 100644 index 000000000..be2159a59 Binary files /dev/null and b/docs/english/tutorial/custom-steps-workflow-builder-new/wfb-11.png differ diff --git a/docs/english/tutorial/custom-steps-workflow-builder-new/wfb-12.png b/docs/english/tutorial/custom-steps-workflow-builder-new/wfb-12.png new file mode 100644 index 000000000..0e59ed5a5 Binary files /dev/null and b/docs/english/tutorial/custom-steps-workflow-builder-new/wfb-12.png differ diff --git a/docs/english/tutorial/custom-steps-workflow-builder-new/wfb-2.png b/docs/english/tutorial/custom-steps-workflow-builder-new/wfb-2.png new file mode 100644 index 000000000..8744bee39 Binary files /dev/null and b/docs/english/tutorial/custom-steps-workflow-builder-new/wfb-2.png differ diff --git a/docs/english/tutorial/custom-steps-workflow-builder-new/wfb-3.png b/docs/english/tutorial/custom-steps-workflow-builder-new/wfb-3.png new file mode 100644 index 000000000..17601ffda Binary files /dev/null and b/docs/english/tutorial/custom-steps-workflow-builder-new/wfb-3.png differ diff --git a/docs/english/tutorial/custom-steps-workflow-builder-new/wfb-4.png b/docs/english/tutorial/custom-steps-workflow-builder-new/wfb-4.png new file mode 100644 index 000000000..79f06ac7b Binary files /dev/null and b/docs/english/tutorial/custom-steps-workflow-builder-new/wfb-4.png differ diff --git a/docs/english/tutorial/custom-steps-workflow-builder-new/wfb-5.png b/docs/english/tutorial/custom-steps-workflow-builder-new/wfb-5.png new file mode 100644 index 000000000..3a304316c Binary files /dev/null and b/docs/english/tutorial/custom-steps-workflow-builder-new/wfb-5.png differ diff --git a/docs/english/tutorial/custom-steps-workflow-builder-new/wfb-6.png b/docs/english/tutorial/custom-steps-workflow-builder-new/wfb-6.png new file mode 100644 index 000000000..b5e85e95e Binary files /dev/null and b/docs/english/tutorial/custom-steps-workflow-builder-new/wfb-6.png differ diff --git a/docs/english/tutorial/custom-steps-workflow-builder-new/wfb-7.png b/docs/english/tutorial/custom-steps-workflow-builder-new/wfb-7.png new file mode 100644 index 000000000..a8992b84e Binary files /dev/null and b/docs/english/tutorial/custom-steps-workflow-builder-new/wfb-7.png differ diff --git a/docs/english/tutorial/custom-steps-workflow-builder-new/wfb-8.png b/docs/english/tutorial/custom-steps-workflow-builder-new/wfb-8.png new file mode 100644 index 000000000..1a3d636cb Binary files /dev/null and b/docs/english/tutorial/custom-steps-workflow-builder-new/wfb-8.png differ diff --git a/docs/english/tutorial/custom-steps-workflow-builder-new/wfb-9.png b/docs/english/tutorial/custom-steps-workflow-builder-new/wfb-9.png new file mode 100644 index 000000000..1347c463f Binary files /dev/null and b/docs/english/tutorial/custom-steps-workflow-builder-new/wfb-9.png differ diff --git a/docs/english/tutorial/custom-steps-workflow-builder-new/workflow-step.png b/docs/english/tutorial/custom-steps-workflow-builder-new/workflow-step.png new file mode 100644 index 000000000..3ae8c26fa Binary files /dev/null and b/docs/english/tutorial/custom-steps-workflow-builder-new/workflow-step.png differ diff --git a/docs/english/tutorial/custom-steps.md b/docs/english/tutorial/custom-steps.md new file mode 100644 index 000000000..50bd723ce --- /dev/null +++ b/docs/english/tutorial/custom-steps.md @@ -0,0 +1,272 @@ +--- +title: Custom Steps +--- + +:::info[This feature requires a paid plan] +If you don't have a paid workspace for development, you can join the [Developer Program](https://api.slack.com/developer-program) and provision a sandbox with access to all Slack features for free. +::: + + +With custom steps for Bolt apps, your app can create and process workflow steps that users later add in Workflow Builder. This guide goes through how to build a custom step for your app using the [app settings](https://api.slack.com/apps). + +If you're looking to build a custom step using the Deno Slack SDK, check out our guide on [creating a custom step for Workflow Builder with the Deno Slack SDK](/tools/deno-slack-sdk/tutorials/workflow-builder-custom-step/). + +You can also take a look at the template for the [Bolt for Python custom workflow step](https://github.com/slack-samples/bolt-python-custom-step-template) on GitHub. + +There are two components of a custom step: the step definition in the app manifest, and a listener to handle the `function_executed` event in your project code. + +## Opt in to org-ready apps {#org-ready-apps} + +Before we create the step definition, we first need to opt in to organization-ready apps. The app must opt-in to org-ready apps to be able to add the custom step to its manifest. This can be done in one of two ways: + +- Set the manifest `settings.org_deploy_enabled` property to `true`. +- Alternatively, navigate to your [apps](https://api.slack.com/apps), select your app, then under the **Features** section in the navigation, select **Org Level Apps** and then **Opt-In**. + +Whichever method you use, the following will be reflected in the app manifest as such: + +```json + "settings": { + "org_deploy_enabled": true, + ... + } +``` + +Next, the app must be installed at the organization level. While it is possible to install the app at a workspace level, doing so means that the custom steps will not appear in Workflow Builder. To remedy this, install the app at the organization level. + +If you are a developer who is not an admin of their organization, you will need to request an Org Admin to perform this installation at the organization level. To do this: + +- Navigate to your [apps](https://api.slack.com/apps) page and select the app you'd like to install. +- Under **Settings**, select **Collaborators**. +- Add an Org Admin as a collaborator. + +The Org Admin can then install your app directly at the org level from the [app settings](https://api.slack.com/apps) page. + +## Defining the custom step {#define-step} + +A workflow step's definition contains information about the step, including its `input_parameters`, `output_parameters`, as well as display information. + +Each step is defined in the `functions` object of the manifest. Each entry in the `functions` object is a key-value pair representing each step. The key is the step's `callback_id`, which is any string you wish to use to identify the step (max 100 characters), and the value contains the details listed in the table below for each separate custom step. We recommend using the step's name, like `sample_step` in the code example below for the step's `callback_id`. + +Field | Type | Description | Required? +---- | ----- | ------------|---------- +`title` | String | A string to identify the step. Max 255 characters. | Yes +`description` | String | A succinct summary of what your step does. | No +`input_parameters` | Object | An object which describes one or more [input parameters](#inputs-outputs) that will be available to your step. Each top-level property of this object defines the name of one input parameter available to your step.| No +`output_parameters` | Object | An object which describes one or more [output parameters](#inputs-outputs) that will be returned by your step. Each top-level property of this object defines the name of one output parameter your step makes available. | No + +Once you are in your [app settings](https://api.slack.com/apps), navigate to **Workflow Steps** in the left nav. Click **Add Step** and fill out your step details, including callback ID, name, description, input parameters, and output parameters. + +### Defining input and output parameters {#inputs-outputs} + +Step inputs and outputs (`input_parameters` and `output_parameters`) define what information goes into a step before it runs and what comes out of a step after it completes, respectively. + +Both inputs and outputs adhere to the same schema and consist of a unique identifier and an object that describes the input or output. + +Each input or output that belongs to `input_parameters` or `output_parameters` must have a unique key. + +Field | Type | Description +------|------|------------- +`type` | String | Defines the data type and can fall into one of two categories: primitives or Slack-specific. +`title` | String | The label that appears in Workflow Builder when a user sets up this step in their workflow. +`description` | String | The description that accompanies the input when a user sets up this step in their workflow. +`dynamic_options` | Object | For custom steps dynamic options in Workflow Builder, define this property and point to a custom step designed to return the set of dynamic elements once the step is added to a workflow within Workflow Builder. Dynamic options in Workflow Builder can be rendered in one of two ways: as a drop-down menu (single-select or multi-select), or as a set of fields. Refer to custom steps dynamic options for Workflow Builder using [Bolt for JavaScript](/tools/bolt-js/concepts/custom-steps-dynamic-options/) or [Bolt for Python](https://docs.slack.dev/tools/bolt-python/concepts/custom-steps-dynamic-options/) for more details. +`is_required` | Boolean | Indicates whether or not the input is required by the step in order to run. If it’s required and not provided, the user will not be able to save the configuration nor use the step in their workflow. This property is available only in v1 of the manifest. We recommend v2, using the `required` array as noted in the example above. +`hint` | String | Helper text that appears below the input when a user sets up this step in their workflow. + +In addition, the `dynamic_options` field has two required properties: + +Property | Type | Description +------|------|------------- +`function` | String | A reference to the custom step that should be used as a dynamic option. +`inputs` | Object | Maps the inputs from the custom step consuming the dynamic option to the inputs required by the step used as a dynamic option. + +For example: + +``` +"inputs": { + "category": { + "value": "{{input_parameters.category}}" + } +} +``` + +Once you've added your step details, save your changes, then navigate to **App Manifest**. Notice your new step configuration reflected in the `function` property! + +#### Sample manifest {#sample-manifest} + +Here is a sample app manifest laying out a step definition. This definition tells Slack that the step in our workspace with the callback ID of `sample_step` belongs to our app, and that when it runs, we want to receive information about its execution event. + +```json +"functions": { + "sample_step": { + "title": "Sample step", + "description": "Runs sample step", + "input_parameters": { + "properties": { + "user_id": { + "type": "slack#/types/user_id", + "title": "User", + "description": "Message recipient", + "hint": "Select a user in the workspace", + "name": "user_id" + } + }, + "required": [ + "user_id" + ] + }, + "output_parameters": { + "properties": { + "user_id": { + "type": "slack#/types/user_id", + "title": "User", + "description": "User that received the message", + "name": "user_id" + } + }, + "required": [ + "user_id" + ] + }, + } +} +``` + +### Adding steps for existing apps {#existing-apps} + +If you are adding custom steps to an existing app directly to the app manifest, you will also need to add the `function_runtime` property to the app manifest. Do this in the `settings` section as such: + +```json +"settings": { + ... + "function_runtime": "remote" +} +``` + +If you are adding custom steps in the **Workflow Steps** section of the [App Config](https://api.slack.com/apps) as shown above, then this will be added automatically. + +## Listening to function executions {#listener} + +When your custom step is executed in a workflow, your app will receive a `function_executed` event. The callback provided to the `function()` method will be run when this event is received. + +The callback is where you can access `inputs`, make third-party API calls, save information to a database, update the user’s Home tab, or set the output values that will be available to subsequent workflow steps by mapping values to the `outputs` object. + +Your app must call `complete()` to indicate that the step’s execution was successful, or `fail()` to signal that the step failed to complete. + +Notice in the example code here that the name of the step, `sample_step`, is the same as it is listed in the manifest above. This is required. + +```py +@app.function("sample_step") +def handle_sample_step_event(client: WebClient, inputs: dict, fail: Fail, complete: Complete, logger: logging.Logger): + user_id = inputs["user_id"] + try: + client.chat_postMessage( + channel=user_id, + text=f"Greetings <@{user_id}>!" + ) + complete({"user_id": user_id}) + except Exception as e: + logger.exception(e) + fail(f"Failed to complete the step: {e}") + +``` + +Here's another example. Note in this snippet, the name of the step, `create_issue`, must be listed the same as it is listed in the manifest file. + +```py +@app.function("create_issue") +def create_issue_callback(ack: Ack, inputs: dict, fail: Fail, complete: Complete, logger: logging.Logger): + ack() + JIRA_BASE_URL = os.getenv("JIRA_BASE_URL") + + headers = { + "Authorization": f'Bearer {os.getenv("JIRA_SERVICE_TOKEN")}', + "Accept": "application/json", + "Content-Type": "application/json", + } + + try: + project: str = inputs["project"] + issue_type: str = inputs["issuetype"] + + url = f"{JIRA_BASE_URL}/rest/api/latest/issue" + + payload = json.dumps( + { + "fields": { + "description": inputs["description"], + "issuetype": {"id" if issue_type.isdigit() else "name": issue_type}, + "project": {"id" if project.isdigit() else "key": project}, + "summary": inputs["summary"], + }, + } + ) + + response = requests.post(url, data=payload, headers=headers) + + response.raise_for_status() + json_data = json.loads(response.text) + complete(outputs={ + "issue_id": json_data["id"], + "issue_key": json_data["key"], + "issue_url": f'https://{JIRA_BASE_URL}/browse/{json_data["key"]}' + }) + except Exception as e: + logger.exception(e) + fail(f"Failed to handle a step request (error: {e})") + +``` + +### Anatomy of a function listener {#anatomy} + +The first argument (in our case above, `sample_step`) is the unique callback ID of the step. After receiving an event from Slack, this identifier is how your app knows which custom step handler to invoke. This `callback_id` also corresponds to the step definition provided in your manifest file. + +The second argument is the callback function, or the logic that will run when your app receives notice from Slack that `sample_step` was run by a user—in the Slack client—as part of a workflow. + +Field | Description +------|------------ +`client` | A `WebClient` instance used to make things happen in Slack. From sending messages to opening modals, `client` makes it all happen. For a full list of available methods, refer to the [Web API methods](/reference/methods). Read more about the `WebClient` for Bolt Python [here](https://docs.slack.dev/tools/bolt-python/concepts/web-api/). +`complete` | A utility method that invokes `functions.completeSuccess`. This method indicates to Slack that a step has completed successfully without issue. When called, `complete` accepts an optional `outputs` object that matches your step definition in [`output_parameters`](#inputs-outputs). +`fail` | A utility method that invokes `functions.completeError`. True to its name, this method signals to Slack that a step has failed to complete. The `fail` method requires an argument of `error` to be sent along with it, which is used to help users understand what went wrong. +`inputs` | An alias for the `input_parameters` that were provided to the step upon execution. + +## Responding to interactivity {#interactivity} + +Interactive elements provided to the user from within the `function()` method’s callback are associated with that unique `function_executed` event. This association allows for the completion of steps at a later time, like once the user has clicked a button. + +Incoming actions that are associated with a step have the same `inputs`, `complete`, and `fail` utilities as offered by the `function()` method. + +```py +# If associated with a step, step-specific utilities are made available +@app.action("sample_click") +def handle_sample_click(context: BoltContext, complete: Complete, fail: Fail, logger: logging.Logger): + try: + # Signal the step has completed once the button is clicked + complete({"user_id": context.actor_user_id}) + except Exception as e: + logger.exception(e) + fail(f"Failed to handle a step request (error: {e})") + +``` + +## Deploying a custom step {#deploy} + +When you're ready to deploy your steps for wider use, you'll need to decide *where* to deploy, since Bolt apps are not hosted on the Slack infrastructure. + +### Control step access {#access} + +You can choose who has access to your custom steps. To define this, refer to the [custom function access](/tools/deno-slack-sdk/guides/controlling-access-to-custom-functions) page. + +### Distribution {#distribution} + +Distribution works differently for Slack apps that contain custom steps when the app is within a standalone (non-Enterprise Grid) workspace versus within an Enterprise Grid organization. + +* **Within a standalone workspace**: Slack apps that contain custom steps can be installed on the same workspace and used within that workspace. We do not support distribution to other standalone workspaces (also known as public distribution). +* **Within an organization**: Slack apps that contain custom steps should be org-ready (enabled for private distribution) and installed on the organization level. They must also be granted access to at least one workspace in the organization for the steps to appear in Workflow Builder. + +Apps containing custom steps cannot be distributed publicly or submitted to the Slack Marketplace. We recommend sharing your code as a public repository in order to share custom steps in Bolt apps. + +## Related tutorials {#tutorials} + +* [Custom steps for Workflow Builder (new app)](/tools/bolt-python/tutorial/custom-steps-workflow-builder-new) +* [Custom steps for Workflow Builder (existing app)](/tools/bolt-python/tutorial/custom-steps-workflow-builder-existing/) \ No newline at end of file diff --git a/docs/english/tutorial/modals/base_link.gif b/docs/english/tutorial/modals/base_link.gif new file mode 100644 index 000000000..263799dda Binary files /dev/null and b/docs/english/tutorial/modals/base_link.gif differ diff --git a/docs/english/tutorial/modals/final_product.gif b/docs/english/tutorial/modals/final_product.gif new file mode 100644 index 000000000..0789badb6 Binary files /dev/null and b/docs/english/tutorial/modals/final_product.gif differ diff --git a/docs/english/tutorial/modals/heart_icon.gif b/docs/english/tutorial/modals/heart_icon.gif new file mode 100644 index 000000000..1dc0860df Binary files /dev/null and b/docs/english/tutorial/modals/heart_icon.gif differ diff --git a/docs/english/tutorial/modals/interactivity_url.png b/docs/english/tutorial/modals/interactivity_url.png new file mode 100644 index 000000000..af877a8e2 Binary files /dev/null and b/docs/english/tutorial/modals/interactivity_url.png differ diff --git a/docs/english/tutorial/modals/modals.md b/docs/english/tutorial/modals/modals.md new file mode 100644 index 000000000..d25470b97 --- /dev/null +++ b/docs/english/tutorial/modals/modals.md @@ -0,0 +1,134 @@ +# Modals + +If you're learning about Slack apps, modals, or slash commands for the first time, you've come to the right place! In this tutorial, we'll take a look at setting up your very own server using GitHub Codespaces, then using that server to run your Slack app built with the [**Bolt for Python framework**](https://github.com/SlackAPI/bolt-python). + +:::info[GitHub Codespaces] +GitHub Codespaces is an online IDE that allows you to work on code and host your own server at the same time. While Codespaces is good for testing and development purposes, it should not be used in production. + +::: + +At the end of this tutorial, your final app will look like this: + +![announce](/img/bolt-python/announce.gif) + +And will make use of these Slack concepts: +* [**Block Kit**](/block-kit/) is a UI framework for Slack apps that allows you to create beautiful, interactive messages within Slack. If you've ever seen a message in Slack with buttons or a select menu, that's Block Kit. +* [**Modals**](/surfaces/modals) are a pop-up window that displays right in Slack. They grab the attention of the user, and are normally used to prompt users to provide some kind of information or input in a form. +* [**Slash Commands**](/interactivity/implementing-slash-commands) allow you to invoke your app within Slack by just typing into the message composer box. e.g. `/remind`, `/topic`. + +If you're familiar with using Heroku you can also deploy directly to Heroku with the following button. + +[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://www.heroku.com/deploy?template=https://github.com/wongjas/modal-example) + +--- + +## Setting up your app within App Settings {#setting-up-app-settings} + +You'll need to create an app and configure it properly within App Settings before using it. + +1. [Create a new app](https://api.slack.com/apps/new), click `From a Manifest`, and choose the workspace that you want to develop on. Then copy the following JSON object; it describes the metadata about your app, like its name, its bot display name and permissions it will request. + +```json +{ + "display_information": { + "name": "Intro to Modals" + }, + "features": { + "bot_user": { + "display_name": "Intro to Modals", + "always_online": false + }, + "slash_commands": [ + { + "command": "/announce", + "description": "Makes an announcement", + "should_escape": false + } + ] + }, + "oauth_config": { + "scopes": { + "bot": [ + "chat:write", + "commands" + ] + } + }, + "settings": { + "interactivity": { + "is_enabled": true + }, + "org_deploy_enabled": false, + "socket_mode_enabled": true, + "token_rotation_enabled": false + } +} +``` + +2. Once your app has been created, scroll down to `App-Level Tokens` and create a token that requests for the [`connections:write`](/reference/scopes/connections.write) scope, which allows you to use [Socket Mode](/apis/events-api/using-socket-mode), a secure way to develop on Slack through the use of WebSockets. Copy the value of your app token and keep it for safe-keeping. + +3. Install your app by heading to `Install App` in the left sidebar. Hit `Allow`, which means you're agreeing to install your app with the permissions that it is requesting. Be sure to copy the token that you receive, and keep it somewhere secret and safe. + +## Starting your Codespaces server {#starting-server} + +1. Log into GitHub and head to this [repository](https://github.com/wongjas/modal-example). + +2. Click the green `Code` button and hit the `Codespaces` tab and then `Create codespace on main`. This will bring up a code editor within your browser so you can start coding. + +## Understanding the project files {#understanding-files} + +Within the project you'll find a `manifest.json` file. This is a a configuration file used by Slack apps. With a manifest, you can create an app with a pre-defined configuration, or adjust the configuration of an existing app. + +The `simple_modal_example.py` Python script contains the code that powers your app. If you're going to tinker with the app itself, take a look at the comments found within the `simple_modal_example.py` file! + +The `requirements.txt` file contains the Python package dependencies needed to run this app. + +:::info[This repo contains optional Heroku-specific configurations] + +The `app.json` file defines your Heroku app configuration including environment variables and deployment settings, to allow your app to deploy with one click. `Procfile` is a Heroku-specific file that tells Heroku what command to run when starting your app — in this case a Python script would run as a `worker` process. If you aren't deploying to Heroku, you can ignore both these files. + +::: + +## Adding tokens {#adding-tokens} + +1. Open a terminal up within the browser's editor. + +2. Grab the app and bot tokens that you kept safe. We're going to set them as environment variables. + +```bash +export SLACK_APP_TOKEN= +export SLACK_BOT_TOKEN= +``` + +## Running the app {#running-app} + +1. Activate a virtual environment for your Python packages to be installed. + +```bash +# Setup your python virtual environment +python3 -m venv .venv +source .venv/bin/activate +``` + +2. Install the dependencies from the `requirements.txt` file. + + +```bash +# Install the dependencies +pip install -r requirements.txt +``` + +3. Start your app using the `python3 simple_modal_example.py` command. + +```bash +# Start your local server +python3 simple_modal_example.py +``` + +4. Now that your app is running, you should be able to see it within Slack. Test this by heading to Slack and typing `/announce`. + +All done! 🎉 You've created your first slash command using Block Kit and modals! The world is your oyster; play around with [Block Kit Builder](https://app.slack.com/block-kit-builder) and create more complex modals and place them in your code to see what happens! + +## Next steps {#next-steps} + +If you want to learn more about Bolt for Python, refer to the [Getting Started guide](https://docs.slack.dev/tools/bolt-python/getting-started). \ No newline at end of file diff --git a/docs/english/tutorial/modals/slash_command.png b/docs/english/tutorial/modals/slash_command.png new file mode 100644 index 000000000..2473bda3b Binary files /dev/null and b/docs/english/tutorial/modals/slash_command.png differ diff --git a/docs/english/tutorial/order-confirmation/order-confirmation.md b/docs/english/tutorial/order-confirmation/order-confirmation.md new file mode 100644 index 000000000..695d6965a --- /dev/null +++ b/docs/english/tutorial/order-confirmation/order-confirmation.md @@ -0,0 +1,553 @@ +--- +title: Create a Salesforce order confirmation app +--- + +In this tutorial, you'll use the [Bolt for Python](/tools/bolt-python/) framework and [Block Kit Builder](https://app.slack.com/block-kit-builder) to create an order confirmation app that links to a system of record, like Salesforce. + +The Slack app will: +* allow users to enter order numbers from within Slack, along with some additional order information, +* post that information to a Slack channel, and +* send the information to the system of record. + +End users will be able to enter information across devices, as many will likely be using a mobile device. + +Along the way, you'll learn how to use the Bolt for Python starter app template as a jumping off point for your own custom apps. Let's begin! + +:::warning[Consider the following] + +This tutorial was created for educational purposes within a Slack workshop. As a result, it has not been tested quite as rigorously as our sample apps. Proceed carefully if you'd like to use a similar app in production. + +::: + +## Getting started + +### Installing the Slack CLI + +If you don't already have the Slack CLI, install it from your terminal: navigate to the installation guide ([for Mac and Linux](/tools/slack-cli/guides/installing-the-slack-cli-for-mac-and-linux) or [for Windows](/tools/slack-cli/guides/installing-the-slack-cli-for-windows)) and follow the steps. + +### Cloning the starter app + +Once installed, use the command `slack create` to get started with the Bolt for Python [starter template](https://github.com/slack-samples/bolt-python-starter-template). Alternatively, you can clone the template using Git. + +You can remove the portions from the template that are not used within this tutorial to make things a bit cleaner for yourself. To do this, open your project in VS Code (you can do this from the terminal with the `code .` command) and delete the `commands`, `events`, and `shortcuts` folders from the `/listeners` folder. You can also do the same to the corresponding folders within the `/listeners/tests` folder as well. Finally, remove the imports of these files from the `/listeners/__init__.py` file. + +## Creating your app + +We’ll use the contents of the `manifest.json` file below. This file describes the metadata associated with your app, like its name and permissions that it requests. + +These values are used to create an app in one of two ways: + +- **With the Slack CLI**: Save the contents of the file to your project's `manifest.json` file then skip ahead to [starting your app](#starting-your-app). +- **With app settings**: Copy the contents of the file and [create a new app](https://api.slack.com/apps/new). Next, choose **From a manifest** and follow the prompts, pasting the manifest file contents you copied. + +```json +{ + "_metadata": { + "major_version": 1, + "minor_version": 1 + }, + "display_information": { + "name": "Delivery Tracker App" + }, + "features": { + "bot_user": { + "display_name": "Delivery Tracker App", + "always_online": false + } + }, + "oauth_config": { + "scopes": { + "bot": [ + "channels:history", + "chat:write" + ] + } + }, + "settings": { + "event_subscriptions": { + "bot_events": [ + "message.channels" + ] + }, + "interactivity": { + "is_enabled": true + }, + "org_deploy_enabled": false, + "socket_mode_enabled": true, + "token_rotation_enabled": false + } +} +``` + +### Tokens + +Once your app has been created, scroll down to **App-Level Tokens** on the **Basic Information** page and create a token that requests the [`connections:write`](/reference/scopes/connections.write) scope. This token will allow you to use [Socket Mode](/apis/events-api/using-socket-mode), which is a secure way to develop on Slack through the use of WebSockets. Save the value of your app token and store it in a safe place (we’ll use it in the next step). + +### Install app + +Still in the app settings, navigate to the **Install App** page in the left sidebar. Install your app. When you press **Allow**, this means you’re agreeing to install your app with the permissions that it’s requesting. Copy the bot token that you receive as well and store this in a safe place as well for subsequent steps. + +## Saving credentials + +Within a terminal of your choice, set the two tokens from the previous step as environment variables using the commands below. Make sure not to mix these two up, `SLACK_APP_TOKEN` will start with “xapp-“ and `SLACK_BOT_TOKEN` will start with “xoxb-“. + +For macOS: + +```bash +export SLACK_APP_TOKEN= +export SLACK_BOT_TOKEN= +``` + +For Windows Command Prompt: + +```cmd +set SLACK_APP_TOKEN= +set SLACK_BOT_TOKEN= +``` + +For Windows PowerShell: + +```powershell +$env:SLACK_APP_TOKEN="YOUR-APP-TOKEN-HERE" +$env:SLACK_BOT_TOKEN="YOUR-BOT-TOKEN-HERE" +``` + +## Starting your app {#starting-your-app} + +Run the following commands to activate a virtual environment for your Python packages to be installed, install the dependencies, and start your app. + +```bash +# Setup your python virtual environment +python -m venv .venv +source .venv/bin/activate + +# Install the dependencies +pip install -r requirements.txt + +# Start your local server +slack run +``` + +If you're not using the Slack CLI, a different `python` command can be used to start your app instead: + +```sh +python app.py +``` + +Now that your app is running, you should be able to see it within Slack. In Slack, create a channel that you can test in and try inviting your bot to it using the `/invite @Your-app-name-here` command. Check that your app works by saying “hi” in the channel where your app is, and you should receive a message back from it. If you don’t, ensure you completed all the steps above. + +## Coding the app + +We'll make four changes to the app: + +* Update the “hi” message to something more interesting and interactive +* Handle when the wrong delivery ID button is pressed +* Handle when the correct delivery IDs are sent and bring up a modal for more information +* Send the information to all of the places needed when the form is submitted (including third-party locations) + +For all of these steps, we will use [Block Kit Builder](https://app.slack.com/block-kit-builder), a tool that helps you create messages, modals and other surfaces within Slack. Open [Block Kit Builder](https://app.slack.com/block-kit-builder), take a look, and play around! We’ll create some views next. + +### Updating the "hi" message + +The first thing we want to do is change the “hi, how are you?” message from our app into something more useful. Here’s a `blocks` object built with Block Kit Builder: + +```json + + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "Confirm *{delivery_id}* is correct?" + } + }, + { + "type": "actions", + "elements": [ + { + "type": "button", + "text": { + "type": "plain_text", + "text": "Correct", + "emoji": true + }, + "style": "primary", + "action_id": "approve_delivery" + }, + { + "type": "button", + "text": { + "type": "plain_text", + "text": "Not correct", + "emoji": true + }, + "style": "danger", + "action_id": "deny_delivery" + } + ] + } + ] + +``` + +Take the function below and place your blocks within the blocks dictionary `[]`. + +```python +def delivery_message_callback(context: BoltContext, say: Say, logger: Logger): + try: + delivery_id = context["matches"][0] + say( + blocks=[] # insert your blocks here + ) + except Exception as e: + logger.error(e) +``` + +Update the payload: +* Remove the initial blocks key and convert any boolean true values to `True` to fit with Python conventions. +* If you see variables within `{}` brackets, this is part of an f-string, which allows you to insert variables within strings in a clean manner. Place the `f` character before these strings like this: + +```python +{ + "type": "section", + "text": { + "type": "mrkdwn", + "text": f"Confirm *{delivery_id}* is correct?", # place the "f" character here at the beginning of the string + }, +}, +``` + +Place all of this in the `sample_message.py` file. + +Next, you’ll need to register this listener to respond when a message is sent in the channel with your app. Head to `messages/__init__.py` and overwrite the function there with the one below, which registers the function. Don’t forget to add the import to the callback function as well! + +```python +from .sample_message import delivery_message_callback # import the function to this file + +def register(app: App): + # This regex will capture any number letters followed by dash + # and then any number of digits, our "confirmation number" e.g. ASDF-1234 + app.message(re.compile(r"[A-Za-z]+-\d+"))(delivery_message_callback) ## add this line! +``` + +Now, restart your server to bring in the new code and test that your function works by sending an order confirmation ID, like `HWOA-1524`, in your testing channel. Your app should respond with the message you created within Block Kit Builder. + +### Handling an incorrect delivery ID + +Notice that if you try to click on either of the buttons within your message, nothing will happen. This is because we have yet to create a function to handle the button click. Let’s start with the `Not correct` button first. + +1. Head to Block Kit Builder once again. We want to build a message that lets the user know that the wrong order ID has been submitted. Here's a [section](/reference/block-kit/blocks/section-block) block to get you started: + +```json + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "Delivery *{delivery_id}* was incorrect ❌" + } + } + ] +``` + +View this block in Block Kit Builder [here](https://app.slack.com/block-kit-builder/#%7B%22blocks%22:%5B%7B%22type%22:%22section%22,%22text%22:%7B%22type%22:%22mrkdwn%22,%22text%22:%22Delivery%20*%7Bdelivery_id%7D*%20was%20incorrect%20%E2%9D%8C%22%7D%7D%5D%7D). + +2. Once you have something that you like, add it to the function below and place the function within the `actions/sample_action.py` file. Remember to make any strings with variables into f-strings! + +```python +def deny_delivery_callback(ack, body, client, logger: Logger): + try: + ack() + delivery_id = body["message"]["text"].split("*")[1] + + # Calls the chat.update function to replace the message, + # preventing it from being pressed more than once. + client.chat_update( + channel=body["container"]["channel_id"], + ts=body["container"]["message_ts"], + blocks=[], # Add your blocks here! + ) + + logger.info(f"Delivery denied by user {body['user']['id']}") + except Exception as e: + logger.error(e) +``` + +This function will call the [`chat.update`](/reference/methods/chat.update) Web API method, which will update the original message with buttons, to the one that we created previously. This will also prevent the message from being pressed more than once. + +3. Make the connection to this function again within the `actions/__init__.py` folder with the following code: + +```python +from slack_bolt import App +from .sample_action import sample_action_callback # This can be deleted +from .sample_action import deny_delivery_callback + +def register(app: App): + app.action("sample_action_id")(sample_action_callback) # This can be deleted + app.action("deny_delivery")(deny_delivery_callback) # Add this line +``` + +Test out your app by sending in a confirmation number into your channel and clicking the `Not correct` button. If the message is updated, then you’re good to go onto the next step. + +### Handling a correct delivery ID + +The next step is to handle the `Confirm` button. In this case, we’re going to pull up a modal instead of just a message. + +1. Using the following modal as a base; create a modal that captures the kind of information that you need. + +```json +{ + "title": { + "type": "plain_text", + "text": "Approve Delivery" + }, + "submit": { + "type": "plain_text", + "text": "Approve" + }, + "type": "modal", + "callback_id": "approve_delivery_view", + "private_metadata": "{delivery_id}", + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "Approving delivery *{delivery_id}*" + } + }, + { + "type": "input", + "block_id": "notes", + "label": { + "type": "plain_text", + "text": "Additional delivery notes" + }, + "element": { + "type": "plain_text_input", + "action_id": "notes_input", + "multiline": true, + "placeholder": { + "type": "plain_text", + "text": "Add notes..." + } + }, + "optional": true + }, + { + "type": "input", + "block_id": "location", + "label": { + "type": "plain_text", + "text": "Delivery Location" + }, + "element": { + "type": "plain_text_input", + "action_id": "location_input", + "placeholder": { + "type": "plain_text", + "text": "Enter the location details..." + } + }, + "optional": true + }, + { + "type": "input", + "block_id": "channel", + "label": { + "type": "plain_text", + "text": "Notification Channel" + }, + "element": { + "type": "channels_select", + "action_id": "channel_select", + "placeholder": { + "type": "plain_text", + "text": "Select channel for notifications" + } + }, + "optional": false + } + ] +} +``` + +View this modal in Block Kit Builder [here](https://app.slack.com/block-kit-builder/#%7B%22type%22:%22modal%22,%22callback_id%22:%22approve_delivery_view%22,%22title%22:%7B%22type%22:%22plain_text%22,%22text%22:%22Approve%20Delivery%22%7D,%22private_metadata%22:%22%7Bdelivery_id%7D%22,%22blocks%22:%5B%7B%22type%22:%22section%22,%22text%22:%7B%22type%22:%22mrkdwn%22,%22text%22:%22Approving%20delivery%20*%7Bdelivery_id%7D*%22%7D%7D,%7B%22type%22:%22input%22,%22block_id%22:%22notes%22,%22label%22:%7B%22type%22:%22plain_text%22,%22text%22:%22Additional%20delivery%20notes%22%7D,%22element%22:%7B%22type%22:%22plain_text_input%22,%22action_id%22:%22notes_input%22,%22multiline%22:true,%22placeholder%22:%7B%22type%22:%22plain_text%22,%22text%22:%22Add%20notes...%22%7D%7D,%22optional%22:true%7D,%7B%22type%22:%22input%22,%22block_id%22:%22location%22,%22label%22:%7B%22type%22:%22plain_text%22,%22text%22:%22Delivery%20Location%22%7D,%22element%22:%7B%22type%22:%22plain_text_input%22,%22action_id%22:%22location_input%22,%22placeholder%22:%7B%22type%22:%22plain_text%22,%22text%22:%22Enter%20the%20location%20details...%22%7D%7D,%22optional%22:true%7D,%7B%22type%22:%22input%22,%22block_id%22:%22channel%22,%22label%22:%7B%22type%22:%22plain_text%22,%22text%22:%22Notification%20Channel%22%7D,%22element%22:%7B%22type%22:%22channels_select%22,%22action_id%22:%22channel_select%22,%22placeholder%22:%7B%22type%22:%22plain_text%22,%22text%22:%22Select%20channel%20for%20notifications%22%7D%7D,%22optional%22:false%7D%5D,%22submit%22:%7B%22type%22:%22plain_text%22,%22text%22:%22Approve%22%7D%7D). + +2. Within the `actions/sample_action.py` file, add the following function, replacing the view with the one you created above. Again, any strings with variables will be updated to f-strings and also any booleans will need to be capitalized. + +```python +def approve_delivery_callback(ack, body, client, logger: Logger): + try: + ack() + + delivery_id = body["message"]["text"].split("*")[1] + # Updates the original message so you can't press it twice + client.chat_update( + channel=body["container"]["channel_id"], + ts=body["container"]["message_ts"], + blocks=[ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": f"Processed delivery *{delivery_id}*...", + }, + } + ], + ) + + # Open a modal to gather information from the user + client.views_open( + trigger_id=body["trigger_id"], + view={} # Add your view here + ) + + logger.info(f"Approval modal opened by user {body['user']['id']}") + except Exception as e: + logger.error(e) +``` + +Similar to the `deny` button, we need to hook up all the connections. Your `actions/__init__.py` should look something like this: + +```python +from slack_bolt import App +from .sample_action import deny_delivery_callback +from .sample_action import approve_delivery_callback + + +def register(app: App): + app.action("approve_delivery")(approve_delivery_callback) + app.action("deny_delivery")(deny_delivery_callback) +``` + +Test your app by typing in a confirmation number in channel, click the confirm button and see if the modal comes up and you are able to capture information from the user. + +### Submitting the form + +Lastly, we’ll handle the submission of the form, which will trigger two things. We want to send the information into the specified channel, which will let the user know that the form was successful, as well as send the information into our system of record, Salesforce. + +1. Here’s a simple example of a message that you can use to present the information in channel. + +```json + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "✅ Delivery *{delivery_id}* approved:" + } + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*Delivery Notes:*\n{notes or 'None'}" + } + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*Delivery Location:*\n{loc or 'None'}" + } + } + ] +``` + +View this in Block Kit Builder [here](https://app.slack.com/block-kit-builder/?1#%7B%22blocks%22:%5B%7B%22type%22:%22section%22,%22text%22:%7B%22type%22:%22mrkdwn%22,%22text%22:%22%E2%9C%85%20Delivery%20*%7Bdelivery_id%7D*%20approved:%22%7D%7D,%7B%22type%22:%22section%22,%22text%22:%7B%22type%22:%22mrkdwn%22,%22text%22:%22*Delivery%20Notes:*%5Cn%7Bnotes%20or%20'None'%7D%22%7D%7D,%7B%22type%22:%22section%22,%22text%22:%7B%22type%22:%22mrkdwn%22,%22text%22:%22*Delivery%20Location:*%5Cn%7Bloc%20or%20'None'%7D%22%7D%7D%5D%7D). Modify it however you like and then place it within the code below in the `/views/sample_views.py` file. + +```python +def handle_approve_delivery_view(ack, client, view, logger: Logger): + try: + ack() + + delivery_id = view["private_metadata"] + values = view["state"]["values"] + notes = values["notes"]["notes_input"]["value"] + loc = values["location"]["location_input"]["value"] + channel = values["channel"]["channel_select"]["selected_channel"] + + client.chat_postMessage( + channel=channel, + blocks=[], ## Add your message here + ) + + except Exception as e: + logger.error(f"Error in approve_delivery_view: {e}") +``` + +2. Making the connections in the `/views/__init__.py `file, we can test that this works by sending a message once again in our test channel. + +```python +from slack_bolt import App +from .sample_view import handle_approve_delivery_view + +def register(app: App): + app.view("sample_view_id")(sample_view_callback) # This can be deleted + app.view("approve_delivery_view")(handle_approve_delivery_view) ## Add this line +``` + +3. Let’s also send the information to Salesforce. There are [several ways](https://github.com/simple-salesforce/simple-salesforce?tab=readme-ov-file#examples) for you to access Salesforce through its API, but in this example, we’ve utilized `username`, `password` and `token` parameters. If you need help with getting your API token for Salesforce, take a look at [this article](https://help.salesforce.com/s/articleView?id=xcloud.user_security_token.htm&type=5). You’ll need to add these values as environment variables like we did earlier with our Slack tokens. You can use the following commands: + +```bash +export SF_USERNAME= +export SF_PASSWORD= +export SF_TOKEN= +``` + +4. We’re going to use assume that order information is stored in the Order object and that the confirmation IDs map to the 8-digit Order numbers within Salesforce. Given that assumption, we need to make a query to find the correct object, add the inputted information, and we’re done. Place this functionality before the last excerpt within the `/views/sample_views.py` file. + +```python +# Extract just the numeric portion from delivery_id + delivery_number = "".join(filter(str.isdigit, delivery_id)) + + # Update Salesforce order object + try: + sf = Salesforce( + username=os.environ.get("SF_USERNAME"), + password=os.environ.get("SF_PASSWORD"), + security_token=os.environ.get("SF_TOKEN"), + ) + + # Assuming delivery_id maps to Salesforce Order number + order = sf.query(f"SELECT Id FROM Order WHERE OrderNumber = '{delivery_number}'") # noqa: E501 + if order["records"]: + order_id = order["records"][0]["Id"] + sf.Order.update( + order_id, + { + "Status": "Delivered", + "Description": notes, + "Shipping_Location__c": loc, + }, + ) + logger.info(f"Updated order {delivery_id}") + else: + logger.warning(f"No order found for {delivery_id}") + + except Exception as sf_error: + logger.error(f"Update failed for order {delivery_id}: {sf_error}") + # Continue execution even if Salesforce update fails +``` + +You’ll also need to add the two imports that are found within this code to the top of the file. + +```python +import os +from simple_salesforce import Salesforce +``` + +With these imports, add `simple_salesforce` to your `requirements.txt` file, then install that package with the following command once again. + +```bash +pip install -r requirements.txt +``` + +![Image of delivery tracker app](/img/bolt-python/delivery-tracker-main.png) + +## Testing your app + +Test your app one last time, and you’re done! + +Congratulations! You’ve built an app using [Bolt for Python](/tools/bolt-python/) that allows you to send information into Slack, as well as into a third-party service. While there are more features you can add to make this a more robust app, we hope that this serves as a good introduction into connecting services like Salesforce using Slack as a conduit. \ No newline at end of file diff --git a/docs/footerConfig.js b/docs/footerConfig.js deleted file mode 100644 index e3e10c571..000000000 --- a/docs/footerConfig.js +++ /dev/null @@ -1,19 +0,0 @@ -const footer = { - links: [ - { - items: [ - { - html: ` - - ©2024 Slack Technologies, LLC, a Salesforce company. All rights reserved. Various trademarks held by their respective owners. - `, - }, - ], - }, - ], -}; - -module.exports = footer; diff --git a/docs/i18n/ja-jp/README.md b/docs/i18n/ja-jp/README.md deleted file mode 100644 index e23cb969b..000000000 --- a/docs/i18n/ja-jp/README.md +++ /dev/null @@ -1,121 +0,0 @@ -# Bolt for Python Japanese documentation - -This README describes how the Japanese documentation is created. Please read the [/docs README](./docs/README) for information on _all_ the documentation. - -[Docusaurus](https://docusaurus.io) supports using different languages. Each language is a different version of the same site. The English site is the default. The English page will be viewable if the page is not translated into Japanese. - -There will be English pages on the Japanese site for any non-translated pages. Japanese readers will not miss any content, but they may be confused seeing English and Japanese mixed together. Please give us your thoughts on this setup. - -Because of this, the sidebar does not need to be updated for the Japanese documentation. It's always the same as the English documentation! - -## Testing the Japanese site. - -Please read the [/docs README](./docs/README.md) for instructions. Be sure to run the site in Japanese: - -``` -npm run start -- --locale ja-jp -``` - ---- - -## Japanese documentation files - -``` -docs/ -├── content/ -│ ├── getting-started.md -│ └── concepts -│ └── sending-message.md -├── i18n/ja-jp -│ ├── code.json -│ ├── docusaurus-theme-classic/ -│ │ ├── footer.json -│ │ └── navbar.json -│ └── docusaurus-plugin-content-docs/ -│ └── current/ -│ ├── getting-started.md -│ └── concepts -│ └── sending-message.md -``` - -The Japanese documentation is in `i18n/ja-jp/`. The folder contains `docusaurus-plugin-content-docs`, `docusaurus-theme-classic`, and `code.json`. - -### `docusaurus-plugin-content-docs` - -``` -docs/ -├── content/ (English pages) -│ ├── example-page.md -│ ├── getting-started.md -│ └── concepts -│ └── sending-message.md -├── i18n/ja-jp -│ └── docusaurus-plugin-content-docs/ -│ └── current/ (Japanese pages) -│ ├── getting-started.md -│ └── concepts -│ └── sending-message.md -``` - -If the file is not in `i18n/ja-jp/docusaurus-plugin-content-docs/current/`, then the English file will be used. In the example above, `example-page.md` is not in `i18n/ja-jp/docusaurus-plugin-content-docs/current/`. Therefore, the English version of `example-page.md` will appear on the Japanese site. - -The Japanese page file formats in `i18n/ja-jp/docusaurus-plugin-content-docs/current/` must be the same as the English page files in `docs/content/`. Please keep the file names in English (example: `sending-message.md`). - -Please provide a title in Japanese. It will show up in the sidebar. There are two options: - -``` ---- -title: こんにちは ---- - -# こんにちは - -``` - -[Read the Docusaurus documentation for info on writing pages in markdown](https://docusaurus.io/docs/markdown-features). - -### `docusaurus-theme-classic` - -``` -└── i18n/ja-jp - └── docusaurus-theme-classic/ - ├── footer.json - └── navbar.json -``` - -`docusaurus-theme-classic` You can translate site components (footer and navbar) for the Japanese site. Each JSON object has a `messages` and `description` value: - * `message` - The Japanese translation. It will be in English if not translated. - * `description` - What and where the message is. This stays in English. - -For example: - -``` -{ - "item.label.Hello": { - "message": "こんにちは", - "description": "The title of the page" - } -} -``` - -The JSON files are created with the `npm run write-translations -- --locale ja-jp` command. [Please read the Docusaurus documentation](https://docusaurus.io/docs/i18n/tutorial#translate-your-react-code) for more info. - -### `code.json` - -``` -└── i18n/ja-jp - └── code.json -``` - -The `code.json` file is similar to `docusaurus-theme-classic` JSON objects. `code.json` has translations provided by Docusaurus for site elements. - -For example: - -``` - "theme.CodeBlock.copy": { - "message": "コピー", - "description": "The copy button label on code blocks" - }, -``` - -Be careful changing `code.json`. If you change something in this repo, it will likely need to be changed in the other tools.slack.dev repos too, like the Bolt-Python repo. We want these translations to match for all tools.slack.dev sites. \ No newline at end of file diff --git a/docs/i18n/ja-jp/code.json b/docs/i18n/ja-jp/code.json deleted file mode 100644 index eb1a022fa..000000000 --- a/docs/i18n/ja-jp/code.json +++ /dev/null @@ -1,289 +0,0 @@ -{ - "theme.NotFound.title": { - "message": "ページが見つかりません", - "description": "The title of the 404 page" - }, - "theme.NotFound.p1": { - "message": "お探しのページが見つかりませんでした", - "description": "The first paragraph of the 404 page" - }, - "theme.NotFound.p2": { - "message": "このページにリンクしているサイトの所有者にリンクが壊れていることを伝えてください", - "description": "The 2nd paragraph of the 404 page" - }, - "theme.ErrorPageContent.title": { - "message": "エラーが発生しました", - "description": "The title of the fallback page when the page crashed" - }, - "theme.BackToTopButton.buttonAriaLabel": { - "message": "先頭へ戻る", - "description": "The ARIA label for the back to top button" - }, - "theme.blog.archive.title": { - "message": "アーカイブ", - "description": "The page & hero title of the blog archive page" - }, - "theme.blog.archive.description": { - "message": "アーカイブ", - "description": "The page & hero description of the blog archive page" - }, - "theme.blog.paginator.navAriaLabel": { - "message": "ブログ記事一覧のナビゲーション", - "description": "The ARIA label for the blog pagination" - }, - "theme.blog.paginator.newerEntries": { - "message": "新しい記事", - "description": "The label used to navigate to the newer blog posts page (previous page)" - }, - "theme.blog.paginator.olderEntries": { - "message": "過去の記事", - "description": "The label used to navigate to the older blog posts page (next page)" - }, - "theme.blog.post.paginator.navAriaLabel": { - "message": "ブログ記事のナビゲーション", - "description": "The ARIA label for the blog posts pagination" - }, - "theme.blog.post.paginator.newerPost": { - "message": "新しい記事", - "description": "The blog post button label to navigate to the newer/previous post" - }, - "theme.blog.post.paginator.olderPost": { - "message": "過去の記事", - "description": "The blog post button label to navigate to the older/next post" - }, - "theme.blog.post.plurals": { - "message": "{count}件", - "description": "Pluralized label for \"{count} posts\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)" - }, - "theme.blog.tagTitle": { - "message": "「{tagName}」タグの記事が{nPosts}件あります", - "description": "The title of the page for a blog tag" - }, - "theme.tags.tagsPageLink": { - "message": "全てのタグを見る", - "description": "The label of the link targeting the tag list page" - }, - "theme.colorToggle.ariaLabel": { - "message": "ダークモードを切り替える(現在は{mode})", - "description": "The ARIA label for the navbar color mode toggle" - }, - "theme.colorToggle.ariaLabel.mode.dark": { - "message": "ダークモード", - "description": "The name for the dark color mode" - }, - "theme.colorToggle.ariaLabel.mode.light": { - "message": "ライトモード", - "description": "The name for the light color mode" - }, - "theme.docs.breadcrumbs.navAriaLabel": { - "message": "パンくずリストのナビゲーション", - "description": "The ARIA label for the breadcrumbs" - }, - "theme.docs.DocCard.categoryDescription.plurals": { - "message": "{count}項目", - "description": "The default description for a category card in the generated index about how many items this category includes" - }, - "theme.docs.paginator.navAriaLabel": { - "message": "ドキュメントページ", - "description": "The ARIA label for the docs pagination" - }, - "theme.docs.paginator.previous": { - "message": "前へ", - "description": "The label used to navigate to the previous doc" - }, - "theme.docs.paginator.next": { - "message": "次へ", - "description": "The label used to navigate to the next doc" - }, - "theme.docs.tagDocListPageTitle.nDocsTagged": { - "message": "{count}記事", - "description": "Pluralized label for \"{count} docs tagged\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)" - }, - "theme.docs.tagDocListPageTitle": { - "message": "「{tagName}」タグのついた{nDocsTagged}", - "description": "The title of the page for a docs tag" - }, - "theme.docs.versionBadge.label": { - "message": "バージョン: {versionLabel}" - }, - "theme.docs.versions.unreleasedVersionLabel": { - "message": "これはリリース前のバージョン{versionLabel}の{siteTitle}のドキュメントです。", - "description": "The label used to tell the user that he's browsing an unreleased doc version" - }, - "theme.docs.versions.unmaintainedVersionLabel": { - "message": "これはバージョン{versionLabel}の{siteTitle}のドキュメントで現在はメンテナンスされていません", - "description": "The label used to tell the user that he's browsing an unmaintained doc version" - }, - "theme.docs.versions.latestVersionSuggestionLabel": { - "message": "最新のドキュメントは{latestVersionLink} ({versionLabel}) を見てください", - "description": "The label used to tell the user to check the latest version" - }, - "theme.docs.versions.latestVersionLinkLabel": { - "message": "最新バージョン", - "description": "The label used for the latest version suggestion link label" - }, - "theme.common.editThisPage": { - "message": "このページを編集", - "description": "The link label to edit the current page" - }, - "theme.lastUpdated.atDate": { - "message": "{date}に", - "description": "The words used to describe on which date a page has been last updated" - }, - "theme.lastUpdated.byUser": { - "message": "{user}が", - "description": "The words used to describe by who the page has been last updated" - }, - "theme.lastUpdated.lastUpdatedAtBy": { - "message": "{atDate}{byUser}最終更新", - "description": "The sentence used to display when a page has been last updated, and by who" - }, - "theme.common.headingLinkTitle": { - "message": "{heading} への直接リンク", - "description": "Title for link to heading" - }, - "theme.navbar.mobileVersionsDropdown.label": { - "message": "他のバージョン", - "description": "The label for the navbar versions dropdown on mobile view" - }, - "theme.tags.tagsListLabel": { - "message": "タグ:", - "description": "The label alongside a tag list" - }, - "theme.admonition.caution": { - "message": "注意", - "description": "The default label used for the Caution admonition (:::caution)" - }, - "theme.admonition.danger": { - "message": "危険", - "description": "The default label used for the Danger admonition (:::danger)" - }, - "theme.admonition.info": { - "message": "備考", - "description": "The default label used for the Info admonition (:::info)" - }, - "theme.admonition.note": { - "message": "注記", - "description": "The default label used for the Note admonition (:::note)" - }, - "theme.admonition.tip": { - "message": "ヒント", - "description": "The default label used for the Tip admonition (:::tip)" - }, - "theme.admonition.warning": { - "message": "警告", - "description": "The default label used for the Warning admonition (:::warning)" - }, - "theme.AnnouncementBar.closeButtonAriaLabel": { - "message": "閉じる", - "description": "The ARIA label for close button of announcement bar" - }, - "theme.blog.sidebar.navAriaLabel": { - "message": "最近のブログ記事のナビゲーション", - "description": "The ARIA label for recent posts in the blog sidebar" - }, - "theme.CodeBlock.copied": { - "message": "コピーしました", - "description": "The copied button label on code blocks" - }, - "theme.CodeBlock.copyButtonAriaLabel": { - "message": "クリップボードにコードをコピー", - "description": "The ARIA label for copy code blocks button" - }, - "theme.CodeBlock.copy": { - "message": "コピー", - "description": "The copy button label on code blocks" - }, - "theme.CodeBlock.wordWrapToggle": { - "message": "折り返し", - "description": "The title attribute for toggle word wrapping button of code block lines" - }, - "theme.DocSidebarItem.expandCategoryAriaLabel": { - "message": "'{label}'の目次を開く", - "description": "The ARIA label to expand the sidebar category" - }, - "theme.DocSidebarItem.collapseCategoryAriaLabel": { - "message": "'{label}'の目次を隠す", - "description": "The ARIA label to collapse the sidebar category" - }, - "theme.NavBar.navAriaLabel": { - "message": "ナビゲーション", - "description": "The ARIA label for the main navigation" - }, - "theme.navbar.mobileLanguageDropdown.label": { - "message": "他の言語", - "description": "The label for the mobile language switcher dropdown" - }, - "theme.TOCCollapsible.toggleButtonLabel": { - "message": "このページの見出し", - "description": "The label used by the button on the collapsible TOC component" - }, - "theme.blog.post.readMore": { - "message": "もっと見る", - "description": "The label used in blog post item excerpts to link to full blog posts" - }, - "theme.blog.post.readMoreLabel": { - "message": "{title}についてもっと見る", - "description": "The ARIA label for the link to full blog posts from excerpts" - }, - "theme.blog.post.readingTime.plurals": { - "message": "約{readingTime}分", - "description": "Pluralized label for \"{readingTime} min read\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)" - }, - "theme.docs.breadcrumbs.home": { - "message": "ホームページ", - "description": "The ARIA label for the home page in the breadcrumbs" - }, - "theme.docs.sidebar.collapseButtonTitle": { - "message": "サイドバーを隠す", - "description": "The title attribute for collapse button of doc sidebar" - }, - "theme.docs.sidebar.collapseButtonAriaLabel": { - "message": "サイドバーを隠す", - "description": "The title attribute for collapse button of doc sidebar" - }, - "theme.docs.sidebar.navAriaLabel": { - "message": "ドキュメントのサイドバー", - "description": "The ARIA label for the sidebar navigation" - }, - "theme.docs.sidebar.closeSidebarButtonAriaLabel": { - "message": "ナビゲーションバーを閉じる", - "description": "The ARIA label for close button of mobile sidebar" - }, - "theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel": { - "message": "← メインメニューに戻る", - "description": "The label of the back button to return to main menu, inside the mobile navbar sidebar secondary menu (notably used to display the docs sidebar)" - }, - "theme.docs.sidebar.toggleSidebarButtonAriaLabel": { - "message": "ナビゲーションバーを開く", - "description": "The ARIA label for hamburger menu button of mobile navigation" - }, - "theme.docs.sidebar.expandButtonTitle": { - "message": "サイドバーを開く", - "description": "The ARIA label and title attribute for expand button of doc sidebar" - }, - "theme.docs.sidebar.expandButtonAriaLabel": { - "message": "サイドバーを開く", - "description": "The ARIA label and title attribute for expand button of doc sidebar" - }, - "theme.ErrorPageContent.tryAgain": { - "message": "もう一度試してください", - "description": "The label of the button to try again rendering when the React error boundary captures an error" - }, - "theme.common.skipToMainContent": { - "message": "メインコンテンツまでスキップ", - "description": "The skip to content label used for accessibility, allowing to rapidly navigate to main content with keyboard tab/enter navigation" - }, - "theme.tags.tagsPageTitle": { - "message": "タグ", - "description": "The title of the tag list page" - }, - "theme.unlistedContent.title": { - "message": "非公開のページ", - "description": "The unlisted content banner title" - }, - "theme.unlistedContent.message": { - "message": "このページは非公開です。 検索対象外となり、このページのリンクに直接アクセスできるユーザーのみに公開されます。", - "description": "The unlisted content banner message" - } -} diff --git a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current.json b/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current.json deleted file mode 100644 index ecd4e1e22..000000000 --- a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "version.label": { - "message": "Next", - "description": "The label for version current" - }, - "sidebar.sidebarBoltPy.category.Basic concepts": { - "message": "基本的な概念", - "description": "The label for category Basic concepts in sidebar sidebarBoltPy" - }, - "sidebar.sidebarBoltPy.category.Advanced concepts": { - "message": "応用コンセプト", - "description": "The label for category Advanced concepts in sidebar sidebarBoltPy" - }, - "sidebar.sidebarBoltPy.category.steps from apps (Deprecated)": { - "message": "ワークフローステップ 非推奨", - "description": "The label for category steps from apps (Deprecated) in sidebar sidebarBoltPy" - }, - "sidebar.sidebarBoltPy.category.Tutorials": { - "message": "チュートリアル", - "description": "The label for category Tutorials in sidebar sidebarBoltPy" - }, - "sidebar.sidebarBoltPy.link.Code on GitHub": { - "message": "Code on GitHub", - "description": "The label for link Code on GitHub in sidebar sidebarBoltPy, linking to https://github.com/SlackAPI/bolt-python" - }, - "sidebar.sidebarBoltPy.link.Contributors Guide": { - "message": "貢献", - "description": "The label for link Contributors Guide in sidebar sidebarBoltPy, linking to https://github.com/SlackAPI/bolt-python/blob/main/.github/contributing.md" - } -} diff --git a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/basic/action-listening.md b/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/basic/action-listening.md deleted file mode 100644 index 7be3340d6..000000000 --- a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/basic/action-listening.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -title: アクションのリスニング -lang: ja-jp -slug: /concepts/action-listening ---- - -Bolt アプリは `action` メソッドを用いて、ボタンのクリック、メニューの選択、メッセージショートカットなどのユーザーのアクションをリッスンすることができます。 - -アクションは `str` 型または `re.Pattern` 型の `action_id` でフィルタリングできます。`action_id` は、Slack プラットフォーム上のインタラクティブコンポーネントを区別する一意の識別子として機能します。 - -`action()` を使ったすべての例で `ack()` が使用されていることに注目してください。アクションのリスナー内では、Slack からのリクエストを受信したことを確認するために、`ack()` 関数を呼び出す必要があります。これについては、[リクエストの確認](/concepts/acknowledge)セクションで説明しています。 - -指定可能な引数の一覧はモジュールドキュメントを参考にしてください。 -```python -# 'approve_button' という action_id のブロックエレメントがトリガーされるたびに、このリスナーが呼び出させれる -@app.action("approve_button") -def update_message(ack): - ack() - # アクションへの反応としてメッセージを更新 -``` - -
- -

制約付きオブジェクトを使用したアクションのリスニング

-
- -制約付きのオブジェクトを使用すると、`block_id` と `action_id` をそれぞれ、または任意に組み合わせてリッスンできます。オブジェクト内の制約は、`str` 型または `re.Pattern` 型で指定できます。 - -```python -# この関数は、block_id が 'assign_ticket' に一致し -# かつ action_id が 'select_user' に一致する場合にのみ呼び出される -@app.action({ - "block_id": "assign_ticket", - "action_id": "select_user" -}) -def update_message(ack, body, client): - ack() - - if "container" in body and "message_ts" in body["container"]: - client.reactions_add( - name="white_check_mark", - channel=body["channel"]["id"], - timestamp=body["container"]["message_ts"], - ) -``` - -
\ No newline at end of file diff --git a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/basic/action-respond.md b/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/basic/action-respond.md deleted file mode 100644 index 3a31a05c5..000000000 --- a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/basic/action-respond.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -title: アクションへの応答 -lang: ja-jp -slug: /concepts/action-respond ---- - -アクションへの応答には、主に 2 つの方法があります。1 つ目の最も一般的なやり方は `say()` を使用する方法です。そのリクエストが発生した会話(チャンネルや DM)にメッセージを返します。 - -2 つ目は、`respond()` を使用する方法です。これは、アクションに関連づけられた `response_url` を使ったメッセージ送信を行うためのユーティリティです。 - -指定可能な引数の一覧はモジュールドキュメントを参考にしてください。 -```python -# 'approve_button' という action_id のインタラクティブコンポーネントがトリガーされると、このリスナーが呼ばれる -@app.action("approve_button") -def approve_request(ack, say): - # アクションのリクエストを確認 - ack() - say("Request approved 👍") -``` - -
- -respond() の利用 - - -`respond()` は `response_url` を使って送信するときに便利なメソッドで、これらと同じような動作をします。投稿するメッセージのペイロードには、全ての[メッセージペイロードのプロパティ](https://api.slack.com/reference/messaging/payload)とオプションのプロパティとして `response_type`(値は `"in_channel"` または `"ephemeral"`)、`replace_original`、`delete_original`、`unfurl_links`、`unfurl_media` などを指定できます。こうすることによってアプリから送信されるメッセージは、やり取りの発生元に反映されます。 - -```python -# 'user_select' という action_id を持つアクションのトリガーをリッスン -@app.action("user_select") -def select_user(ack, action, respond): - ack() - respond(f"You selected <@{action['selected_user']}>") -``` - -
\ No newline at end of file diff --git a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/basic/app-home.md b/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/basic/app-home.md deleted file mode 100644 index 6954bb75e..000000000 --- a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/basic/app-home.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -title: ホームタブの更新 -lang: ja-jp -slug: /concepts/app-home ---- - -ホームタブは、サイドバーや検索画面からアクセス可能なサーフェスエリアです。アプリはこのエリアを使ってユーザーごとのビューを表示することができます。アプリ設定ページで App Home の機能を有効にすると、`views.publish` API メソッドの呼び出しで `user_id` と[ビューのペイロード](https://api.slack.com/reference/block-kit/views)を指定して、ホームタブを公開・更新することができるようになります。 - -`app_home_opened` イベントをサブスクライブすると、ユーザーが App Home を開く操作をリッスンできます。 - -指定可能な引数の一覧はモジュールドキュメントを参考にしてください。 -```python -@app.event("app_home_opened") -def update_home_tab(client, event, logger): - try: - # 組み込みのクライアントを使って views.publish を呼び出す - client.views_publish( - # イベントに関連づけられたユーザー ID を使用 - user_id=event["user"], - # アプリの設定で予めホームタブが有効になっている必要がある - view={ - "type": "home", - "blocks": [ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "*Welcome home, <@" + event["user"] + "> :house:*" - } - }, - { - "type": "section", - "text": { - "type": "mrkdwn", - "text":"Learn how home tabs can be more useful and interactive ." - } - } - ] - } - ) - except Exception as e: - logger.error(f"Error publishing home tab: {e}") -``` \ No newline at end of file diff --git a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/basic/assistant.md b/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/basic/assistant.md deleted file mode 100644 index c09f7979e..000000000 --- a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/basic/assistant.md +++ /dev/null @@ -1,239 +0,0 @@ ---- -title: エージェント・アシスタント -lang: en -slug: /concepts/assistant ---- - -このページは、Bolt を使ってエージェント・アシスタントを実装するための方法を紹介します。この機能に関する一般的な情報については、[こちらのドキュメントページ(英語)](https://api.slack.com/docs/apps/ai)を参照してください。 - -この機能を実装するためには、まず[アプリの設定画面](https://api.slack.com/apps)で **Agents & Assistants** 機能を有効にし、**OAuth & Permissions** のページで [`assistant:write`](https://api.slack.com/scopes/assistant:write)、[chat:write](https://api.slack.com/scopes/chat:write)、[`im:history`](https://api.slack.com/scopes/im:history) を**ボットの**スコープに追加し、**Event Subscriptions** のページで [`assistant_thread_started`](https://api.slack.com/events/assistant_thread_started)、[`assistant_thread_context_changed`](https://api.slack.com/events/assistant_thread_context_changed)、[`message.im`](https://api.slack.com/events/message.im) イベントを有効にしてください。 - -また、この機能は Slack の有料プランでのみ利用可能です。もし開発用の有料プランのワークスペースをお持ちでない場合は、[Developer Program](https://api.slack.com/developer-program) に参加し、全ての有料プラン向け機能を利用可能なサンドボックス環境をつくることができます。 - -ユーザーとのアシスタントスレッド内でのやりとりを処理するには、`assistant_thread_started`、`assistant_thread_context_changed`、`message` イベントの `app.event(...)` リスナーを使うことも可能ですが、Bolt はよりシンプルなアプローチを提供しています。`Assistant` インスタンスを作り、それに必要なイベントリスナーを追加し、最後にこのアシスタント設定を `App` インスタンスに渡すだけでよいのです。 - -```python -assistant = Assistant() - -# ユーザーがアシスタントスレッドを開いたときに呼び出されます -@assistant.thread_started -def start_assistant_thread(say: Say, set_suggested_prompts: SetSuggestedPrompts): - # ユーザーに対して最初の返信を送信します - say(":wave: Hi, how can I help you today?") - - # プロンプト例を送るのは必須ではありません - set_suggested_prompts( - prompts=[ - # もしプロンプトが長い場合は {"title": "表示する短いラベル", "message": "完全なプロンプト"} を使うことができます - "What does SLACK stand for?", - "When Slack was released?", - ], - ) - -# ユーザーがスレッド内で返信したときに呼び出されます -@assistant.user_message -def respond_in_assistant_thread( - payload: dict, - logger: logging.Logger, - context: BoltContext, - set_status: SetStatus, - say: Say, - client: WebClient, -): - try: - # ユーザーにこのbotがリクエストを受信して作業中であることを伝えます - set_status("is typing...") - - # 会話の履歴を取得します - replies_in_thread = client.conversations_replies( - channel=context.channel_id, - ts=context.thread_ts, - oldest=context.thread_ts, - limit=10, - ) - messages_in_thread: List[Dict[str, str]] = [] - for message in replies_in_thread["messages"]: - role = "user" if message.get("bot_id") is None else "assistant" - messages_in_thread.append({"role": role, "content": message["text"]}) - - # プロンプトと会話の履歴を LLM に渡します(この call_llm はあなた自身のコードです) - returned_message = call_llm(messages_in_thread) - - # 結果をアシスタントスレッドに送信します - say(text=returned_message) - - except Exception as e: - logger.exception(f"Failed to respond to an inquiry: {e}") - # エラーになった場合は必ずメッセージを送信するようにしてください - # そうしなかった場合、'is typing...' の表示のままになってしまい、ユーザーは会話を続けることができなくなります - say(f":warning: Sorry, something went wrong during processing your request (error: {e})") - -# このミドルウェアを Bolt アプリに追加します -app.use(assistant) -``` - -リスナーに指定可能な引数の一覧はモジュールドキュメントを参考にしてください。 - - -ユーザーがチャンネルの横でアシスタントスレッドを開いた場合、そのチャンネルの情報は、そのスレッドの `AssistantThreadContext` データとして保持され、 `get_thread_context` ユーティリティを使ってアクセスすることができます。Bolt がこのユーティリティを提供している理由は、後続のユーザーメッセージ投稿のイベントペイロードに最新のスレッドのコンテキスト情報は含まれないためです。そのため、アプリはコンテキスト情報が変更されたタイミングでそれを何らかの方法で保存し、後続のメッセージイベントのリスナーコードから参照できるようにする必要があります。 - -そのユーザーがチャンネルを切り替えた場合、`assistant_thread_context_changed` イベントがあなたのアプリに送信されます。(上記のコード例のように)組み込みの `Assistant` ミドルウェアをカスタム設定なしで利用している場合、この更新されたチャンネル情報は、自動的にこのアシスタントボットからの最初の返信のメッセージメタデータとして保存されます。これは、組み込みの仕組みを使う場合は、このコンテキスト情報を自前で用意したデータストアに保存する必要はないということです。この組み込みの仕組みの唯一の短所は、追加の Slack API 呼び出しによる処理時間のオーバーヘッドです。具体的には `get_thread_context` を実行したときに、この保存されたメッセージメタデータにアクセスするために `conversations.history` API が呼び出されます。 - -このデータを別の場所に保存したい場合、自前の `AssistantThreadContextStore` 実装を `Assistant` のコンストラクターに渡すことができます。リファレンス実装として、`FileAssistantThreadContextStore` というローカルファイルシステムを使って実装を提供しています: - -```python -# これはあくまで例であり、自前のものを渡すことができます -from slack_bolt import FileAssistantThreadContextStore -assistant = Assistant(thread_context_store=FileAssistantThreadContextStore()) -``` - -このリファレンス実装はローカルファイルに依存しており、本番環境での利用は推奨しません。本番アプリでは `AssistantThreadContextStore` を継承した自前のクラスを使うようにしてください。 - -
- - -アシスタントスレッドでの Block Kit インタラクション - - -より高度なユースケースでは、上のようなプロンプト例の提案ではなく Block Kit のボタンなどを使いたいという場合があるかもしれません。そして、後続の処理のために[構造化されたメッセージメタデータ](https://api.slack.com/metadata)を含むメッセージを送信したいという場合もあるでしょう。 - -例えば、アプリが最初の返信で「参照しているチャンネルを要約」のようなボタンを表示し、ユーザーがそれをクリックして、より詳細な情報(例:要約するメッセージ数・日数、要約の目的など)を送信、アプリがそれを構造化されたメータデータに整理した上でリクエスト内容をボットのメッセージとして送信するようなシナリオです。 - -デフォルトでは、アプリはそのアプリ自身から送信したボットメッセージに応答することはできません(Bolt にはあらかじめ無限ループを防止する制御が入っているため)。`ignoring_self_assistant_message_events_enabled=False` を `App` のコンストラクターに渡し、`bot_message` リスナーを `Assistant` ミドルウェアに追加すると、上記の例のようなリクエストを伝えるボットメッセージを使って処理を継続することができるようになります。 - -```python -app = App( - token=os.environ["SLACK_BOT_TOKEN"], - # bot message を受け取るには必ずこれを指定してください - ignoring_self_assistant_message_events_enabled=False, -) - -assistant = Assistant() - -# リスナーに指定可能な引数の一覧は https://tools.slack.dev/bolt-python/api-docs/slack_bolt/kwargs_injection/args.html を参照してください - -@assistant.thread_started -def start_assistant_thread(say: Say): - say( - text=":wave: Hi, how can I help you today?", - blocks=[ - { - "type": "section", - "text": {"type": "mrkdwn", "text": ":wave: Hi, how can I help you today?"}, - }, - { - "type": "actions", - "elements": [ - # 複数のボタンを配置することが可能です - { - "type": "button", - "action_id": "assistant-generate-random-numbers", - "text": {"type": "plain_text", "text": "Generate random numbers"}, - "value": "clicked", - }, - ], - }, - ], - ) - -# 上のボタンがクリックされたときに実行されます -@app.action("assistant-generate-random-numbers") -def configure_random_number_generation(ack: Ack, client: WebClient, body: dict): - ack() - client.views_open( - trigger_id=body["trigger_id"], - view={ - "type": "modal", - "callback_id": "configure_assistant_summarize_channel", - "title": {"type": "plain_text", "text": "My Assistant"}, - "submit": {"type": "plain_text", "text": "Submit"}, - "close": {"type": "plain_text", "text": "Cancel"}, - # アシスタントスレッドの情報を app.view リスナーに引き継ぎます - "private_metadata": json.dumps( - { - "channel_id": body["channel"]["id"], - "thread_ts": body["message"]["thread_ts"], - } - ), - "blocks": [ - { - "type": "input", - "block_id": "num", - "label": {"type": "plain_text", "text": "# of outputs"}, - # 自然言語のテキストではなく、あらかじめ決められた形式の入力を受け取ることができます - "element": { - "type": "static_select", - "action_id": "input", - "placeholder": {"type": "plain_text", "text": "How many numbers do you need?"}, - "options": [ - {"text": {"type": "plain_text", "text": "5"}, "value": "5"}, - {"text": {"type": "plain_text", "text": "10"}, "value": "10"}, - {"text": {"type": "plain_text", "text": "20"}, "value": "20"}, - ], - "initial_option": {"text": {"type": "plain_text", "text": "5"}, "value": "5"}, - }, - } - ], - }, - ) - -# 上のモーダルが送信されたときに実行されます -@app.view("configure_assistant_summarize_channel") -def receive_random_number_generation_details(ack: Ack, client: WebClient, payload: dict): - ack() - num = payload["state"]["values"]["num"]["input"]["selected_option"]["value"] - thread = json.loads(payload["private_metadata"]) - - # 構造化された入力情報とともにボットのメッセージを送信します - # 以下の assistant.bot_message リスナーが処理を継続します - # このリスナー内で処理したい場合はそれでも構いません! - # bot_message リスナーが必要ない場合は ignoring_self_assistant_message_events_enabled=False を設定する必要はありません - client.chat_postMessage( - channel=thread["channel_id"], - thread_ts=thread["thread_ts"], - text=f"OK, you need {num} numbers. I will generate it shortly!", - metadata={ - "event_type": "assistant-generate-random-numbers", - "event_payload": {"num": int(num)}, - }, - ) - -# このアプリのボットユーザーがメッセージを送信したときに実行されます -@assistant.bot_message -def respond_to_bot_messages(logger: logging.Logger, set_status: SetStatus, say: Say, payload: dict): - try: - if payload.get("metadata", {}).get("event_type") == "assistant-generate-random-numbers": - # 上の random-number-generation リクエストを処理します - set_status("is generating an array of random numbers...") - time.sleep(1) - nums: Set[str] = set() - num = payload["metadata"]["event_payload"]["num"] - while len(nums) < num: - nums.add(str(random.randint(1, 100))) - say(f"Here you are: {', '.join(nums)}") - else: - # それ以外のパターンでは何もしません - # さらに他のパターンを追加する場合、メッセージ送信の無限ループを起こさないよう注意して実装してください - pass - - except Exception as e: - logger.exception(f"Failed to respond to an inquiry: {e}") - -# ユーザーが返信したときに実行されます -@assistant.user_message -def respond_to_user_messages(logger: logging.Logger, set_status: SetStatus, say: Say): - try: - set_status("is typing...") - say("Please use the buttons in the first reply instead :bow:") - except Exception as e: - logger.exception(f"Failed to respond to an inquiry: {e}") - say(f":warning: Sorry, something went wrong during processing your request (error: {e})") - - -# このミドルウェアを Bolt アプリに追加します -app.use(assistant) -``` - -
- -最後に、動作する完全なサンプルコード例を確認したい場合は、私たちが GitHub 上で提供している[サンプルアプリのリポジトリ](https://github.com/slack-samples/bolt-python-assistant-template)をチェックしてみてください。 \ No newline at end of file diff --git a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/basic/custom-steps.md b/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/basic/custom-steps.md deleted file mode 100644 index bc1089d21..000000000 --- a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/basic/custom-steps.md +++ /dev/null @@ -1,153 +0,0 @@ ---- -title: Listening and responding to custom steps -lang: ja-jp -slug: /concepts/custom-steps ---- - -Your app can use the `function()` method to listen to incoming [custom step requests](https://api.slack.com/automation/functions/custom-bolt). Custom steps are used in Workflow Builder to build workflows. The method requires a step `callback_id` of type `str`. This `callback_id` must also be defined in your [Function](https://api.slack.com/concepts/manifests#functions) definition. Custom steps must be finalized using the `complete()` or `fail()` listener arguments to notify Slack that your app has processed the request. - -* `complete()` requires **one** argument: `outputs` of type `dict`. It ends your custom step **successfully** and provides a dictionary containing the outputs of your custom step as per its definition. -* `fail()` requires **one** argument: `error` of type `str`. It ends your custom step **unsuccessfully** and provides a message containing information regarding why your custom step failed. - -You can reference your custom step's inputs using the `inputs` listener argument of type `dict`. - -Refer to [the module document](https://tools.slack.dev/bolt-python/api-docs/slack_bolt/kwargs_injection/args.html) to learn about the available listener arguments. - -```python -# This sample custom step formats an input and outputs it -@app.function("sample_custom_step") -def sample_step_callback(inputs: dict, fail: Fail, complete: Complete): - try: - message = inputs["message"] - complete( - outputs={ - "message": f":wave: You submitted the following message: \n\n>{message}" - } - ) - except Exception as e: - fail(f"Failed to handle a custom step request (error: {e})") - raise e -``` - -
- -Example app manifest definition - - -```json -... -"functions": { - "sample_custom_step": { - "title": "Sample custom step", - "description": "Run a sample custom step", - "input_parameters": { - "message": { - "type": "string", - "title": "Message", - "description": "A message to be formatted by the custom step", - "is_required": true, - } - }, - "output_parameters": { - "message": { - "type": "string", - "title": "Messge", - "description": "A formatted message", - "is_required": true, - } - } - } -} -``` - -
- ---- - -### Listening to custom step interactivity events - -Your app's custom steps may create interactivity points for users, for example: Post a message with a button. - -If such interaction points originate from a custom step execution, the events sent to your app representing the end-user interaction with these points are considered to be _function-scoped interactivity events_. These interactivity events can be handled by your app using the same concepts we covered earlier, such as [Listening to actions](/concepts/action-listening). - -_function-scoped interactivity events_ will contain data related to the custom step (`function_executed` event) they were spawned from, such as custom step `inputs` and access to `complete()` and `fail()` listener arguments. - -Your app can skip calling `complete()` or `fail()` in the `function()` handler method if the custom step creates an interaction point that requires user interaction before the step can end. However, in the relevant interactivity handler method, your app must invoke `complete()` or `fail()` to notify Slack that the custom step has been processed. - -You’ll notice in all interactivity handler examples, `ack()` is used. It is required to call the `ack()` function within an interactivity listener to acknowledge that the request was received from Slack. This is discussed in the [acknowledging requests section](/concepts/acknowledge). - -```python -# This sample custom step posts a message with a button -@app.function("custom_step_button") -def sample_step_callback(inputs, say, fail): - try: - say( - channel=inputs["user_id"], # sending a DM to this user - text="Click the button to signal the step completion", - blocks=[ - { - "type": "section", - "text": {"type": "mrkdwn", "text": "Click the button to signal step completion"}, - "accessory": { - "type": "button", - "text": {"type": "plain_text", "text": "Complete step"}, - "action_id": "sample_click", - }, - } - ], - ) - except Exception as e: - fail(f"Failed to handle a function request (error: {e})") - -# Your listener will be called every time a block element with the action_id "sample_click" is triggered -@app.action("sample_click") -def handle_sample_click(ack, body, context, client, complete, fail): - ack() - try: - # Since the button no longer works, we should remove it - client.chat_update( - channel=context.channel_id, - ts=body["message"]["ts"], - text="Congrats! You clicked the button", - ) - - # Signal that the custom step completed successfully - complete({"user_id": context.actor_user_id}) - except Exception as e: - fail(f"Failed to handle a function request (error: {e})") -``` - -
- -Example app manifest definition - - -```json -... -"functions": { - "custom_step_button": { - "title": "Custom step with a button", - "description": "Custom step that waits for a button click", - "input_parameters": { - "user_id": { - "type": "slack#/types/user_id", - "title": "User", - "description": "The recipient of a message with a button", - "is_required": true, - } - }, - "output_parameters": { - "user_id": { - "type": "slack#/types/user_id", - "title": "User", - "description": "The user that completed the function", - "is_required": true - } - } - } -} -``` - -
- -Learn more about responding to interactivity, see the [Slack API documentation](https://api.slack.com/automation/functions/custom-bolt#interactivity). diff --git a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/basic/event-listening.md b/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/basic/event-listening.md deleted file mode 100644 index 6d0409bb0..000000000 --- a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/basic/event-listening.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -title: イベントのリスニング -lang: ja-jp -slug: /concepts/event-listening ---- - -`event()` メソッドを使うと、[Events API](https://api.slack.com/events) の任意のイベントをリッスンできます。リッスンするイベントは、アプリの設定であらかじめサブスクライブしておく必要があります。これを利用することで、アプリがインストールされたワークスペースで何らかのイベント(例:ユーザーがメッセージにリアクションをつけた、ユーザーがチャンネルに参加した)が発生したときに、アプリに何らかのアクションを実行させることができます。 - -`event()` メソッドには `str` 型の `eventType` を指定する必要があります。 - -指定可能な引数の一覧はモジュールドキュメントを参考にしてください。 -```python -# ユーザーがワークスペースに参加した際に、自己紹介を促すメッセージを指定のチャンネルに送信 -@app.event("team_join") -def ask_for_introduction(event, say): - welcome_channel_id = "C12345" - user_id = event["user"] - text = f"Welcome to the team, <@{user_id}>! 🎉 You can introduce yourself in this channel." - say(text=text, channel=welcome_channel_id) -``` - -
- - -メッセージのサブタイプのフィルタリング - - -`message()` リスナーは `event("message")` と等価の機能を提供します。 - -`subtype` という追加のキーを指定して、イベントのサブタイプでフィルタリングすることもできます。よく使われるサブタイプには、`bot_message` や `message_replied` があります。詳しくは[メッセージイベントページ](https://api.slack.com/events/message#message_subtypes)を参照してください。サブタイプなしのイベントだけにフィルターするために明に `None` を指定することもできます。 - - -```python -# 変更されたすべてのメッセージに一致 -@app.event({ - "type": "message", - "subtype": "message_changed" -}) -def log_message_change(logger, event): - user, text = event["user"], event["text"] - logger.info(f"The user {user} changed the message to {text}") -``` -
\ No newline at end of file diff --git a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/basic/web-api.md b/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/basic/web-api.md deleted file mode 100644 index 0070ed0fb..000000000 --- a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/basic/web-api.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -title: Web API の使い方 -lang: ja-jp -slug: /concepts/web-api ---- - -`app.client`、またはミドルウェア・リスナーの引数 `client` として Bolt アプリに提供されている [`WebClient`](https://tools.slack.dev/python-slack-sdk/basic_usage.html) は必要な権限を付与されており、これを利用することで[あらゆる Web API メソッド](https://api.slack.com/methods)を呼び出すことができます。このクライアントのメソッドを呼び出すと `SlackResponse` という Slack からの応答情報を含むオブジェクトが返されます。 - -Bolt の初期化に使用するトークンは `context` オブジェクトに設定されます。このトークンは、多くの Web API メソッドを呼び出す際に必要となります。 - -指定可能な引数の一覧はモジュールドキュメントを参考にしてください。 -```python -@app.message("wake me up") -def say_hello(client, message): - # 2020 年 9 月 30 日午後 11:59:59 を示す Unix エポック秒 - when_september_ends = 1601510399 - channel_id = message["channel"] - client.chat_scheduleMessage( - channel=channel_id, - post_at=when_september_ends, - text="Summer has come and passed" - ) -``` \ No newline at end of file diff --git a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/steps/adding-editing-steps.md b/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/steps/adding-editing-steps.md deleted file mode 100644 index 24b85bfa7..000000000 --- a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/steps/adding-editing-steps.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -title: ステップの追加・編集 -lang: ja-jp -slug: /concepts/adding-editing-steps ---- - -作成したワークフローステップがワークフローに追加またはその設定を変更されるタイミングで、[`workflow_step_edit` イベントがアプリに送信されます](https://api.slack.com/reference/workflows/workflow_step_edit)。このイベントがアプリに届くと、`WorkflowStep` で設定した `edit` コールバックが実行されます。 - -ステップの追加と編集のどちらが行われるときも、[ワークフローステップの設定モーダル](https://api.slack.com/reference/workflows/configuration-view)をビルダーに送信する必要があります。このモーダルは、そのステップ独自の設定を選択するための場所です。通常のモーダルより制限が強く、例えば `title`、`submit`、`close` のプロパティを含めることができません。設定モーダルの `callback_id` は、デフォルトではワークフローステップと同じものになります。 - -`edit` コールバック内で `configure()` ユーティリティを使用すると、対応する `blocks` 引数にビューのblocks 部分だけを渡して、ステップの設定モーダルを簡単に表示させることができます。必要な入力内容が揃うまで設定の保存を無効にするには、`True` の値をセットした `submit_disabled` を渡します。 - -設定モーダルの開き方に関する詳細は、[こちらのドキュメント](https://api.slack.com/workflows/steps#handle_config_view)を参照してください。 - -指定可能な引数の一覧はモジュールドキュメントを参考にしてください(共通 / ステップ用 - -```python -def edit(ack, step, configure): - ack() - - blocks = [ - { - "type": "input", - "block_id": "task_name_input", - "element": { - "type": "plain_text_input", - "action_id": "name", - "placeholder": {"type": "plain_text", "text":"Add a task name"}, - }, - "label": {"type": "plain_text", "text":"Task name"}, - }, - { - "type": "input", - "block_id": "task_description_input", - "element": { - "type": "plain_text_input", - "action_id": "description", - "placeholder": {"type": "plain_text", "text":"Add a task description"}, - }, - "label": {"type": "plain_text", "text":"Task description"}, - }, - ] - configure(blocks=blocks) - -ws = WorkflowStep( - callback_id="add_task", - edit=edit, - save=save, - execute=execute, -) -app.step(ws) -``` \ No newline at end of file diff --git a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/steps/creating-steps.md b/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/steps/creating-steps.md deleted file mode 100644 index 889543767..000000000 --- a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/steps/creating-steps.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -title: ステップの定義 -lang: ja-jp -slug: /concepts/creating-steps ---- - -ワークフローステップの作成には、Bolt が提供する `WorkflowStep` クラスを利用します。 - -ステップの `callback_id` と設定オブジェクトを指定して、`WorkflowStep` の新しいインスタンスを作成します。 - -設定オブジェクトは、`edit`、`save`、`execute` という 3 つのキーを持ちます。それぞれのキーは、単一のコールバック、またはコールバックのリストである必要があります。すべてのコールバックは、ワークフローステップのイベントに関する情報を保持する `step` オブジェクトにアクセスできます。 - -`WorkflowStep` のインスタンスを作成したら、それを`app.step()` メソッドに渡します。これによって、アプリがワークフローステップのイベントをリッスンし、設定オブジェクトで指定されたコールバックを使ってそれに応答できるようになります。 - -また、デコレーターとして利用できる `WorkflowStepBuilder` クラスを使ってワークフローステップを定義することもできます。 詳細は、[こちらのドキュメント](https://tools.slack.dev/bolt-python/api-docs/slack_bolt/workflows/step/step.html#slack_bolt.workflows.step.step.WorkflowStepBuilder)のコード例などを参考にしてください。 - -指定可能な引数の一覧はモジュールドキュメントを参考にしてください(共通 / ステップ用 - -```python -import os -from slack_bolt import App -from slack_bolt.workflows.step import WorkflowStep - -# いつも通りBolt アプリを起動する -app = App( - token=os.environ.get("SLACK_BOT_TOKEN"), - signing_secret=os.environ.get("SLACK_SIGNING_SECRET") -) - -def edit(ack, step, configure): - pass - -def save(ack, view, update): - pass - -def execute(step, complete, fail): - pass - -# WorkflowStep の新しいインスタンスを作成する -ws = WorkflowStep( - callback_id="add_task", - edit=edit, - save=save, - execute=execute, -) -# ワークフローステップを渡してリスナーを設定する -app.step(ws) -``` \ No newline at end of file diff --git a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/steps/executing-steps.md b/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/steps/executing-steps.md deleted file mode 100644 index e10c7eec3..000000000 --- a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/steps/executing-steps.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -title: ステップの実行 -lang: ja-jp -slug: /concepts/executing-steps ---- - -エンドユーザーがワークフローステップを実行すると、アプリに [`workflow_step_execute` イベントが送信されます](https://api.slack.com/events/workflow_step_execute)。このイベントがアプリに届くと、`WorkflowStep` で設定した `execute` コールバックが実行されます。 - -`save` コールバックで取り出した `inputs` を使って、サードパーティの API を呼び出す、情報をデータベースに保存する、ユーザーのホームタブを更新するといった処理を実行することができます。また、ワークフローの後続のステップで利用する出力値を `outputs` オブジェクトに設定します。 - -`execute` コールバック内では、`complete()` を呼び出してステップの実行が成功したことを示すか、`fail()` を呼び出してステップの実行が失敗したことを示す必要があります。 - -指定可能な引数の一覧はモジュールドキュメントを参考にしてください(共通 / ステップ用 -```python -def execute(step, complete, fail): - inputs = step["inputs"] - # すべての処理が成功した場合 - outputs = { - "task_name": inputs["task_name"]["value"], - "task_description": inputs["task_description"]["value"], - } - complete(outputs=outputs) - - # 失敗した処理がある場合 - error = {"message":"Just testing step failure!"} - fail(error=error) - -ws = WorkflowStep( - callback_id="add_task", - edit=edit, - save=save, - execute=execute, -) -app.step(ws) -``` \ No newline at end of file diff --git a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/steps/saving-steps.md b/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/steps/saving-steps.md deleted file mode 100644 index 94ad32934..000000000 --- a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/steps/saving-steps.md +++ /dev/null @@ -1,53 +0,0 @@ ---- -title: ステップの設定の保存 -lang: ja-jp -slug: /concepts/saving-steps ---- - -設定モーダルを開いた後、アプリは `view_submission` イベントをリッスンします。このイベントがアプリに届くと、`WorkflowStep` で設定した `save` コールバックが実行されます。 - -`save` コールバック内では、`update()` メソッドを使って、ワークフローに追加されたステップの設定を保存することができます。このメソッドには次の引数を指定します。 - -- `inputs` : ユーザーがワークフローステップを実行したときにアプリが受け取る予定のデータを表す辞書型の値です。 -- `outputs` : ワークフローステップの完了時にアプリが出力するデータが設定されたオブジェクトのリストです。この outputs は、ワークフローの後続のステップで利用することができます。 -- `step_name` : ステップのデフォルトの名前をオーバーライドします。 -- `step_image_url` : ステップのデフォルトの画像をオーバーライドします。 - -これらのパラメータの構成方法に関する詳細は、[こちらのドキュメント](https://api.slack.com/reference/workflows/workflow_step)を参照してください。 - -指定可能な引数の一覧はモジュールドキュメントを参考にしてください(共通 / ステップ用 - -```python -def save(ack, view, update): - ack() - - values = view["state"]["values"] - task_name = values["task_name_input"]["name"] - task_description = values["task_description_input"]["description"] - - inputs = { - "task_name": {"value": task_name["value"]}, - "task_description": {"value": task_description["value"]} - } - outputs = [ - { - "type": "text", - "name": "task_name", - "label":"Task name", - }, - { - "type": "text", - "name": "task_description", - "label":"Task description", - } - ] - update(inputs=inputs, outputs=outputs) - -ws = WorkflowStep( - callback_id="add_task", - edit=edit, - save=save, - execute=execute, -) -app.step(ws) -``` \ No newline at end of file diff --git a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/steps/steps.md b/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/steps/steps.md deleted file mode 100644 index 881d791b2..000000000 --- a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/steps/steps.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -title: ワークフローステップの概要 -lang: ja-jp -slug: /concepts/steps ---- - -(アプリによる)ワークフローステップでは、処理をアプリ側で行うカスタムのワークフローステップを提供することができます。ユーザーは[ワークフロービルダー](https://api.slack.com/workflows)を使ってこれらのステップをワークフローに追加できます。 - -ワークフローステップは、次の 3 つのユーザーイベントで構成されます。 - -- ワークフローステップをワークフローに追加・変更する -- ワークフロー内のステップの設定内容を更新する -- エンドユーザーがそのステップを実行する - -ワークフローステップを機能させるためには、これら 3 つのイベントすべてに対応する必要があります。 - -アプリを使ったワークフローステップに関する詳細は、[API ドキュメント](https://api.slack.com/workflows/steps)を参照してください。 \ No newline at end of file diff --git a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/tutorial/getting-started-http.md b/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/tutorial/getting-started-http.md deleted file mode 100644 index b6c461de2..000000000 --- a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/tutorial/getting-started-http.md +++ /dev/null @@ -1,322 +0,0 @@ ---- -title: Bolt 入門ガイド(HTTP) -slug: getting-started-http -lang: ja-jp ---- - -# Bolt 入門ガイド(HTTP) - -このガイドでは、**HTTP上で Bolt for Python** を使った Slack アプリの設定と起動する方法について説明します。ここで説明する手順は、新しい Slack アプリを作成し、ローカルの開発環境をセットアップし、Slack ワークスペースからのメッセージをリッスンして応答するアプリを開発するという流れになります。 - -この手順を全て終わらせたら、あなたはきっと ⚡️[Slack アプリのはじめ方](https://github.com/slackapi/bolt-python/tree/main/examples/getting_started)のサンプルアプリを動作させたり、それに変更を加えたり、自分のアプリを作ったりすることができるようになるでしょう。 - ---- - -### アプリを作成する {#create-an-app} -最初にやるべきこと : Bolt での開発を始める前に、[Slack アプリを作成](https://api.slack.com/apps/new)します。 - -:::tip - -いつもの仕事のさまたげにならないように、別の開発用のワークスペースを使用することをおすすめします。[新しいワークスペースは無料で作成できます](https://slack.com/get-started#create)。 - -::: - -アプリ名を入力し(_後で変更可能_)、インストール先のワークスペースを選択したら、「`Create App`」ボタンをクリックすると、アプリの **Basic Information** ページが表示されます。 - -このページでは、アプリの概要を確認できます。また、「**App Credentials**」ヘッダーの下では「`Signing Secret`」などの重要な認証情報も確認できます。これらの認証情報は後で必要になります。 - -![Basic Information ページ](/img/boltpy/basic-information-page.png "Basic Information ページ") - -ひと通り確認し、アプリのアイコンと説明を追加したら、アプリの構成 🔩 を始めましょう。 - ---- - -### トークンとアプリのインストール {#tokens-and-installing-apps} -Slack アプリでは、[Slack API へのアクセスの管理に OAuth を使用します](https://api.slack.com/docs/oauth)。アプリがインストールされると、トークンが発行されます。アプリはそのトークンを使って API メソッドを呼び出すことができます。 - -Slack アプリで使用できるトークンには、ユーザートークン(`xoxp`)とボットトークン(`xoxb`)、アプリレベルトークン(`xapp`)の 3 種類があります。 -- [ユーザートークン](https://api.slack.com/authentication/token-types#user) を使用すると、ユーザーがアプリをインストールまたは認証した後、アプリがそのユーザーを代理して API メソッドを呼び出すことができます。1 つのワークスペースに複数のユーザートークンが存在する可能性があります。 -- [ボットトークン](https://api.slack.com/authentication/token-types#bot) はボットユーザーに関連づけられ、1 つのワークスペースでは最初に誰かがそのアプリをインストールした際に一度だけ発行されます。どのユーザーがインストールを実行しても、アプリが使用するボットトークンは同じになります。_ほとんど_のアプリで使用されるのは、ボットトークンです。 -- [アプリレベルトークン](https://api.slack.com/authentication/token-types#app) は、組織に渡ってあなたのアプリを表すものです。所属する組織内の全てのワークスペースに、全ての個人ユーザによってインストールされたアプリについても同様です。アプリレベルトークンは WebSocket 通信を行うアプリを作る際に通常使われます。 - -説明を簡潔にするために、このガイドではボットトークンを使用します。 - -1. 左サイドバーの「**OAuth & Permissions**」をクリックし、「**Bot Token Scopes**」セクションまで下にスクロールします。「**Add an OAuth Scope**」をクリックします。 - -2. ここでは [`chat:write`](https://api.slack.com/scopes/chat:write) というスコープのみを追加します。このスコープは、アプリが参加しているチャンネルにメッセージを投稿することを許可します。 - -3. OAuth & Permissions ページの一番上までスクロールし、「**Install App to Workspace**」をクリックします。Slack の OAuth 確認画面 が表示されます。この画面で開発用ワークスペースへのアプリのインストールを承認します。 - -4. インストールを承認すると **OAuth & Permissions** ページが表示され、**Bot User OAuth Access Token** を確認できるでしょう。このトークンはこのあと利用します。 - -![OAuth トークン](/img/boltpy/bot-token.png "ボット用 OAuth トークン") - -:::tip - -トークンはパスワードと同様に取り扱い、[安全な方法で保管してください](https://api.slack.com/docs/oauth-safety)。アプリはこのトークンを使って Slack ワークスペースで投稿をしたり、情報の取得をしたりします。 - -::: - ---- - -### プロジェクトをセットアップする {#setting-up-your-project} -初期設定が終わったら、新しい Bolt プロジェクトのセットアップを行いましょう。このプロジェクトが、あなたのアプリのロジックを処理するコードを配置する場所となります。 - -プロジェクトをまだ作成していない場合は、新しく作成しましょう。空のディレクトリを作成します。 - -```shell -mkdir first-bolt-app -cd first-bolt-app -``` - -次に、プロジェクトの依存関係を管理する方法として、[Python 仮想環境](https://packaging.python.org/guides/installing-using-pip-and-virtual-environments/#creating-a-virtual-environment)を使ったおすすめの方法を紹介します。これはシステム Python に存在するパッケージとのコンフリクトを防ぐために推奨されている優れた方法です。[Python 3.6 以降](https://www.python.org/downloads/)の仮想環境を作成し、アクティブにしてみましょう。 - -```shell -python3 -m venv .venv -source .venv/bin/activate -``` - -`python3` へのパスがプロジェクトの中を指していることを確かめることで、仮想環境がアクティブになっていることを確認できます([Windows でもこれに似たコマンドが利用できます](https://packaging.python.org/guides/installing-using-pip-and-virtual-environments/#activating-a-virtual-environment))。 - -```shell -which python3 -# 出力結果 : /path/to/first-bolt-app/.venv/bin/python3 -``` - -Bolt for Python のパッケージを新しいプロジェクトにインストールする前に、アプリの設定時に作成された **ボットトークン** と **署名シークレット** を保存しましょう。 - -1. **Basic Information ページの署名シークレットをコピー**して、新しい環境変数に保存します。以下のコマンド例は Linux と macOS で利用できます。[Windows でもこれに似たコマンドが利用できます](https://superuser.com/questions/212150/how-to-set-env-variable-in-windows-cmd-line/212153#212153)。 -```shell -export SLACK_SIGNING_SECRET= -``` - -2. **OAuth & Permissions ページのボットトークン (xoxb) をコピー**して、新しい環境変数に保存します。以下のコマンド例は Linux と macOS で利用できます。[Windows でもこれに似たコマンドが利用できます](https://superuser.com/questions/212150/how-to-set-env-variable-in-windows-cmd-line/212153#212153)。 -```shell -export SLACK_BOT_TOKEN=xoxb- -``` - -> 🔒 全てのトークンは安全に保管してください。最低限、パブリックなバージョンコントロールにチェックインすることは避けてください。また、上記の例のように環境変数を介してアクセスするようにしてください。詳細な情報は [best practices for app security](https://api.slack.com/authentication/best-practices).のドキュメントを参照してください。 - - -完了したら、アプリを作ってみましょう。以下のコマンドを使って、仮想環境に Python の `slack_bolt` パッケージをインストールします。 - -```shell -pip install slack_bolt -``` - -このディレクトリに「`app.py`」という名前の新しいファイルを作成し、以下のコードを追加します。 - -```python -import os -from slack_bolt import App -from slack_bolt.adapter.socket_mode import SocketModeHandler - -# ボットトークンと署名シークレットを使ってアプリを初期化します -app = App( - token=os.environ.get("SLACK_BOT_TOKEN"), - signing_secret=os.environ.get("SLACK_SIGNING_SECRET") -) - -# アプリを起動します -if __name__ == "__main__": - app.start(port=int(os.environ.get("PORT", 3000))) -``` - -このようにトークンがあれば、最初の Bolt アプリが作成できます。「`app.py`」ファイルを保存して、コマンドラインで以下を実行します。 - -```script -python3 app.py -``` - -アプリが起動し、実行中であることが表示されます。🎉 - ---- - -### HTTP を利用したイベントを設定する {#setting-up-events} -アプリはワークスペース内の他のメンバーと同じように振る舞い、メッセージを投稿したり、絵文字リアクションを追加したり、イベントをリッスンして返答したりできます。 - -Slack ワークスペースで発生するイベント(メッセージが投稿されたときや、メッセージに対するリアクションがつけられたときなど)をリッスンするには、[Events API を使って特定の種類のイベントをサブスクライブします](https://api.slack.com/events-api)。 - -それでは、アプリのイベント設定を有効化してみましょう。 - -1. [アプリ管理ページ](https://api.slack.com/apps)でアプリをクリックします。次に、左サイドバーの「**Event Subscriptions**」をクリックします。「**Enable Events**」というラベルのスイッチをオンに切り替えます。 -2. リクエストURLを追加します。Slackはイベントに対応するHTTP POSTリクエストをこの [Request URL](https://api.slack.com/apis/connections/events-api#the-events-api__subscribing-to-event-types__events-api-request-urls) のエンドポイントに送信します。Bolt は `/slack/events` のエンドポイントで、全ての受信リクエストをリッスンします。これらのリクエストにはショートカット、イベント、インタラクションペイロードが含まれます。アプリの設定でエンドポイントを指定するときは、すべての Request URL の末尾に「/slack/events」を追加してください。例えば、 `https:///slack/events` のようになります。Bolt アプリが起動した状態のままなら、URL の検証が成功するはずです。 - -:::tip - -ローカル開発では、[ngrok](https://ngrok.com/)のようなプロキシサービスを使って公開 URL を作成し、リクエストを開発環境にトンネリングすることができます。このトンネリングの方法については、[ngrok のガイド](https://ngrok.com/docs#getting-started-expose)を参照してください。また、アプリのホスティングが必要になった場合には、[API サイトに](https://api.slack.com/docs/hosting) Slack開発者達がアプリのホスティングよく利用するホスティングプロバイダーを集めています。 - -::: - -それでは、Slackにどのイベントをリッスンするかを教えてあげましょう。 - -イベントが発生すると、そのイベントをトリガーしたユーザーやイベントが発生したチャンネルなど、イベントに関する情報が Slack からアプリに送信されます。アプリではこれらの情報を処理して、適切な応答を返します。 - -左側のサイドバーから **Event Subscriptions** にアクセスして、機能を有効にしてください。 **Subscribe to Bot Events** 配下で、ボットが受け取れる イベントを追加することができます。4つのメッセージに関するイベントがあります。 -- [`message.channels`](https://api.slack.com/events/message.channels) アプリが参加しているパブリックチャンネルのメッセージをリッスン -- [`message.groups`](https://api.slack.com/events/message.groups) アプリが参加しているプライベートチャンネルのメッセージをリッスン -- [`message.im`](https://api.slack.com/events/message.im) あなたのアプリとユーザーのダイレクトメッセージをリッスン -- [`message.mpim`](https://api.slack.com/events/message.mpim) あなたのアプリが追加されているグループ DM をリッスン - -ボットが参加するすべての場所のメッセージをリッスンさせるには、これら 4 つのメッセージイベントをすべて選択します。ボットにリッスンさせるメッセージイベントの種類を選択したら、「**Save Changes**」ボタンをクリックします。 - ---- - -### メッセージをリッスンして応答する {#listening-and-responding-to-a-message} -アプリにロジックを組み込む準備が整いました。まずは `message()` メソッドを使用して、メッセージのリスナーをアタッチしましょう。 - -次の例では、アプリが参加するチャンネルとダイレクトメッセージに投稿されるすべてのメッセージをリッスンし、「hello」というメッセージに応答を返します。 - -```python -import os -from slack_bolt import App - -# ボットトークンと署名シークレットを使ってアプリを初期化します -app = App( - token=os.environ.get("SLACK_BOT_TOKEN"), - signing_secret=os.environ.get("SLACK_SIGNING_SECRET") -) - -# 'hello' を含むメッセージをリッスンします -# 指定可能なリスナーのメソッド引数の一覧は以下のモジュールドキュメントを参考にしてください: -# https://tools.slack.dev/bolt-python/api-docs/slack_bolt/kwargs_injection/args.html -@app.message("hello") -def message_hello(message, say): - # イベントがトリガーされたチャンネルへ say() でメッセージを送信します - say(f"Hey there <@{message['user']}>!") - -# アプリを起動します -if __name__ == "__main__": - app.start(port=int(os.environ.get("PORT", 3000))) -``` - -アプリを再起動し、ボットユーザーが参加しているチャンネルまたはダイレクトメッセージに「hello」というメッセージを投稿すれば、アプリが応答するでしょう。 - -これはごく基本的なコード例ですが、最終的にやりたいことを実現するためにアプリをカスタマイズするための起点として利用できます。プレーンテキストを送信する代わりにボタンを表示するという、もう少しインタラクティブな動作を試してみましょう。 - ---- - -### アクションを送信して応答する {#sending-and-responding-to-actions} - -インタラクティブ機能を有効にすると、ボタン、選択メニュー、日付ピッカー、モーダル、ショートカットなどの機能が利用できるようになります。イベントと同様に、Slack からのアクション(*ユーザーがボタンをクリックした*など)の送信先となる URL を設定する必要があります。 - -アプリ設定ページに戻り、左サイドメニューの「**Interactivity & Shortcuts**」をクリックします。別の **Request URL** ボックスを見つけます。 - -:::tip - -デフォルトでは、Bolt はイベントに使用しているのと同じエンドポイントをインタラクティブコンポーネントにも使用するように設定されているため、上記と同じリクエスト URL(この例では「`https://8e8ec2d7.ngrok.io/slack/events`」)を使用します。このままの状態で、右下隅にある「**Save Changes**」ボタンを押してください。これでインタラクティブ機能がアプリで利用できるようになりました。 - -::: - -![Request URL の設定](/img/boltpy/request-url-config.png "Request URL の設定") - -インタラクティブ機能が有効化されている時、ショートカット、モーダル、インタラクティブコンポーネント (ボタンや、選択メニュー、日付ピッカー) とのインタラクションはイベントとしてアプリに対して送信されます。 - -それでは、アプリのコードに戻り、これらのイベントを処理する為のロジックを追加しましょう。 -- まず、インタラクティブコンポーネントを含んだメッセージをアプリから送信します(このケースではボタン)。 -- 次に、ユーザーから返されるボタンクリックのアクションをリッスンし、それに応答します。 - -以下のコードの後の部分を編集し、文字列だけのメッセージの代わりに、ボタンを含んだメッセージを送信するようにしてみます。 - -```python -import os -from slack_bolt import App - -# ボットトークンと署名シークレットを使ってアプリを初期化します -app = App( - token=os.environ.get("SLACK_BOT_TOKEN"), - signing_secret=os.environ.get("SLACK_SIGNING_SECRET") -) - -# 'hello' を含むメッセージをリッスンします -@app.message("hello") -def message_hello(message, say): - # イベントがトリガーされたチャンネルへ say() でメッセージを送信します - say( - blocks=[ - { - "type": "section", - "text": {"type": "mrkdwn", "text": f"Hey there <@{message['user']}>!"}, - "accessory": { - "type": "button", - "text": {"type": "plain_text", "text":"Click Me"}, - "action_id": "button_click" - } - } - ], - text=f"Hey there <@{message['user']}>!" - ) - -# アプリを起動します -if __name__ == "__main__": - app.start(port=int(os.environ.get("PORT", 3000))) -``` - -`say()` の中の値を `blocks` という配列のオブジェクトに変えました。ブロックは Slack メッセージを構成するコンポーネントであり、テキストや画像、日付ピッカーなど、さまざまなタイプのブロックがあります。この例では `accessory` に `button` を持たせた「section」のブロックを、アプリからの応答に含めています。`blocks` を使用する場合、`text` は通知やアクセシビリティのためのフォールバックとなります。 - -ボタンを含む `accessory` オブジェクトでは、`action_id` を指定していることがわかります。これは、ボタンを一意に示す識別子として機能します。これを使って、アプリをどのアクションに応答させるかを指定できます。 - -:::tip - -[Block Kit Builder](https://app.slack.com/block-kit-builder) を使用すると、インタラクティブなメッセージのプロトタイプを簡単に作成できます。自分自身やチームメンバーがメッセージのモックアップを作成し、生成される JSON をアプリに直接貼りつけることができます。 - -::: - -アプリを再起動し、アプリが参加しているチャンネルで「hello」と入力すると、ボタン付きのメッセージが表示されるようになりました。ただし、ボタンをクリックしても、*まだ*何も起こりません。 - -ハンドラーを追加して、ボタンがクリックされたときにフォローアップメッセージを送信するようにしてみましょう。 - -```python -import os -from slack_bolt import App - -# ボットトークンと署名シークレットを使ってアプリを初期化します -app = App( - token=os.environ.get("SLACK_BOT_TOKEN"), - signing_secret=os.environ.get("SLACK_SIGNING_SECRET") -) - -# 'hello' を含むメッセージをリッスンします -@app.message("hello") -def message_hello(message, say): - # イベントがトリガーされたチャンネルへ say() でメッセージを送信します - say( - blocks=[ - { - "type": "section", - "text": {"type": "mrkdwn", "text": f"Hey there <@{message['user']}>!"}, - "accessory": { - "type": "button", - "text": {"type": "plain_text", "text":"Click Me"}, - "action_id": "button_click" - } - } - ], - text=f"Hey there <@{message['user']}>!" - ) - -@app.action("button_click") -def action_button_click(body, ack, say): - # アクションを確認したことを即時で応答します - ack() - # チャンネルにメッセージを投稿します - say(f"<@{body['user']['id']}> clicked the button") - -# アプリを起動します -if __name__ == "__main__": - app.start(port=int(os.environ.get("PORT", 3000))) -``` - -`app.action()` を使って、先ほど命名した `button_click` という `action_id` をリッスンしています。アプリを再起動し、ボタンをクリックすると、アプリからの「clicked the button」というメッセージが新たに表示されるでしょう。 - ---- - -### 次のステップ {#next-steps} -はじめての [Bolt for Python アプリ](https://github.com/slackapi/bolt-python/tree/main/examples/getting_started)を構築することができました。🎉 - -ここまでで基本的なアプリをセットアップして実行することはできたので、次は自分だけの Bolt アプリを作る方法を調べてみましょう。参考になりそうな記事をいくつかご紹介します。 - -* 基本的な概念について読む。Bolt アプリがアクセスできるさまざまメソッドや機能について知ることができます。 -* [`app.event()` メソッド](/concepts/event-listening)でボットがリッスンできるイベントをほかにも試してみる。すべてのイベントの一覧は [API サイト](https://api.slack.com/events)で確認できます。 -* Bolt では、アプリにアタッチされたクライアントから [Web API メソッドを呼び出す](/concepts/web-api)ことができます。API サイトに [220 以上のメソッド](https://api.slack.com/methods)を一覧しています。 -* [API サイト](https://api.slack.com/docs/token-types)でほかのタイプのトークンを確認する。アプリで実行したいアクションによって、異なるトークンが必要になる場合があります。HTTPの代わりにソケットモードを利用したい場合には、`connections:write` のスコープを追加した、追加のトークン (`xapp`) が必要です。 \ No newline at end of file diff --git a/docs/i18n/ja-jp/docusaurus-theme-classic/footer.json b/docs/i18n/ja-jp/docusaurus-theme-classic/footer.json deleted file mode 100644 index 5a2d1bc10..000000000 --- a/docs/i18n/ja-jp/docusaurus-theme-classic/footer.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "copyright": { - "message": "

Made with ♡ by Slack and friends

", - "description": "The footer copyright" - } -} diff --git a/docs/i18n/ja-jp/docusaurus-theme-classic/navbar.json b/docs/i18n/ja-jp/docusaurus-theme-classic/navbar.json deleted file mode 100644 index 014f5f0c4..000000000 --- a/docs/i18n/ja-jp/docusaurus-theme-classic/navbar.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "title": { - "message": "Slack Developer Tools", - "description": "The title in the navbar" - }, - "item.label.SDKs": { - "message": "SDKs", - "description": "Navbar item with label SDKs" - }, - "item.label.Java": { - "message": "Java", - "description": "Navbar item with label Java" - }, - "item.label.JavaScript": { - "message": "JavaScript", - "description": "Navbar item with label JavaScript" - }, - "item.label.Python": { - "message": "Python", - "description": "Navbar item with label Python" - }, - "item.label.Community": { - "message": "Community", - "description": "Navbar item with label Community" - }, - "item.label.Bolt": { - "message": "Bolt", - "description": "Navbar item with label Bolt" - }, - "item.label.API Docs": { - "message": "API Docs", - "description": "Navbar item with label API Docs" - }, - "item.label.Java Slack SDK": { - "message": "Java Slack SDK", - "description": "Navbar item with label Java Slack SDK" - }, - "item.label.Node Slack SDK": { - "message": "Node Slack SDK", - "description": "Navbar item with label Node Slack SDK" - }, - "item.label.Python Slack SDK": { - "message": "Python Slack SDK", - "description": "Navbar item with label Python Slack SDK" - }, - "item.label.Deno Slack SDK": { - "message": "Deno Slack SDK", - "description": "Navbar item with label Deno Slack SDK" - }, - "item.label.Community tools": { - "message": "Community tools", - "description": "Navbar item with label Community tools" - }, - "item.label.Slack Community": { - "message": "Slack Community", - "description": "Navbar item with label Slack Community" - } -} \ No newline at end of file diff --git a/docs/img/announce.gif b/docs/img/announce.gif new file mode 100644 index 000000000..7602784ce Binary files /dev/null and b/docs/img/announce.gif differ diff --git a/docs/static/img/boltpy/basic-information-page.png b/docs/img/basic-information-page.png similarity index 100% rename from docs/static/img/boltpy/basic-information-page.png rename to docs/img/basic-information-page.png diff --git a/docs/static/img/boltpy/bot-token.png b/docs/img/bot-token.png similarity index 100% rename from docs/static/img/boltpy/bot-token.png rename to docs/img/bot-token.png diff --git a/docs/img/delivery-tracker-main.png b/docs/img/delivery-tracker-main.png new file mode 100644 index 000000000..b8d2e885c Binary files /dev/null and b/docs/img/delivery-tracker-main.png differ diff --git a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/basic/acknowledge.md b/docs/japanese/concepts/acknowledge.md similarity index 69% rename from docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/basic/acknowledge.md rename to docs/japanese/concepts/acknowledge.md index d180a966d..36ba6cba4 100644 --- a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/basic/acknowledge.md +++ b/docs/japanese/concepts/acknowledge.md @@ -1,22 +1,14 @@ ---- -title: リクエストの確認 -lang: ja-jp -slug: /concepts/acknowledge ---- - - +# リクエストの確認 アクション(action)、コマンド(command)、ショートカット(shortcut)、オプション(options)、およびモーダルからのデータ送信(view_submission)の各リクエストは、**必ず** `ack()` 関数を使って確認を行う必要があります。これによってリクエストが受信されたことが Slack に認識され、Slack のユーザーインターフェイスが適切に更新されます。 -リクエストの種類によっては、確認で通知方法が異なる場合があります。例えば、外部データソースを使用する選択メニューのオプションのリクエストに対する確認では、適切な[オプション](https://api.slack.com/reference/block-kit/composition-objects#option)のリストとともに `ack()` を呼び出します。モーダルからのデータ送信に対する確認では、 `response_action` を渡すことで[モーダルの更新](/concepts/view_submissions)などを行えます。 +リクエストの種類によっては、確認で通知方法が異なる場合があります。例えば、外部データソースを使用する選択メニューのオプションのリクエストに対する確認では、適切な[オプション](/reference/block-kit/composition-objects/option-object)のリストとともに `ack()` を呼び出します。モーダルからのデータ送信に対する確認では、 `response_action` を渡すことで[モーダルの更新](/tools/bolt-python/concepts/view-submissions)などを行えます。 確認までの猶予は 3 秒しかないため、新しいメッセージの送信やデータベースからの情報の取得といった時間のかかる処理は、`ack()` を呼び出した後で行うことをおすすめします。 - FaaS / serverless 環境を使う場合、 `ack()` するタイミングが異なります。 これに関する詳細は [Lazy listeners (FaaS)](/concepts/lazy-listeners) を参照してください。 - - + FaaS / serverless 環境を使う場合、 `ack()` するタイミングが異なります。 これに関する詳細は [Lazy listeners (FaaS)](/tools/bolt-python/concepts/lazy-listeners) を参照してください。 -指定可能な引数の一覧はモジュールドキュメントを参考にしてください。 +指定可能な引数の一覧は[モジュールドキュメント](https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html)を参考にしてください。 ```python # 外部データを使用する選択メニューオプションに応答するサンプル @app.options("menu_selection") diff --git a/docs/japanese/concepts/actions.md b/docs/japanese/concepts/actions.md new file mode 100644 index 000000000..8f1d1180e --- /dev/null +++ b/docs/japanese/concepts/actions.md @@ -0,0 +1,70 @@ +# アクション + +## アクションのリスニング + +Bolt アプリは `action` メソッドを用いて、ボタンのクリック、メニューの選択、メッセージショートカットなどのユーザーのアクションをリッスンすることができます。 + +アクションは `str` 型または `re.Pattern` 型の `action_id` でフィルタリングできます。`action_id` は、Slack プラットフォーム上のインタラクティブコンポーネントを区別する一意の識別子として機能します。 + +`action()` を使ったすべての例で `ack()` が使用されていることに注目してください。アクションのリスナー内では、Slack からのリクエストを受信したことを確認するために、`ack()` 関数を呼び出す必要があります。これについては、[リクエストの確認](/tools/bolt-python/concepts/acknowledge)セクションで説明しています。 + +指定可能な引数の一覧は[モジュールドキュメント](https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html)を参考にしてください。 + +```python +# 'approve_button' という action_id のブロックエレメントがトリガーされるたびに、このリスナーが呼び出させれる +@app.action("approve_button") +def update_message(ack): + ack() + # アクションへの反応としてメッセージを更新 +``` + +### 制約付きオブジェクトを使用したアクションのリスニング + +制約付きのオブジェクトを使用すると、`block_id` と `action_id` をそれぞれ、または任意に組み合わせてリッスンできます。オブジェクト内の制約は、`str` 型または `re.Pattern` 型で指定できます。 + +```python +# この関数は、block_id が 'assign_ticket' に一致し +# かつ action_id が 'select_user' に一致する場合にのみ呼び出される +@app.action({ + "block_id": "assign_ticket", + "action_id": "select_user" +}) +def update_message(ack, body, client): + ack() + + if "container" in body and "message_ts" in body["container"]: + client.reactions_add( + name="white_check_mark", + channel=body["channel"]["id"], + timestamp=body["container"]["message_ts"], + ) +``` + +## アクションへの応答 + +アクションへの応答には、主に 2 つの方法があります。1 つ目の最も一般的なやり方は `say()` を使用する方法です。そのリクエストが発生した会話(チャンネルや DM)にメッセージを返します。 + +2 つ目は、`respond()` を使用する方法です。これは、アクションに関連づけられた `response_url` を使ったメッセージ送信を行うためのユーティリティです。 + +指定可能な引数の一覧は[モジュールドキュメント](https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html)を参考にしてください。 + +```python +# 'approve_button' という action_id のインタラクティブコンポーネントがトリガーされると、このリスナーが呼ばれる +@app.action("approve_button") +def approve_request(ack, say): + # アクションのリクエストを確認 + ack() + say("Request approved 👍") +``` + +### respond() の利用 + +`respond()` は `response_url` を使って送信するときに便利なメソッドで、これらと同じような動作をします。投稿するメッセージのペイロードには、全ての[メッセージペイロードのプロパティ](/messaging/#payloads)とオプションのプロパティとして `response_type`(値は `"in_channel"` または `"ephemeral"`)、`replace_original`、`delete_original`、`unfurl_links`、`unfurl_media` などを指定できます。こうすることによってアプリから送信されるメッセージは、やり取りの発生元に反映されます。 + +```python +# 'user_select' という action_id を持つアクションのトリガーをリッスン +@app.action("user_select") +def select_user(ack, action, respond): + ack() + respond(f"You selected <@{action['selected_user']}>") +``` diff --git a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/advanced/adapters.md b/docs/japanese/concepts/adapters.md similarity index 53% rename from docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/advanced/adapters.md rename to docs/japanese/concepts/adapters.md index 4dfd0f9a1..6ed804c26 100644 --- a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/advanced/adapters.md +++ b/docs/japanese/concepts/adapters.md @@ -1,17 +1,12 @@ ---- -title: アダプター -lang: ja-jp -slug: /concepts/adapters ---- +# アダプター -アダプターは Slack から届く受信リクエストの受付とパーズを担当し、それらのリクエストを `BoltRequest` の形式に変換して Bolt アプリに引き渡します。 +アダプターは Slack から届く受信リクエストの受付とパーズを担当し、それらのリクエストを [`BoltRequest`](https://github.com/slackapi/bolt-python/blob/main/slack_bolt/request/request.py) の形式に変換して Bolt アプリに引き渡します。 -デフォルトでは、Bolt の組み込みの `HTTPServer` アダプターが使われます。このアダプターは、ローカルで開発するのには問題がありませんが、本番環境での利用は推奨されていません。Bolt for Python には複数の組み込みのアダプターが用意されており、必要に応じてインポートしてアプリで使用することができます。組み込みのアダプターは Flask、Django、Starlette をはじめとする様々な人気の Python フレームワークをサポートしています。これらのアダプターは、あなたが選択した本番環境で利用可能な Webサーバーとともに利用することができます。 +デフォルトでは、Bolt の組み込みの [`HTTPServer`](https://docs.python.org/3/library/http.server.html) アダプターが使われます。このアダプターは、ローカルで開発するのには問題がありませんが、**本番環境での利用は推奨されていません**。Bolt for Python には複数の組み込みのアダプターが用意されており、必要に応じてインポートしてアプリで使用することができます。組み込みのアダプターは Flask、Django、Starlette をはじめとする様々な人気の Python フレームワークをサポートしています。これらのアダプターは、あなたが選択した本番環境で利用可能な Webサーバーとともに利用することができます。 アダプターを使用するには、任意のフレームワークを使ってアプリを開発し、そのコードに対応するアダプターをインポートします。その後、アダプターのインスタンスを初期化して、受信リクエストの受付とパーズを行う関数を呼び出します。 -すべてのアダプターの一覧と、設定や使い方のサンプルは、リポジトリの `examples` フォルダをご覧ください。 - +すべてのアダプターの一覧と、設定や使い方のサンプルは、リポジトリの [`examples` フォルダ](https://github.com/slackapi/bolt-python/tree/main/examples)をご覧ください。 ```python from slack_bolt import App diff --git a/docs/japanese/concepts/app-home.md b/docs/japanese/concepts/app-home.md new file mode 100644 index 000000000..d950221ad --- /dev/null +++ b/docs/japanese/concepts/app-home.md @@ -0,0 +1,40 @@ +# ホームタブの更新 + +[ホームタブ](/surfaces/app-home)は、サイドバーや検索画面からアクセス可能なサーフェスエリアです。アプリはこのエリアを使ってユーザーごとのビューを表示することができます。アプリ設定ページで App Home の機能を有効にすると、[`views.publish`](/reference/methods/views.publish) API メソッドの呼び出しで `user_id` と[ビューのペイロード](/reference/interaction-payloads/view-interactions-payload/#view_submission)を指定して、ホームタブを公開・更新することができるようになります。 + +[`app_home_opened`](/reference/events/app_home_opened) イベントをサブスクライブすると、ユーザーが App Home を開く操作をリッスンできます。 + +指定可能な引数の一覧は [モジュールドキュメント](https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html)を参考にしてください。 + +```python +@app.event("app_home_opened") +def update_home_tab(client, event, logger): + try: + # 組み込みのクライアントを使って views.publish を呼び出す + client.views_publish( + # イベントに関連づけられたユーザー ID を使用 + user_id=event["user"], + # アプリの設定で予めホームタブが有効になっている必要がある + view={ + "type": "home", + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*Welcome home, <@" + event["user"] + "> :house:*" + } + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text":"Learn how home tabs can be more useful and interactive ." + } + } + ] + } + ) + except Exception as e: + logger.error(f"Error publishing home tab: {e}") +``` \ No newline at end of file diff --git a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/advanced/async.md b/docs/japanese/concepts/async.md similarity index 80% rename from docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/advanced/async.md rename to docs/japanese/concepts/async.md index 7ea6c7dc9..6687dcff5 100644 --- a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/advanced/async.md +++ b/docs/japanese/concepts/async.md @@ -1,13 +1,8 @@ ---- -title: Async(asyncio)の使用 -lang: ja-jp -slug: /concepts/async ---- +# Async(asyncio)の使用 -非同期バージョンの Bolt を使用する場合は、`App` の代わりに `AsyncApp` インスタンスをインポートして初期化します。`AsyncApp` では AIOHTTP を使って API リクエストを行うため、`aiohttp` をインストールする必要があります(`requirements.txt` に追記するか、`pip install aiohttp` を実行します)。 - -非同期バージョンのプロジェクトのサンプルは、リポジトリの `examples` フォルダにあります。 +非同期バージョンの Bolt を使用する場合は、`App` の代わりに `AsyncApp` インスタンスをインポートして初期化します。`AsyncApp` では [AIOHTTP](https://docs.aiohttp.org/) を使って API リクエストを行うため、`aiohttp` をインストールする必要があります(`requirements.txt` に追記するか、`pip install aiohttp` を実行します)。 +非同期バージョンのプロジェクトのサンプルは、リポジトリの [`examples` フォルダ](https://github.com/slackapi/bolt-python/tree/main/examples)にあります。 ```python # aiohttp のインストールが必要です @@ -27,11 +22,7 @@ if __name__ == "__main__": app.start(3000) ``` -

- ->他のフレームワークを使用する - - +## 他のフレームワークを使用する `AsyncApp#start()` では内部的に [`AIOHTTP`](https://docs.aiohttp.org/) のWebサーバーが実装されています。必要に応じて、受信リクエストの処理に `AIOHTTP` 以外のフレームワークを使用することができます。 @@ -46,7 +37,6 @@ pip install slack_bolt sanic uvicorn uvicorn async_app:api --reload --port 3000 --log-level debug ``` - ```python from slack_bolt.async_app import AsyncApp app = AsyncApp() @@ -74,5 +64,4 @@ async def endpoint(req: Request): if __name__ == "__main__": api.run(host="0.0.0.0", port=int(os.environ.get("PORT", 3000))) -``` -
\ No newline at end of file +``` \ No newline at end of file diff --git a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/basic/authenticating-oauth.md b/docs/japanese/concepts/authenticating-oauth.md similarity index 87% rename from docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/basic/authenticating-oauth.md rename to docs/japanese/concepts/authenticating-oauth.md index b1478ee3b..e09443d64 100644 --- a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/basic/authenticating-oauth.md +++ b/docs/japanese/concepts/authenticating-oauth.md @@ -1,18 +1,14 @@ ---- -title: OAuth を使った認証 -lang: ja-jp -slug: /concepts/authenticating-oauth ---- +# OAuth を使った認証 -Slack アプリを複数のワークスペースにインストールできるようにするためには、OAuth フローを実装した上で、アクセストークンなどのインストールに関する情報をセキュアな方法で保存する必要があります。アプリを初期化する際に `client_id`、`client_secret`、`scopes`、`installation_store`、`state_store` を指定することで、OAuth のエンドポイントのルート情報や stateパラメーターの検証をBolt for Python にハンドリングさせることができます。カスタムのアダプターを実装する場合は、SDK が提供する組み込みの[OAuth ライブラリ](https://tools.slack.dev/python-slack-sdk/oauth/)を利用するのが便利です。これは Slack が開発したモジュールで、Bolt for Python 内部でも利用しています。 +Slack アプリを複数のワークスペースにインストールできるようにするためには、OAuth フローを実装した上で、アクセストークンなどのインストールに関する情報をセキュアな方法で保存する必要があります。アプリを初期化する際に `client_id`、`client_secret`、`scopes`、`installation_store`、`state_store` を指定することで、OAuth のエンドポイントのルート情報や stateパラメーターの検証をBolt for Python にハンドリングさせることができます。カスタムのアダプターを実装する場合は、SDK が提供する組み込みの[OAuth ライブラリ](/tools/python-slack-sdk/oauth/)を利用するのが便利です。これは Slack が開発したモジュールで、Bolt for Python 内部でも利用しています。 Bolt for Python によって `slack/oauth_redirect` という**リダイレクト URL** が生成されます。Slack はアプリのインストールフローを完了させたユーザーをこの URL にリダイレクトします。この**リダイレクト URL** は、アプリの設定の「**OAuth and Permissions**」であらかじめ追加しておく必要があります。この URL は、後ほど説明するように `OAuthSettings` というコンストラクタの引数で指定することもできます。 Bolt for Python は `slack/install` というルートも生成します。これはアプリを直接インストールするための「**Add to Slack**」ボタンを表示するために使われます。すでにワークスペースへのアプリのインストールが済んでいる場合に追加で各ユーザーのユーザートークンなどの情報を取得する場合や、カスタムのインストール用の URL を動的に生成したい場合などは、`oauth_settings` の `authorize_url_generator` でカスタムの URL ジェネレーターを指定することができます。 -バージョン 1.1.0 以降の Bolt for Python では、[OrG 全体へのインストール](https://api.slack.com/enterprise/apps)がデフォルトでサポートされています。OrG 全体へのインストールは、アプリの設定の「**Org Level Apps**」で有効化できます。 +バージョン 1.1.0 以降の Bolt for Python では、[OrG 全体へのインストール](/enterprise)がデフォルトでサポートされています。OrG 全体へのインストールは、アプリの設定の「**Org Level Apps**」で有効化できます。 -Slack での OAuth を使ったインストールフローについて詳しくは、[API ドキュメントを参照してください](https://api.slack.com/authentication/oauth-v2)。 +Slack での OAuth を使ったインストールフローについて詳しくは、[API ドキュメントを参照してください](/authentication/installing-with-oauth)。 ```python import os @@ -35,10 +31,7 @@ app = App( ) ``` -
- -OAuth デフォルト設定をカスタマイズ - +## OAuth デフォルト設定をカスタマイズ `oauth_settings` を使って OAuth モジュールのデフォルト設定を上書きすることができます。このカスタマイズされた設定は App の初期化時に渡します。以下の情報を変更可能です: @@ -90,6 +83,4 @@ app = App( callback_options=callback_options, ), ) -``` - -
\ No newline at end of file +``` \ No newline at end of file diff --git a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/advanced/authorization.md b/docs/japanese/concepts/authorization.md similarity index 94% rename from docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/advanced/authorization.md rename to docs/japanese/concepts/authorization.md index 1a8797bb5..b6a14b30a 100644 --- a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/advanced/authorization.md +++ b/docs/japanese/concepts/authorization.md @@ -1,13 +1,9 @@ ---- -title: 認可(Authorization) -lang: ja-jp -slug: /concepts/authorization ---- +# 認可(Authorization) 認可(Authorization)は、Slack からの受信リクエストを処理するにあたって、どのようなSlack クレデンシャル (ボットトークンなど) を使用可能にするかを決定するプロセスです。 -単一のワークスペースにインストールされるアプリでは、`token` パラメーターを使って `App` のコンストラクターにボットトークンを渡すという、シンプルな方法が使えます。それに対して、複数のワークスペースにインストールされるアプリでは、次の 2 つの方法のいずれかを使用する必要があります。簡単なのは、組み込みの OAuth サポートを使用する方法です。OAuth サポートは、OAuth フロー用のURLのセットアップとstateの検証を行います。詳細は「[OAuth を使った認証](/concepts/authenticating-oauth)」セクションを参照してください。 +単一のワークスペースにインストールされるアプリでは、`token` パラメーターを使って `App` のコンストラクターにボットトークンを渡すという、シンプルな方法が使えます。それに対して、複数のワークスペースにインストールされるアプリでは、次の 2 つの方法のいずれかを使用する必要があります。簡単なのは、組み込みの OAuth サポートを使用する方法です。OAuth サポートは、OAuth フロー用のURLのセットアップとstateの検証を行います。詳細は「[OAuth を使った認証](/tools/bolt-python/concepts/authenticating-oauth)」セクションを参照してください。 よりカスタマイズできる方法として、`App` をインスタンス化する関数に`authorize` パラメーターを指定する方法があります。`authorize` 関数から返される [`AuthorizeResult` のインスタンス](https://github.com/slackapi/bolt-python/blob/main/slack_bolt/authorization/authorize_result.py)には、どのユーザーがどこで発生させたリクエストかを示す情報が含まれます。 diff --git a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/basic/commands.md b/docs/japanese/concepts/commands.md similarity index 72% rename from docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/basic/commands.md rename to docs/japanese/concepts/commands.md index 73d262446..ebb43c4d3 100644 --- a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/basic/commands.md +++ b/docs/japanese/concepts/commands.md @@ -1,18 +1,14 @@ ---- -title: コマンドのリスニングと応答 -lang: ja-jp -slug: /concepts/commands ---- +# コマンドのリスニングと応答 スラッシュコマンドが実行されたリクエストをリッスンするには、`command()` メソッドを使用します。このメソッドでは `str` 型の `command_name` の指定が必要です。 コマンドリクエストをアプリが受信し確認したことを Slack に通知するため、`ack()` を呼び出す必要があります。 -スラッシュコマンドに応答する方法は 2 つあります。1 つ目は `say()` を使う方法で、文字列または JSON のペイロードを渡すことができます。2 つ目は `respond()` を使う方法です。これは `response_url` がある場合に活躍します。これらの方法は[アクションへの応答](/concepts/action-respond)セクションで詳しく説明しています。 +スラッシュコマンドに応答する方法は 2 つあります。1 つ目は `say()` を使う方法で、文字列または JSON のペイロードを渡すことができます。2 つ目は `respond()` を使う方法です。これは `response_url` がある場合に活躍します。これらの方法は[アクションへの応答](/tools/bolt-python/concepts/actions)セクションで詳しく説明しています。 アプリの設定でコマンドを登録するときは、リクエスト URL の末尾に `/slack/events` をつけます。 -指定可能な引数の一覧はモジュールドキュメントを参考にしてください。 +指定可能な引数の一覧は[モジュールドキュメント](https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html)を参考にしてください。 ```python # echoコマンドは受け取ったコマンドをそのまま返す @app.command("/echo") diff --git a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/advanced/context.md b/docs/japanese/concepts/context.md similarity index 96% rename from docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/advanced/context.md rename to docs/japanese/concepts/context.md index 6f4dd8257..13a287728 100644 --- a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/advanced/context.md +++ b/docs/japanese/concepts/context.md @@ -1,8 +1,4 @@ ---- -title: コンテキストの追加 -lang: ja-jp -slug: /concepts/context ---- +# コンテキストの追加 すべてのリスナーは `context` ディクショナリにアクセスできます。リスナーはこれを使ってリクエストの付加情報を得ることができます。受信リクエストに含まれる `user_id`、`team_id`、`channel_id`、`enterprise_id` などの情報は、Bolt によって自動的に設定されます。 diff --git a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/advanced/custom-adapters.md b/docs/japanese/concepts/custom-adapters.md similarity index 90% rename from docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/advanced/custom-adapters.md rename to docs/japanese/concepts/custom-adapters.md index 7d2e63a91..584893511 100644 --- a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/advanced/custom-adapters.md +++ b/docs/japanese/concepts/custom-adapters.md @@ -1,10 +1,6 @@ ---- -title: カスタムのアダプター -lang: ja-jp -slug: /concepts/custom-adapters ---- +# カスタムのアダプター -[アダプター](/concepts/adapters)はフレキシブルで、あなたが使用したいフレームワークに合わせた調整も可能です。アダプターでは、次の 2 つの要素が必須となっています。 +[アダプター](/tools/bolt-python/concepts/adapters)はフレキシブルで、あなたが使用したいフレームワークに合わせた調整も可能です。アダプターでは、次の 2 つの要素が必須となっています。 - `__init__(app:App)` : コンストラクター。Bolt の `App` のインスタンスを受け取り、保持します。 - `handle(req:Request)` : Slack からの受信リクエストを受け取り、解析を行う関数。通常は `handle()` という名前です。リクエストを [`BoltRequest`](https://github.com/slackapi/bolt-python/blob/main/slack_bolt/request/request.py) のインスタンスに合った形にして、保持している Bolt アプリに引き渡します。 @@ -22,7 +18,6 @@ slug: /concepts/custom-adapters カスタムのアダプターに関連した詳しいサンプルについては、[組み込みのアダプター](https://github.com/slackapi/bolt-python/tree/main/slack_bolt/adapter)の実装を参考にしてください。 - ```python # Flask で必要なパッケージをインポートします from flask import Request, Response, make_response diff --git a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/advanced/errors.md b/docs/japanese/concepts/errors.md similarity index 92% rename from docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/advanced/errors.md rename to docs/japanese/concepts/errors.md index 6d715c1d7..2e8e27f90 100644 --- a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/advanced/errors.md +++ b/docs/japanese/concepts/errors.md @@ -1,8 +1,4 @@ ---- -title: エラーの処理 -lang: ja-jp -slug: /concepts/errors ---- +# エラーの処理 リスナー内でエラーが発生した場合に try/except ブロックを使用して直接エラーを処理することができます。アプリに関連するエラーは、`BoltError` 型です。Slack API の呼び出しに関連するエラーは、`SlackApiError` 型となります。 diff --git a/docs/japanese/concepts/event-listening.md b/docs/japanese/concepts/event-listening.md new file mode 100644 index 000000000..7b21f1fa4 --- /dev/null +++ b/docs/japanese/concepts/event-listening.md @@ -0,0 +1,33 @@ +# イベントのリスニング + +`event()` メソッドを使うと、[Events API](/reference/events) の任意のイベントをリッスンできます。リッスンするイベントは、アプリの設定であらかじめサブスクライブしておく必要があります。これを利用することで、アプリがインストールされたワークスペースで何らかのイベント(例:ユーザーがメッセージにリアクションをつけた、ユーザーがチャンネルに参加した)が発生したときに、アプリに何らかのアクションを実行させることができます。 + +`event()` メソッドには `str` 型の `eventType` を指定する必要があります。 + +指定可能な引数の一覧は[モジュールドキュメント](https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html)を参考にしてください。 +```python +# ユーザーがワークスペースに参加した際に、自己紹介を促すメッセージを指定のチャンネルに送信 +@app.event("team_join") +def ask_for_introduction(event, say): + welcome_channel_id = "C12345" + user_id = event["user"] + text = f"Welcome to the team, <@{user_id}>! 🎉 You can introduce yourself in this channel." + say(text=text, channel=welcome_channel_id) +``` + +## メッセージのサブタイプのフィルタリング + +`message()` リスナーは `event("message")` と等価の機能を提供します。 + +`subtype` という追加のキーを指定して、イベントのサブタイプでフィルタリングすることもできます。よく使われるサブタイプには、`bot_message` や `message_replied` があります。詳しくは[メッセージイベントページ](/reference/events/message#subtypes)を参照してください。サブタイプなしのイベントだけにフィルターするために明に `None` を指定することもできます。 + +```python +# 変更されたすべてのメッセージに一致 +@app.event({ + "type": "message", + "subtype": "message_changed" +}) +def log_message_change(logger, event): + user, text = event["user"], event["text"] + logger.info(f"The user {user} changed the message to {text}") +``` \ No newline at end of file diff --git a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/advanced/global-middleware.md b/docs/japanese/concepts/global-middleware.md similarity index 81% rename from docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/advanced/global-middleware.md rename to docs/japanese/concepts/global-middleware.md index caace0621..01ca417ac 100644 --- a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/advanced/global-middleware.md +++ b/docs/japanese/concepts/global-middleware.md @@ -1,15 +1,10 @@ ---- -title: グローバルミドルウェア -lang: ja-jp -slug: /concepts/global-middleware -order: 8 ---- +# グローバルミドルウェア グローバルミドルウェアは、すべての受信リクエストに対して、リスナーミドルウェアが呼ばれる前に実行されるものです。ミドルウェア関数を `app.use()` に渡すことで、アプリにはグローバルミドルウェアをいくつでも追加できます。ミドルウェア関数で受け取れる引数はリスナー関数と同じものに加えて`next()` 関数があります。 グローバルミドルウェアでもリスナーミドルウェアでも、次のミドルウェアに実行チェーンの制御をリレーするために、`next()` を呼び出す必要があります。 -指定可能な引数の一覧はモジュールドキュメントを参考にしてください。 +指定可能な引数の一覧は[モジュールドキュメント](https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html)を参考にしてください。 ```python @app.use diff --git a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/advanced/lazy-listeners.md b/docs/japanese/concepts/lazy-listeners.md similarity index 97% rename from docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/advanced/lazy-listeners.md rename to docs/japanese/concepts/lazy-listeners.md index de5f538e8..029f61d99 100644 --- a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/advanced/lazy-listeners.md +++ b/docs/japanese/concepts/lazy-listeners.md @@ -1,8 +1,4 @@ ---- -title: Lazy リスナー(FaaS) -lang: ja-jp -slug: /concepts/lazy-listeners ---- +# Lazy リスナー(FaaS Lazy リスナー関数は、FaaS 環境への Slack アプリのデプロイを容易にする機能です。この機能は Bolt for Python でのみ利用可能で、他の Bolt フレームワークでこの機能に対応することは予定していません。 @@ -14,7 +10,6 @@ Lazy リスナー関数は、FaaS 環境への Slack アプリのデプロイを * `ack: Callable`: 3 秒以内での `ack()` メソッドの呼び出しを担当します。 * `lazy: List[Callable]` : リクエストに関する時間のかかる処理のハンドリングを担当します。Lazy 関数からは `ack()` にアクセスすることはできません。 - ```python def respond_to_slack_within_3_seconds(body, ack): text = body.get("text") @@ -36,11 +31,7 @@ app.command("/start-process")( ) ``` -
- -AWS Lambda を使用した例 - - +## AWS Lambda を使用した例 このサンプルは、[AWS Lambda](https://aws.amazon.com/lambda/) にコードをデプロイします。[`examples` フォルダ](https://github.com/slackapi/bolt-python/tree/main/examples/aws_lambda)にはほかにもサンプルが用意されています。 @@ -60,7 +51,6 @@ echo 'slack_bolt' > requirements.txt lambda deploy --config-file config.yaml --requirements requirements.txt ``` - ```python from slack_bolt import App from slack_bolt.adapter.aws_lambda import SlackRequestHandler @@ -107,5 +97,4 @@ def handler(event, context): } ] } -``` -
\ No newline at end of file +``` \ No newline at end of file diff --git a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/advanced/listener-middleware.md b/docs/japanese/concepts/listener-middleware.md similarity index 70% rename from docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/advanced/listener-middleware.md rename to docs/japanese/concepts/listener-middleware.md index 822b5ac63..425ae4ea7 100644 --- a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/advanced/listener-middleware.md +++ b/docs/japanese/concepts/listener-middleware.md @@ -1,21 +1,16 @@ ---- -title: リスナーミドルウェア -lang: ja-jp -slug: /concepts/listener-middleware ---- +# リスナーミドルウェア リスナーミドルウェアは、それを渡したリスナーでのみ実行されるミドルウェアです。リスナーには、`middleware` パラメーターを使ってミドルウェア関数をいくつでも渡すことができます。このパラメーターには、1 つまたは複数のミドルウェア関数からなるリストを指定します。 非常にシンプルなリスナーミドルウェアの場合であれば、`next()` メソッドを呼び出す代わりに `bool` 値(処理を継続したい場合は `True`)を返すだけで済む「リスナーマッチャー」を使うとよいでしょう。 -指定可能な引数の一覧はモジュールドキュメントを参考にしてください。 +指定可能な引数の一覧は[モジュールドキュメント](https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html)を参考にしてください。 ```python -# "bot_message" サブタイプのメッセージを抽出するリスナーミドルウェア +# ボットからのメッセージをフィルタリングするリスナーミドルウェア def no_bot_messages(message, next): - subtype = message.get("subtype") - if subtype != "bot_message": - next() + if "bot_id" not in message: + next() # このリスナーは人間によって送信されたメッセージのみを受け取ります @app.event(event="message", middleware=[no_bot_messages]) @@ -24,13 +19,13 @@ def log_message(logger, event): # リスナーマッチャー: 簡略化されたバージョンのリスナーミドルウェア def no_bot_messages(message) -> bool: - return message.get("subtype") != "bot_message" + return "bot_id" not in message @app.event( - event="message", + event="message", matchers=[no_bot_messages] # or matchers=[lambda message: message.get("subtype") != "bot_message"] ) def log_message(logger, event): logger.info(f"(MSG) User: {event['user']}\nMessage: {event['text']}") -``` \ No newline at end of file +``` diff --git a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/advanced/logging.md b/docs/japanese/concepts/logging.md similarity index 95% rename from docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/advanced/logging.md rename to docs/japanese/concepts/logging.md index 22223fb08..3afa46539 100644 --- a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/advanced/logging.md +++ b/docs/japanese/concepts/logging.md @@ -1,8 +1,4 @@ ---- -title: ロギング -lang: ja-jp -slug: /concepts/logging ---- +# ロギング デフォルトでは、アプリからのログ情報は、既定の出力先に出力されます。`logging` モジュールをインポートすれば、`basicConfig()` の `level` パラメーターでrootのログレベルを変更することができます。指定できるログレベルは、重要度の低い方から `debug`、`info`、`warning`、`error`、および `critical` です。 diff --git a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/basic/message-listening.md b/docs/japanese/concepts/message-listening.md similarity index 51% rename from docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/basic/message-listening.md rename to docs/japanese/concepts/message-listening.md index a30620abd..dae729b51 100644 --- a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/basic/message-listening.md +++ b/docs/japanese/concepts/message-listening.md @@ -1,14 +1,10 @@ ---- -title: メッセージのリスニング -lang: ja-jp -slug: /concepts/message-listening ---- +# メッセージのリスニング -[あなたのアプリがアクセス権限を持つ](https://api.slack.com/messaging/retrieving#permissions)メッセージの投稿イベントをリッスンするには `message()` メソッドを利用します。このメソッドは `type` が `message` ではないイベントを処理対象から除外します。 +[あなたのアプリがアクセス権限を持つ](/messaging/retrieving-messages)メッセージの投稿イベントをリッスンするには `message()` メソッドを利用します。このメソッドは `type` が `message` ではないイベントを処理対象から除外します。 `message()` の引数には `str` 型または `re.Pattern` オブジェクトを指定できます。この条件のパターンに一致しないメッセージは除外されます。 -指定可能な引数の一覧はモジュールドキュメントを参考にしてください。 +指定可能な引数の一覧は[モジュールドキュメント](https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html)を参考にしてください。 ```python # '👋' が含まれるすべてのメッセージに一致 @app.message(":wave:") @@ -17,10 +13,7 @@ def say_hello(message, say): say(f"Hi there, <@{user}>!") ``` -
- -正規表現パターンの利用 - +## 正規表現パターンの利用 文字列の代わりに `re.compile()` メソッドを使用すれば、より細やかな条件指定ができます。 @@ -32,5 +25,4 @@ def say_hello_regex(say, context): # 正規表現のマッチ結果は context.matches に設定される greeting = context['matches'][0] say(f"{greeting}, how are you?") -``` -
\ No newline at end of file +``` \ No newline at end of file diff --git a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/basic/message-sending.md b/docs/japanese/concepts/message-sending.md similarity index 71% rename from docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/basic/message-sending.md rename to docs/japanese/concepts/message-sending.md index 8b5c9e7e5..ace67051b 100644 --- a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/basic/message-sending.md +++ b/docs/japanese/concepts/message-sending.md @@ -1,28 +1,22 @@ ---- -title: メッセージの送信 -lang: ja-jp -slug: /concepts/message-sending ---- +# メッセージの送信 リスナー関数内では、関連づけられた会話(例:リスナー実行のトリガーとなったイベントまたはアクションの発生元の会話)がある場合はいつでも `say()` を使用できます。`say()` には文字列または JSON ペイロードを指定できます。文字列の場合、送信できるのはテキストベースの単純なメッセージです。より複雑なメッセージを送信するには JSON ペイロードを指定します。指定したメッセージのペイロードは、関連づけられた会話内のメッセージとして送信されます。 -リスナー関数の外でメッセージを送信したい場合や、より高度な処理(特定のエラーの処理など)を実行したい場合は、[Bolt インスタンスにアタッチされたクライアント](/concepts/web-api)の `client.chat_postMessage` を呼び出します。 +リスナー関数の外でメッセージを送信したい場合や、より高度な処理(特定のエラーの処理など)を実行したい場合は、[Bolt インスタンスにアタッチされたクライアント](/tools/bolt-python/concepts/web-api)の `client.chat_postMessage` を呼び出します。 -指定可能な引数の一覧はモジュールドキュメントを参考にしてください。 +指定可能な引数の一覧は[モジュールドキュメント](https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html)を参考にしてください。 ```python # 'knock knock' が含まれるメッセージをリッスンし、イタリック体で 'Who's there?' と返信 @app.message("knock knock") def ask_who(message, say): say("_Who's there?_") ``` -
- -ブロックを用いたメッセージの送信 - + +## ブロックを用いたメッセージの送信 `say()` は、より複雑なメッセージペイロードを受け付けるので、メッセージに機能やリッチな構造を与えることが容易です。 -リッチなメッセージレイアウトをアプリに追加する方法については、[API サイトのガイド](https://api.slack.com/messaging/composing/layouts)を参照してください。また、[Block Kit ビルダー](https://api.slack.com/tools/block-kit-builder?template=1)の一般的なアプリフローのテンプレートも見てみてください。 +リッチなメッセージレイアウトをアプリに追加する方法については、[API サイトのガイド](/messaging/#structure)を参照してください。また、[Block Kit ビルダー](https://api.slack.com/tools/block-kit-builder?template=1)の一般的なアプリフローのテンプレートも見てみてください。 ```python # ユーザーが 📅 のリアクションをつけたら、日付ピッカーのついた section ブロックを送信 @@ -44,5 +38,4 @@ def show_datepicker(event, say): blocks=blocks, text="Pick a date for me to remind you" ) -``` -
\ No newline at end of file +``` \ No newline at end of file diff --git a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/basic/opening-modals.md b/docs/japanese/concepts/opening-modals.md similarity index 63% rename from docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/basic/opening-modals.md rename to docs/japanese/concepts/opening-modals.md index f2bc654a7..65342afb1 100644 --- a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/basic/opening-modals.md +++ b/docs/japanese/concepts/opening-modals.md @@ -1,16 +1,12 @@ ---- -title: モーダルの開始 -lang: ja-jp -slug: /concepts/opening-modals ---- +# モーダルの開始 -モーダルは、ユーザーからのデータの入力を受け付けたり、動的な情報を表示したりするためのインターフェイスです。組み込みの APIクライアントの `views.open` メソッドに、有効な `trigger_id` とビューのペイロードを指定してモーダルを開始します。 +[モーダル](/surfaces/modals)は、ユーザーからのデータの入力を受け付けたり、動的な情報を表示したりするためのインターフェイスです。組み込みの APIクライアントの [`views.open`](/reference/methods/views.open/) メソッドに、有効な `trigger_id` と[ビューのペイロード](/reference/interaction-payloads/view-interactions-payload/#view_submission)を指定してモーダルを開始します。 ショートカットの実行、ボタンを押下、選択メニューの操作などの操作の場合、Request URL に送信されるペイロードには `trigger_id` が含まれます。 -モーダルの生成方法についての詳細は、API ドキュメントを参照してください。 +モーダルの生成方法についての詳細は、[API ドキュメント](/surfaces/modals#composing_views)を参照してください。 -指定可能な引数の一覧はモジュールドキュメントを参考にしてください。 +指定可能な引数の一覧は[モジュールドキュメント](https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html)を参考にしてください。 ```python # ショートカットの呼び出しをリッスン diff --git a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/basic/options.md b/docs/japanese/concepts/select-menu-options.md similarity index 69% rename from docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/basic/options.md rename to docs/japanese/concepts/select-menu-options.md index 4838b2a75..4f3a5f357 100644 --- a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/basic/options.md +++ b/docs/japanese/concepts/select-menu-options.md @@ -1,20 +1,16 @@ ---- -title: オプションのリスニングと応答 -lang: ja-jp -slug: /concepts/options ---- +# オプションのリスニングと応答 -`options()` メソッドは、Slack からのオプション(セレクトメニュー内の動的な選択肢)をリクエストするペイロードをリッスンします。 [`action()` と同様に](/concepts/action-listening)、文字列型の `action_id` または制約付きオブジェクトが必要です。 +`options()` メソッドは、Slack からのオプション(セレクトメニュー内の動的な選択肢)をリクエストするペイロードをリッスンします。 [`action()` と同様に](/tools/bolt-python/concepts/actions)、文字列型の `action_id` または制約付きオブジェクトが必要です。 -外部データソースを使って選択メニューをロードするためには、末部に `/slack/events` が付加された URL を Options Load URL として予め設定しておく必要があります。 +外部データソースを使って選択メニューをロードするためには、末部に `/slack/events` が付加された URL を Options Load URL として予め設定しておく必要があります。 `external_select` メニューでは `action_id` を指定することをおすすめしています。ただし、ダイアログを利用している場合、ダイアログが Block Kit に対応していないため、`callback_id` をフィルタリングするための制約オブジェクトを使用する必要があります。 -オプションのリクエストに応答するときは、有効なオプションを含む `options` または `option_groups` のリストとともに `ack()` を呼び出す必要があります。API サイトにある[外部データを使用する選択メニューに応答するサンプル例](https://api.slack.com/reference/messaging/block-elements#external-select)と、[ダイアログでの応答例](https://api.slack.com/dialogs#dynamic_select_elements_external)を参考にしてください。 +オプションのリクエストに応答するときは、有効なオプションを含む `options` または `option_groups` のリストとともに `ack()` を呼び出す必要があります。API サイトにある[外部データを使用する選択メニューに応答するサンプル例](/reference/block-kit/block-elements/multi-select-menu-element#external_multi_select)と、[ダイアログでの応答例](/legacy/legacy-dialogs/#dynamic_select_elements_external)を参考にしてください。 -さらに、ユーザーが入力したキーワードに基づいたオプションを返すようフィルタリングロジックを適用することもできます。 これは `payload` という引数の ` value` の値に基づいて、それぞれのパターンで異なるオプションの一覧を返すように実装することができます。 Bolt for Python のすべてのリスナーやミドルウェアでは、[多くの有用な引数](https://tools.slack.dev/bolt-python/api-docs/slack_bolt/kwargs_injection/args.html)にアクセスすることができますので、チェックしてみてください。 +さらに、ユーザーが入力したキーワードに基づいたオプションを返すようフィルタリングロジックを適用することもできます。 これは `payload` という引数の ` value` の値に基づいて、それぞれのパターンで異なるオプションの一覧を返すように実装することができます。 Bolt for Python のすべてのリスナーやミドルウェアでは、[多くの有用な引数](https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html)にアクセスすることができますので、チェックしてみてください。 -指定可能な引数の一覧はモジュールドキュメントを参考にしてください。 +指定可能な引数の一覧は[モジュールドキュメント](https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html)を参考にしてください。 ```python # 外部データを使用する選択メニューオプションに応答するサンプル例 @app.options("external_action") @@ -33,4 +29,4 @@ def show_options(ack, payload): if keyword is not None and len(keyword) > 0: options = [o for o in options if keyword in o["text"]["text"]] ack(options=options) -``` \ No newline at end of file +``` diff --git a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/basic/shortcuts.md b/docs/japanese/concepts/shortcuts.md similarity index 75% rename from docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/basic/shortcuts.md rename to docs/japanese/concepts/shortcuts.md index 5824fbb65..39fb10ba8 100644 --- a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/basic/shortcuts.md +++ b/docs/japanese/concepts/shortcuts.md @@ -1,22 +1,18 @@ ---- -title: ショートカットのリスニングと応答 -lang: ja-jp -slug: /concepts/shortcuts ---- +# ショートカットのリスニングと応答 -`shortcut()` メソッドは、[グローバルショートカット](https://api.slack.com/interactivity/shortcuts/using#global_shortcuts)と[メッセージショートカット](https://api.slack.com/interactivity/shortcuts/using#message_shortcuts)の 2 つをサポートしています。 +`shortcut()` メソッドは、[グローバルショートカット](/interactivity/implementing-shortcuts#global)と[メッセージショートカット](/interactivity/implementing-shortcuts#messages)の 2 つをサポートしています。 ショートカットは、いつでも呼び出せるアプリのエントリーポイントを提供するものです。グローバルショートカットは Slack のテキスト入力エリアや検索ウィンドウからアクセスできます。メッセージショートカットはメッセージのコンテキストメニューからアクセスできます。アプリは、ショートカットリクエストをリッスンするために `shortcut()` メソッドを使用します。このメソッドには `str` 型または `re.Pattern` 型の `callback_id` パラメーターを指定します。 ショートカットリクエストがアプリによって確認されたことを Slack に伝えるため、`ack()` を呼び出す必要があります。 -ショートカットのペイロードには `trigger_id` が含まれます。アプリはこれを使って、ユーザーにやろうとしていることを確認するための[モーダルを開く](/concepts/opening-modals)ことができます。 +ショートカットのペイロードには `trigger_id` が含まれます。アプリはこれを使って、ユーザーにやろうとしていることを確認するための[モーダルを開く](/tools/bolt-python/concepts/opening-modals)ことができます。 アプリの設定でショートカットを登録する際は、他の URL と同じように、リクエスト URL の末尾に `/slack/events` をつけます。 -⚠️ グローバルショートカットのペイロードにはチャンネル ID が **含まれません**。アプリでチャンネル ID を取得する必要がある場合は、モーダル内に [`conversations_select`](https://api.slack.com/reference/block-kit/block-elements#conversation_select) エレメントを配置します。メッセージショートカットにはチャンネル ID が含まれます。 +⚠️ グローバルショートカットのペイロードにはチャンネル ID が **含まれません**。アプリでチャンネル ID を取得する必要がある場合は、モーダル内に [`conversations_select`](/reference/block-kit/block-elements/multi-select-menu-element#conversation_multi_select) エレメントを配置します。メッセージショートカットにはチャンネル ID が含まれます。 -指定可能な引数の一覧はモジュールドキュメントを参考にしてください。 +指定可能な引数の一覧は[モジュールドキュメント](https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html)を参考にしてください。 ```python # 'open_modal' という callback_id のショートカットをリッスン @app.shortcut("open_modal") @@ -36,7 +32,7 @@ def open_modal(ack, shortcut, client): "type": "section", "text": { "type": "mrkdwn", - "text":"About the simplest modal you could conceive of :smile:\n\nMaybe or ." + "text":"About the simplest modal you could conceive of :smile:\n\nMaybe or ." } }, { @@ -53,13 +49,7 @@ def open_modal(ack, shortcut, client): ) ``` -
- - -制約付きオブジェクトを使用したショートカットのリスニング - - - +## 制約付きオブジェクトを使用したショートカットのリスニング 制約付きオブジェクトを使って `callback_id` や `type` によるリッスンできます。オブジェクト内の制約は `str` 型または `re.Pattern` オブジェクトを使用できます。 @@ -82,7 +72,7 @@ def open_modal(ack, shortcut, client): "type": "section", "text": { "type": "mrkdwn", - "text":"About the simplest modal you could conceive of :smile:\n\nMaybe or ." + "text":"About the simplest modal you could conceive of :smile:\n\nMaybe or ." } }, { @@ -97,5 +87,4 @@ def open_modal(ack, shortcut, client): ] } ) -``` -
\ No newline at end of file +``` \ No newline at end of file diff --git a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/basic/socket-mode.md b/docs/japanese/concepts/socket-mode.md similarity index 84% rename from docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/basic/socket-mode.md rename to docs/japanese/concepts/socket-mode.md index a4c486cbd..92922d2de 100644 --- a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/basic/socket-mode.md +++ b/docs/japanese/concepts/socket-mode.md @@ -1,10 +1,6 @@ ---- -title: ソケットモードの利用 -lang: ja-jp -slug: /concepts/socket-mode ---- +# ソケットモードの利用 -[ソケットモード](https://api.slack.com/apis/connections/socket)は、アプリに WebSocket での接続と、そのコネクション経由でのデータ受信を可能とします。Bolt for Python は、バージョン 1.2.0 からこれに対応しています。 +[ソケットモード](/apis/events-api/using-socket-mode)は、アプリに WebSocket での接続と、そのコネクション経由でのデータ受信を可能とします。Bolt for Python は、バージョン 1.2.0 からこれに対応しています。 ソケットモードでは、Slack からのペイロード送信を受け付けるエンドポイントをホストする HTTP サーバーを起動する代わりに WebSocket で Slack に接続し、そのコネクション経由でデータを受信します。ソケットモードを使う前に、アプリの管理画面でソケットモードの機能が有効になっていることを確認しておいてください。 @@ -36,15 +32,11 @@ if __name__ == "__main__": handler.start() ``` -
- -Async (asyncio) の利用 - +## Async (asyncio) の利用 aiohttp のような asyncio をベースとしたアダプターを使う場合、アプリケーション全体が asyncio の async/await プログラミングモデルで実装されている必要があります。`AsyncApp` を動作させるためには `AsyncSocketModeHandler` とその async なミドルウェアやリスナーを利用します。 -`AsyncApp` の使い方についての詳細は、[Async (asyncio) の利用](/concepts/async)や、関連する[サンプルコード例](https://github.com/slackapi/bolt-python/tree/main/examples)を参考にしてください。 - +`AsyncApp` の使い方についての詳細は、[Async (asyncio) の利用](/tools/bolt-python/concepts/async)や、関連する[サンプルコード例](https://github.com/slackapi/bolt-python/tree/main/examples)を参考にしてください。 ```python from slack_bolt.app.async_app import AsyncApp @@ -62,5 +54,4 @@ async def main(): if __name__ == "__main__": import asyncio asyncio.run(main()) -``` -
\ No newline at end of file +``` \ No newline at end of file diff --git a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/advanced/token-rotation.md b/docs/japanese/concepts/token-rotation.md similarity index 67% rename from docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/advanced/token-rotation.md rename to docs/japanese/concepts/token-rotation.md index 456083832..25a0c735b 100644 --- a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/advanced/token-rotation.md +++ b/docs/japanese/concepts/token-rotation.md @@ -1,13 +1,9 @@ ---- -title: トークンのローテーション -lang: ja-jp -slug: /concepts/token-rotation ---- +# トークンのローテーション Bolt for Python [v1.7.0](https://github.com/slackapi/bolt-python/releases/tag/v1.7.0) から、アクセストークンのさらなるセキュリティ強化のレイヤーであるトークンローテーションの機能に対応しています。トークンローテーションは [OAuth V2 の RFC](https://datatracker.ietf.org/doc/html/rfc6749#section-10.4) で規定されているものです。 既存の Slack アプリではアクセストークンが無期限に存在し続けるのに対して、トークンローテーションを有効にしたアプリではアクセストークンが失効するようになります。リフレッシュトークンを利用して、アクセストークンを長期間にわたって更新し続けることができます。 -[Bolt for Python の組み込みの OAuth 機能](/concepts/authenticating-oauth) を使用していれば、Bolt for Python が自動的にトークンローテーションの処理をハンドリングします。 +[Bolt for Python の組み込みの OAuth 機能](/tools/bolt-python/concepts/authenticating-oauth) を使用していれば、Bolt for Python が自動的にトークンローテーションの処理をハンドリングします。 -トークンローテーションに関する詳細は [API ドキュメント](https://api.slack.com/authentication/rotation)を参照してください。 \ No newline at end of file +トークンローテーションに関する詳細は [API ドキュメント](/authentication/using-token-rotation)を参照してください。 \ No newline at end of file diff --git a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/basic/updating-pushing-views.md b/docs/japanese/concepts/updating-pushing-views.md similarity index 60% rename from docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/basic/updating-pushing-views.md rename to docs/japanese/concepts/updating-pushing-views.md index 2948f978f..2bbaf5ae5 100644 --- a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/basic/updating-pushing-views.md +++ b/docs/japanese/concepts/updating-pushing-views.md @@ -1,10 +1,6 @@ ---- -title: モーダルの更新と多重表示 -lang: ja-jp -slug: /concepts/updating-pushing-views ---- +# モーダルの更新と多重表示 -モーダル内では、複数のモーダルをスタックのように重ねることができます。`views_open` という APIを呼び出すと、親となるとなるモーダルビューが追加されます。この最初の呼び出しの後、`views_update` を呼び出すことでそのビューを更新することができます。また、`views_push` を呼び出すと、親のモーダルの上にさらに新しいモーダルビューを重ねることもできます。 +モーダル内では、複数のモーダルをスタックのように重ねることができます。[`views_open`](/reference/methods/views.open/) という APIを呼び出すと、親となるとなるモーダルビューが追加されます。この最初の呼び出しの後、[`views_update`](/reference/methods/views.update/) を呼び出すことでそのビューを更新することができます。また、[`views_push`](/reference/methods/views.push) を呼び出すと、親のモーダルの上にさらに新しいモーダルビューを重ねることもできます。 **`views_update`** @@ -12,11 +8,11 @@ slug: /concepts/updating-pushing-views **`views_push`** -既存のモーダルの上に新しいモーダルをスタックのように追加する場合は、組み込みのクライアントで `views_push` API を呼び出します。この API 呼び出しでは、有効な `trigger_id` と新しいビューのペイロードを指定します。`views_push` の引数は モーダルの開始 と同じです。モーダルを開いた後、このモーダルのスタックに追加できるモーダルビューは 2 つまでです。 +既存のモーダルの上に新しいモーダルをスタックのように追加する場合は、組み込みのクライアントで `views_push` API を呼び出します。この API 呼び出しでは、有効な `trigger_id` と新しい[ビューのペイロード](/reference/interaction-payloads/view-interactions-payload/#view_submission)を指定します。`views_push` の引数は [モーダルの開始](#creating-modals) と同じです。モーダルを開いた後、このモーダルのスタックに追加できるモーダルビューは 2 つまでです。 -モーダルの更新と多重表示に関する詳細は、API ドキュメントを参照してください。 +モーダルの更新と多重表示に関する詳細は、[API ドキュメント](/surfaces/modals)を参照してください。 -指定可能な引数の一覧はモジュールドキュメントを参考にしてください。 +指定可能な引数の一覧は[モジュールドキュメント](https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html)を参考にしてください。 ```python # モーダルに含まれる、`button_abc` という action_id のボタンの呼び出しをリッスン diff --git a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/basic/view_submissions.md b/docs/japanese/concepts/view-submissions.md similarity index 72% rename from docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/basic/view_submissions.md rename to docs/japanese/concepts/view-submissions.md index 9e6d74058..f82922004 100644 --- a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/basic/view_submissions.md +++ b/docs/japanese/concepts/view-submissions.md @@ -1,10 +1,6 @@ ---- -title: モーダルの送信のリスニング -lang: ja-jp -slug: /concepts/view_submissions ---- +# モーダルの送信のリスニング -モーダルのペイロードに `input` ブロックを含める場合、その入力値を受け取るために`view_submission` リクエストをリッスンする必要があります。`view_submission` リクエストのリッスンには、組み込みの`view()` メソッドを利用することができます。`view()` の引数には、`str` 型または `re.Pattern` 型の `callback_id` を指定します。 +[モーダルのペイロード](/reference/interaction-payloads/view-interactions-payload/#view_submission)に `input` ブロックを含める場合、その入力値を受け取るために`view_submission` リクエストをリッスンする必要があります。`view_submission` リクエストのリッスンには、組み込みの`view()` メソッドを利用することができます。`view()` の引数には、`str` 型または `re.Pattern` 型の `callback_id` を指定します。 `input` ブロックの値にアクセスするには `state` オブジェクトを参照します。`state` 内には `values` というオブジェクトがあり、`block_id` と一意の `action_id` に紐づける形で入力値を保持しています。 @@ -23,9 +19,9 @@ def handle_submission(ack, body): # https://app.slack.com/block-kit-builder/#%7B%22type%22:%22modal%22,%22callback_id%22:%22view_1%22,%22title%22:%7B%22type%22:%22plain_text%22,%22text%22:%22My%20App%22,%22emoji%22:true%7D,%22blocks%22:%5B%5D%7D ack(response_action="update", view=build_new_view(body)) ``` -この例と同様に、モーダルでの送信リクエストに対して、エラーを表示するためのオプションもあります。 +この例と同様に、モーダルでの送信リクエストに対して、[エラーを表示する](/surfaces/modals#displaying_errors)ためのオプションもあります。 -モーダルの送信について詳しくは、API ドキュメントを参照してください。 +モーダルの送信について詳しくは、[API ドキュメント](/surfaces/modals#interactions)を参照してください。 --- @@ -33,7 +29,7 @@ def handle_submission(ack, body): `view_closed` リクエストをリッスンするためには `callback_id` を指定して、かつ `notify_on_close` 属性をモーダルのビューに設定する必要があります。以下のコード例をご覧ください。 -よく詳しい情報は、API ドキュメントを参照してください。 +よく詳しい情報は、[API ドキュメント](/surfaces/modals#interactions)を参照してください。 ```python client.views_open( @@ -60,7 +56,7 @@ def handle_view_closed(ack, body, logger): logger.info(body) ``` -指定可能な引数の一覧はモジュールドキュメントを参考にしてください。 +指定可能な引数の一覧は[モジュールドキュメント](https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html)を参考にしてください。 ```python # view_submission リクエストを処理 diff --git a/docs/japanese/concepts/web-api.md b/docs/japanese/concepts/web-api.md new file mode 100644 index 000000000..abb8e4121 --- /dev/null +++ b/docs/japanese/concepts/web-api.md @@ -0,0 +1,19 @@ +# Web API の使い方 + +`app.client`、またはミドルウェア・リスナーの引数 `client` として Bolt アプリに提供されている `WebClient` は必要な権限を付与されており、これを利用することで[あらゆる Web API メソッド](/reference/methods)を呼び出すことができます。このクライアントのメソッドを呼び出すと `SlackResponse` という Slack からの応答情報を含むオブジェクトが返されます。 + +Bolt の初期化に使用するトークンは `context` オブジェクトに設定されます。このトークンは、多くの Web API メソッドを呼び出す際に必要となります。 + +指定可能な引数の一覧は[モジュールドキュメント](https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html)を参考にしてください。 +```python +@app.message("wake me up") +def say_hello(client, message): + # 2020 年 9 月 30 日午後 11:59:59 を示す Unix エポック秒 + when_september_ends = 1601510399 + channel_id = message["channel"] + client.chat_scheduleMessage( + channel=channel_id, + post_at=when_september_ends, + text="Summer has come and passed" + ) +``` \ No newline at end of file diff --git a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/getting-started.md b/docs/japanese/getting-started.md similarity index 59% rename from docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/getting-started.md rename to docs/japanese/getting-started.md index 41aecf6ef..46ac55ffb 100644 --- a/docs/i18n/ja-jp/docusaurus-plugin-content-docs/current/getting-started.md +++ b/docs/japanese/getting-started.md @@ -1,9 +1,3 @@ ---- -title: Bolt 入門ガイド -slug: getting-started -lang: ja-jp ---- - # Bolt 入門ガイド このガイドでは、Bolt for Python を使った Slack アプリの設定と起動の方法について説明します。ここで説明する手順では、まず新しい Slack アプリを作成し、ローカルの開発環境をセットアップし、Slack ワークスペースからのメッセージをリッスンして応答するアプリを開発するという流れになります。 @@ -11,60 +5,50 @@ lang: ja-jp この手順を全て終わらせたら、あなたはきっと ⚡️[Slack アプリのはじめ方](https://github.com/slackapi/bolt-python/tree/main/examples/getting_started)のサンプルアプリを動作させたり、それに変更を加えたり、自分のアプリを作ったりすることができるようになるでしょう。 -:::tip - -このガイドでは[ソケットモード](https://api.slack.com/apis/connections/socket) を利用します。ソケットモードは、Slack アプリ開発をとりあえず始めてみるときやあなたのチームだけのためのアプリをつくるときにおすすめのやり方です。もしすでに HTTP をアプリのコミュニケーションプロトコルとするとわかっている場合は、HTTP の方式に対応した同様のドキュメントである [Bolt 入門ガイド(HTTP)](/tutorial/getting-started-http) を参照してください。 - -::: - --- ### アプリを作成する {#create-an-app} 最初にやるべきこと : Bolt での開発を始める前に、[Slack アプリを作成](https://api.slack.com/apps/new)します。 -:::tip +:::tip[通常の業務の妨げにならないよう、別の開発用のワークスペースを使用することをおすすめします。[新しいワークスペースは無料で作成できます](https://slack.com/get-started#create)] -通常の業務の妨げにならないよう、別の開発用のワークスペースを使用することをおすすめします。[新しいワークスペースは無料で作成できます](https://slack.com/get-started#create) - -:::。 +::: アプリ名を入力し(_後で変更可能_)、インストール先のワークスペースを選択して「`Create App`」ボタンをクリックすると、アプリの **Basic Information** ページが表示されます。 このページでは、アプリの概要や重要な認証情報を確認できます。これらの情報は後ほど参照します。 -![Basic Information ページ](/img/boltpy/basic-information-page.png "Basic Information ページ") +![Basic Information ページ](/img/bolt-python/basic-information-page.png "Basic Information ページ") ひと通り確認して、アプリのアイコンと説明を追加したら、アプリのプロジェクトの構成 🔩 を始めましょう。 --- ### トークンとアプリのインストール {#tokens-and-installing-apps} -Slack アプリでは、[Slack API へのアクセスの管理に OAuth を使用します](https://api.slack.com/docs/oauth)。アプリがインストールされると、トークンが発行されます。アプリはそのトークンを使って API メソッドを呼び出すことができます。 +Slack アプリでは、[Slack API へのアクセスの管理に OAuth を使用します](/authentication/installing-with-oauth)。アプリがインストールされると、トークンが発行されます。アプリはそのトークンを使って API メソッドを呼び出すことができます。 Slack アプリで使用できるトークンには、ユーザートークン(`xoxp`)とボットトークン(`xoxb`)、アプリレベルトークン(`xapp`)の 3 種類があります。 -- [ユーザートークン](https://api.slack.com/authentication/token-types#user) を使用すると、アプリをインストールまたは認証したユーザーに成り代わって API メソッドを呼び出すことができます。1 つのワークスペースに複数のユーザートークンが存在する可能性があります。 -- [ボットトークン](https://api.slack.com/authentication/token-types#bot) はボットユーザーに関連づけられ、1 つのワークスペースでは最初に誰かがそのアプリをインストールした際に一度だけ発行されます。どのユーザーがインストールを実行しても、アプリが使用するボットトークンは同じになります。_ほとんど_のアプリで使用されるのは、ボットトークンです。 -- [アプリレベルトークン](https://api.slack.com/authentication/token-types#app) は、全ての組織(とその配下のワークスペースでの個々のユーザーによるインストール)を横断して、あなたのアプリを代理するものです。アプリレベルトークンは、アプリの WebSocket コネクションを確立するためによく使われます。 +- [ユーザートークン](/authentication/tokens#user) を使用すると、アプリをインストールまたは認証したユーザーに成り代わって API メソッドを呼び出すことができます。1 つのワークスペースに複数のユーザートークンが存在する可能性があります。 +- [ボットトークン](/authentication/tokens#bot) はボットユーザーに関連づけられ、1 つのワークスペースでは最初に誰かがそのアプリをインストールした際に一度だけ発行されます。どのユーザーがインストールを実行しても、アプリが使用するボットトークンは同じになります。_ほとんど_のアプリで使用されるのは、ボットトークンです。 +- [アプリレベルトークン](/authentication/tokens#app-level) は、全ての組織(とその配下のワークスペースでの個々のユーザーによるインストール)を横断して、あなたのアプリを代理するものです。アプリレベルトークンは、アプリの WebSocket コネクションを確立するためによく使われます。 このガイドではボットトークンとアプリレベルトークンを使用します。 1. 左サイドバーの「**OAuth & Permissions**」をクリックし、「**Bot Token Scopes**」セクションまで下にスクロールします。「**Add an OAuth Scope**」をクリックします。 -2. ここでは [`chat:write`](https://api.slack.com/scopes/chat:write) というスコープのみを追加します。このスコープはアプリが参加しているチャンネルにメッセージを投稿することを許可します。 +2. ここでは [`chat:write`](/reference/scopes/chat.write) というスコープのみを追加します。このスコープはアプリが参加しているチャンネルにメッセージを投稿することを許可します。 3. OAuth & Permissions ページの一番上までスクロールし、「**Install App to Workspace**」をクリックします。Slack の OAuth 確認画面 が表示されます。この画面で開発用ワークスペースへのアプリのインストールを承認します。 4. インストールを承認すると **OAuth & Permissions** ページが表示され、**Bot User OAuth Access Token** を確認できるでしょう。 -![OAuth トークン](/img/boltpy/bot-token.png "ボット用 OAuth トークン") +![OAuth トークン](/img/bolt-python/bot-token.png "ボット用 OAuth トークン") 5. 次に「**Basic Informationのページ**」まで戻り、アプリレベルトークンのセクションまで下にスクロールし「**Generate Token and Scopes**」をクリックしてアプリレベルトークンを作成します。このトークンに `connections:write` のスコープを付与し、作成された `xapp` トークンを保存します。これらのトークンは後ほど利用します。 6. 左サイドメニューの「**Socket Mode**」を有効にします。 -:::tip - -トークンはパスワードと同様に取り扱い、[安全な方法で保管してください](https://api.slack.com/docs/oauth-safety)。アプリはこのトークンを使って Slack ワークスペースで投稿をしたり、情報の取得をしたりします。 +:::tip[トークンはパスワードと同様に取り扱い、[安全な方法で保管してください](/concepts/security)。アプリはこのトークンを使って Slack ワークスペースで投稿をしたり、情報の取得をしたりします。] ::: @@ -80,7 +64,7 @@ mkdir first-bolt-app cd first-bolt-app ``` -次に、プロジェクトの依存ライブラリを管理する方法として、[Python 仮想環境](https://packaging.python.org/guides/installing-using-pip-and-virtual-environments/#creating-a-virtual-environment)を使ったおすすめの方法を紹介します。これはシステム Python に存在するパッケージとのコンフリクトを防ぐために推奨されている優れた方法です。[Python 3.6 以降](https://www.python.org/downloads/)の仮想環境を作成し、アクティブにしてみましょう。 +次に、プロジェクトの依存ライブラリを管理する方法として、[Python 仮想環境](https://packaging.python.org/guides/installing-using-pip-and-virtual-environments/#creating-a-virtual-environment)を使ったおすすめの方法を紹介します。これはシステム Python に存在するパッケージとのコンフリクトを防ぐために推奨されている優れた方法です。[Python 3.7 以降](https://www.python.org/downloads/)の仮想環境を作成し、アクティブにしてみましょう。 ```shell python3 -m venv .venv @@ -105,9 +89,11 @@ export SLACK_BOT_TOKEN=xoxb-<ボットトークン> ```shell export SLACK_APP_TOKEN=<アプリレベルトークン> ``` +:::warning[🔒 全てのトークンは安全に保管してください。] -> 🔒 全てのトークンは安全に保管してください。少なくともパブリックなバージョン管理にチェックインするようなことは避けるべきでしょう。また、上にあった例のように環境変数を介してアクセスするようにしてください。詳細な情報は [アプリのセキュリティのベストプラクティス](https://api.slack.com/authentication/best-practices)のドキュメントを参照してください。 +少なくともパブリックなバージョン管理にチェックインするようなことは避けるべきでしょう。また、上にあった例のように環境変数を介してアクセスするようにしてください。詳細な情報は [アプリのセキュリティのベストプラクティス](/concepts/security)のドキュメントを参照してください。 +::: 完了したら、いよいよアプリを作っていきましょう。以下のコマンドを使って、仮想環境に Python の `slack_bolt` パッケージをインストールします。 @@ -143,23 +129,47 @@ python3 app.py ### イベントを設定する {#setting-up-events} アプリはワークスペース内の他のメンバーと同じように振る舞い、メッセージを投稿したり、絵文字リアクションを追加したり、イベントをリッスンして返答したりできます。 -Slack ワークスペースで発生するイベント(メッセージが投稿されたときや、メッセージに対するリアクションがつけられたときなど)をリッスンするには、[Events API を使って特定の種類のイベントをサブスクライブします](https://api.slack.com/events-api)。 +Slack ワークスペースで発生するイベント(メッセージが投稿されたときや、メッセージに対するリアクションがつけられたときなど)をリッスンするには、[Events API を使って特定の種類のイベントをサブスクライブします](/apis/events-api/)。 -:::tip +このチュートリアルの序盤でソケットモードを有効にしました。ソケットモードを使うことで、アプリが公開された HTTP エンドポイントを公開せずに Events API やインタラクティブコンポーネントを利用できるようになります。このことは、開発時やファイヤーウォールの裏からのリクエストを受ける際に便利です。HTTP での方式は、ホスティング環境にデプロイするアプリや Slack App Directory で配布されるアプリの開発・運用に適しています。 -このチュートリアルの序盤でソケットモードを有効にしました。ソケットモードを使うことで、アプリが公開された HTTP エンドポイントを公開せずに Events API やインタラクティブコンポーネントを利用できるようになります。このことは、開発時やファイヤーウォールの裏からのリクエストを受ける際に便利です。HTTP での方式は、ホスティング環境にデプロイするアプリや Slack App Directory で配布されるアプリの開発・運用に適しています。HTTP での情報については[こちらのドキュメント](/tutorial/getting-started-http)を参照してください。 +それでは、このアプリがどのイベントをリッスンしたいかを Slack に伝えましょう。 -::: +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; -それでは、このアプリがどのイベントをリッスンしたいかを Slack に伝えましょう。 + + + +1. アプリの構成ページに移動します ([アプリ設定ページから](https://api.slack.com/apps) アプリをクリックします)。左側のメニューで **ソケット モード** に移動し、有効に切り替えます。 + +2. [**基本情報**] に移動し、[アプリレベル トークン] セクションの下にスクロールして、[**トークンとスコープの生成**] をクリックしてアプリ トークンを生成します。このトークンに `connections:write` スコープを追加し、生成された `xapp` トークンを保存します。これはすぐに使用します。 + +3. 最後に、聞きたいイベントを Slack に伝えます。 **イベント サブスクリプション** で、**イベントを有効にする** というラベルのスイッチを切り替えます。 イベントが発生すると、そのイベントをトリガーしたユーザーやイベントが発生したチャンネルなど、イベントに関する情報が Slack からアプリに送信されます。アプリではこれらの情報を処理して、適切な応答を返します。 + + + +1. アプリ構成ページに戻ります ([アプリ管理ページから](https://api.slack.com/apps) アプリをクリックします)。左側のサイドバーで [**イベント サブスクリプション**] をクリックします。 **イベントを有効にする**というラベルの付いたスイッチを切り替えます。 + +2. リクエスト URL を追加します。 Slack は、イベントに対応する HTTP POST リクエストをこの [リクエスト URL](/apis/events-api/#subscribing) に送信します。 Bolt は、`/slack/events` パスを使用して、すべての受信リクエスト (ショートカット、イベント、対話性ペイロードなど) をリッスンします。アプリ構成内でリクエスト URL を構成する場合は、`/slack/events` を追加します。 「https://あなたのドメイン/slack/events」。 💡 Bolt アプリが実行されている限り、URL は検証されるはずです。 + +:::tip + +ローカル開発の場合、ngrok などのプロキシ サービスを使用してパブリック URL を作成し、リクエストを開発環境にトンネリングできます。このトンネルの作成方法については、[ngrok のスタート ガイド](https://ngrok.com/docs#getting-started-expose) を参照してください。アプリをホスティングする際には、Slack 開発者がアプリをホストするために使用する最も一般的なホスティング プロバイダーを [API サイト](/app-management/hosting-slack-apps) に集めました。 + +::: + + + + 左側のサイドバーから **Event Subscriptions** にアクセスして、機能を有効にしてください。 **Subscribe to Bot Events** 配下で、ボットが受け取れるイベントを追加することができます。4つのメッセージに関するイベントがあります。 -- [`message.channels`](https://api.slack.com/events/message.channels) アプリが参加しているパブリックチャンネルのメッセージをリッスン -- [`message.groups`](https://api.slack.com/events/message.groups) アプリが参加しているプライベートチャンネルのメッセージをリッスン -- [`message.im`](https://api.slack.com/events/message.im) あなたのアプリとユーザーのダイレクトメッセージをリッスン -- [`message.mpim`](https://api.slack.com/events/message.mpim) あなたのアプリが追加されているグループ DM をリッスン +- [`message.channels`](/reference/events/message.channels) アプリが参加しているパブリックチャンネルのメッセージをリッスン +- [`message.groups`](/reference/events/message.groups) アプリが参加しているプライベートチャンネルのメッセージをリッスン +- [`message.im`](/reference/events/message.im) あなたのアプリとユーザーのダイレクトメッセージをリッスン +- [`message.mpim`](/reference/events/message.mpim) あなたのアプリが追加されているグループ DM をリッスン ボットが参加するすべての場所のメッセージをリッスンさせるには、これら 4 つのメッセージイベントをすべて選択します。ボットにリッスンさせるメッセージイベントの種類を選択したら、「**Save Changes**」ボタンをクリックします。 @@ -170,6 +180,9 @@ Slack ワークスペースで発生するイベント(メッセージが投 次の例では、アプリが参加するチャンネルとダイレクトメッセージに投稿されるすべてのメッセージをリッスンし、「こんにちは」というメッセージに応答を返します。 + + + ```python import os from slack_bolt import App @@ -180,7 +193,7 @@ app = App(token=os.environ.get("SLACK_BOT_TOKEN")) # 'こんにちは' を含むメッセージをリッスンします # 指定可能なリスナーのメソッド引数の一覧は以下のモジュールドキュメントを参考にしてください: -# https://tools.slack.dev/bolt-python/api-docs/slack_bolt/kwargs_injection/args.html +# https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html @app.message("こんにちは") def message_hello(message, say): # イベントがトリガーされたチャンネルへ say() でメッセージを送信します @@ -191,6 +204,35 @@ if __name__ == "__main__": SocketModeHandler(app, os.environ["SLACK_APP_TOKEN"]).start() ``` + + + +```python +import os +from slack_bolt import App + +# ボットトークンと署名シークレットを使ってアプリを初期化します +app = App( + token=os.environ.get("SLACK_BOT_TOKEN"), + signing_secret=os.environ.get("SLACK_SIGNING_SECRET") +) + +# 'hello' を含むメッセージをリッスンします +# 指定可能なリスナーのメソッド引数の一覧は以下のモジュールドキュメントを参考にしてください: +# https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html +@app.message("hello") +def message_hello(message, say): + # イベントがトリガーされたチャンネルへ say() でメッセージを送信します + say(f"Hey there <@{message['user']}>!") + +# アプリを起動します +if __name__ == "__main__": + app.start(port=int(os.environ.get("PORT", 3000))) +``` + + + + アプリを再起動して、ボットユーザーが参加しているチャンネル・ダイレクトメッセージに「こんにちは」というメッセージを投稿すると、アプリが返答を返すでしょう。 これはごく基本的なコード例ですが、最終的にやりたいことを実現するためにアプリをカスタマイズしていく土台として利用できます。次は、シンプルなテキストの返答を送信する代わりにメッセージ内にボタンを表示するという、もう少しインタラクティブな動作を試してみましょう。 @@ -201,12 +243,25 @@ if __name__ == "__main__": インタラクティブ機能を有効にすると、ボタン、選択メニュー、日付ピッカー、モーダル、ショートカットなどの機能が利用できるようになります。アプリ設定ページの「**Interactivity & Shortcuts**」にアクセスしてください。 + + + +ソケット モードをオンにすると、基本的な対話機能がデフォルトで有効になるため、それ以上の操作は必要ありません。 + + + + +イベントと同様に、Slack がアクション (*ユーザーがボタンをクリックした* など) を送信するには、URL を指定する必要があります。アプリ構成ページに戻り、左側にある **対話性とショートカット** をクリックします。別の **リクエスト URL** ボックスがあることがわかります。 + :::tip ソケットモードを有効にしているとき、デフォルトで基本的なインタラクティブ機能が有効になっていることに気づくでしょう。追加のアクションは不要です。もし HTTP を使っている場合、Slack からのイベント送信先である Request URL を設定する必要があります。 ::: + + + インタラクティビティが有効化されていれば、ショートカット、モーダル、インタラクティブコンポーネント (例:ボタン、選択メニュー、日付ピッカー) とのインタラクションはイベントとしてあなたのアプリに送信されます。 それでは、アプリのコードに戻り、これらのイベントを処理する為のロジックを追加しましょう。 @@ -215,6 +270,9 @@ if __name__ == "__main__": 以下のコードの後の部分を編集し、文字列だけのメッセージの代わりに、ボタンを含んだメッセージを送信するようにしてみます。 + + + ```python import os from slack_bolt import App @@ -247,13 +305,53 @@ if __name__ == "__main__": SocketModeHandler(app, os.environ["SLACK_APP_TOKEN"]).start() ``` + + + +```python +import os +from slack_bolt import App + +# ボットトークンと署名シークレットを使ってアプリを初期化します +app = App( + token=os.environ.get("SLACK_BOT_TOKEN"), + signing_secret=os.environ.get("SLACK_SIGNING_SECRET") +) + +# 'hello' を含むメッセージをリッスンします +@app.message("hello") +def message_hello(message, say): + # イベントがトリガーされたチャンネルへ say() でメッセージを送信します + say( + blocks=[ + { + "type": "section", + "text": {"type": "mrkdwn", "text": f"Hey there <@{message['user']}>!"}, + "accessory": { + "type": "button", + "text": {"type": "plain_text", "text":"Click Me"}, + "action_id": "button_click" + } + } + ], + text=f"Hey there <@{message['user']}>!" + ) + +# アプリを起動します +if __name__ == "__main__": + app.start(port=int(os.environ.get("PORT", 3000))) +``` + + + + `say()` の中の値を `blocks` という配列のオブジェクトに変えました。ブロックは Slack メッセージを構成するコンポーネントであり、テキストや画像、日付ピッカーなど、さまざまなタイプのブロックがあります。この例では `accessory` に `button` を持たせた「section」のブロックを、アプリからの応答に含めています。`blocks` を使用する場合、`text` は通知やアクセシビリティのためのフォールバックとなります。 ボタンを含む `accessory` オブジェクトでは、`action_id` を指定していることがわかります。これは、ボタンを一意に示す識別子として機能します。これを使って、アプリをどのアクションに応答させるかを指定できます。 -:::tip +:::tip[[Block Kit Builder](https://app.slack.com/block-kit-builder) を使用すると、インタラクティブなメッセージのプロトタイプを簡単に作成できます。] -[Block Kit Builder](https://app.slack.com/block-kit-builder) を使用すると、インタラクティブなメッセージのプロトタイプを簡単に作成できます。自分自身やチームメンバーがメッセージのモックアップを作成し、生成される JSON をアプリに直接貼りつけることができます。 +自分自身やチームメンバーがメッセージのモックアップを作成し、生成される JSON をアプリに直接貼りつけることができます。 ::: @@ -261,6 +359,9 @@ if __name__ == "__main__": ハンドラーを追加して、ボタンがクリックされたときにフォローアップメッセージを送信するようにしてみましょう。 + + + ```python import os from slack_bolt import App @@ -300,16 +401,63 @@ if __name__ == "__main__": SocketModeHandler(app, os.environ["SLACK_APP_TOKEN"]).start() ``` + + + +```python +import os +from slack_bolt import App + +# ボットトークンと署名シークレットを使ってアプリを初期化します +app = App( + token=os.environ.get("SLACK_BOT_TOKEN"), + signing_secret=os.environ.get("SLACK_SIGNING_SECRET") +) + +# 'hello' を含むメッセージをリッスンします +@app.message("hello") +def message_hello(message, say): + # イベントがトリガーされたチャンネルへ say() でメッセージを送信します + say( + blocks=[ + { + "type": "section", + "text": {"type": "mrkdwn", "text": f"Hey there <@{message['user']}>!"}, + "accessory": { + "type": "button", + "text": {"type": "plain_text", "text":"Click Me"}, + "action_id": "button_click" + } + } + ], + text=f"Hey there <@{message['user']}>!" + ) + +@app.action("button_click") +def action_button_click(body, ack, say): + # アクションを確認したことを即時で応答します + ack() + # チャンネルにメッセージを投稿します + say(f"<@{body['user']['id']}> clicked the button") + +# アプリを起動します +if __name__ == "__main__": + app.start(port=int(os.environ.get("PORT", 3000))) +``` + + + + `app.action()` を使って、先ほど命名した `button_click` という `action_id` をリッスンしています。アプリを再起動し、ボタンをクリックすると、アプリからの「クリックしました!」というメッセージが新たに表示されるでしょう。 --- ### 次のステップ {#next-steps} -はじめての [Bolt for Python アプリ](https://github.com/slackapi/bolt-python/tree/main/examples/getting_started)をソケットモードを使って構築することができました。🎉 +はじめての [Bolt for Python アプリ](https://github.com/slackapi/bolt-python/tree/main/examples/getting_started)を構築することができました。🎉 ここまでで基本的なアプリをセットアップして実行することはできたので、次は自分だけの Bolt アプリを作る方法について調べてみてください。参考になりそうなリソースをいくつかご紹介します。 * 基本的な概念について読んでみてください。Bolt アプリがアクセスできるさまざまメソッドや機能について知ることができます。 -* [`app.event()` メソッド](/concepts/event-listening)でボットがリッスンできるイベントをほかにも試してみましょう。すべてのイベントの一覧は [API サイト](https://api.slack.com/events)で確認できます。 -* Bolt では、アプリにアタッチされたクライアントから [Web API メソッドを呼び出す](/concepts/web-api)ことができます。API サイトに [220 以上のメソッド](https://api.slack.com/methods)を一覧しています。 -* [API サイト](https://api.slack.com/docs/token-types)でほかのタイプのトークンを確認してみてください。アプリで実行したいアクションによって、異なるトークンが必要になる場合があります。ソケットモードを使わないアプリでは、通常はボットトークン (`xoxb`) と署名シークレットが必要です。ソケットモードを使わない場合の例については、 HTTP 方式のやり方としてこのチュートリアルと対になっている [Bolt 入門ガイド(HTTP)](/tutorial/getting-started-http)を参照してください。 \ No newline at end of file +* [`app.event()` メソッド](/tools/bolt-python/concepts/event-listening)でボットがリッスンできるイベントをほかにも試してみましょう。すべてのイベントの一覧は [API サイト](/reference/events)で確認できます。 +* Bolt では、アプリにアタッチされたクライアントから [Web API メソッドを呼び出す](/tools/bolt-python/concepts/web-api)ことができます。API サイトに [220 以上のメソッド](/reference/methods)を一覧しています。 +* [API サイト](/authentication/tokens)でほかのタイプのトークンを確認してみてください。アプリで実行したいアクションによって、異なるトークンが必要になる場合があります。 diff --git a/docs/japanese/legacy/steps-from-apps.md b/docs/japanese/legacy/steps-from-apps.md new file mode 100644 index 000000000..802802ab3 --- /dev/null +++ b/docs/japanese/legacy/steps-from-apps.md @@ -0,0 +1,184 @@ +# ワークフローステップの概要 + +(アプリによる)ワークフローステップでは、処理をアプリ側で行うカスタムのワークフローステップを提供することができます。ユーザーは[ワークフロービルダー](/workflows/workflow-builder)を使ってこれらのステップをワークフローに追加できます。 + +ワークフローステップは、次の 3 つのユーザーイベントで構成されます。 + +- ワークフローステップをワークフローに追加・変更する +- ワークフロー内のステップの設定内容を更新する +- エンドユーザーがそのステップを実行する + +ワークフローステップを機能させるためには、これら 3 つのイベントすべてに対応する必要があります。 + +## ステップの定義 + +ワークフローステップの作成には、Bolt が提供する `WorkflowStep` クラスを利用します。 + +ステップの `callback_id` と設定オブジェクトを指定して、`WorkflowStep` の新しいインスタンスを作成します。 + +設定オブジェクトは、`edit`、`save`、`execute` という 3 つのキーを持ちます。それぞれのキーは、単一のコールバック、またはコールバックのリストである必要があります。すべてのコールバックは、ワークフローステップのイベントに関する情報を保持する `step` オブジェクトにアクセスできます。 + +`WorkflowStep` のインスタンスを作成したら、それを`app.step()` メソッドに渡します。これによって、アプリがワークフローステップのイベントをリッスンし、設定オブジェクトで指定されたコールバックを使ってそれに応答できるようになります。 + +また、デコレーターとして利用できる `WorkflowStepBuilder` クラスを使ってワークフローステップを定義することもできます。 詳細は、[こちらのドキュメント](https://docs.slack.dev/tools/bolt-python/reference/workflows/step/step.html#slack_bolt.workflows.step.step.WorkflowStepBuilder)のコード例などを参考にしてください。 + +指定可能な引数の一覧はモジュールドキュメントを参考にしてください([共通](https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html) / [ステップ用](https://docs.slack.dev/tools/bolt-python/reference/workflows/step/utilities/index.html)) + +```python +import os +from slack_bolt import App +from slack_bolt.workflows.step import WorkflowStep + +# いつも通りBolt アプリを起動する +app = App( + token=os.environ.get("SLACK_BOT_TOKEN"), + signing_secret=os.environ.get("SLACK_SIGNING_SECRET") +) + +def edit(ack, step, configure): + pass + +def save(ack, view, update): + pass + +def execute(step, complete, fail): + pass + +# WorkflowStep の新しいインスタンスを作成する +ws = WorkflowStep( + callback_id="add_task", + edit=edit, + save=save, + execute=execute, +) +# ワークフローステップを渡してリスナーを設定する +app.step(ws) +``` + +## ステップの追加・編集 + +作成したワークフローステップがワークフローに追加またはその設定を変更されるタイミングで、`workflow_step_edit` イベントがアプリに送信されます。このイベントがアプリに届くと、`WorkflowStep` で設定した `edit` コールバックが実行されます。 + +ステップの追加と編集のどちらが行われるときも、ワークフローステップの設定モーダルをビルダーに送信する必要があります。このモーダルは、そのステップ独自の設定を選択するための場所です。通常のモーダルより制限が強く、例えば `title`、`submit`、`close` のプロパティを含めることができません。設定モーダルの `callback_id` は、デフォルトではワークフローステップと同じものになります。 + +`edit` コールバック内で `configure()` ユーティリティを使用すると、対応する `blocks` 引数にビューのblocks 部分だけを渡して、ステップの設定モーダルを簡単に表示させることができます。必要な入力内容が揃うまで設定の保存を無効にするには、`True` の値をセットした `submit_disabled` を渡します。 + +指定可能な引数の一覧はモジュールドキュメントを参考にしてください([共通](https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html) / [ステップ用](https://docs.slack.dev/tools/bolt-python/reference/workflows/step/utilities/index.html)) + +```python +def edit(ack, step, configure): + ack() + + blocks = [ + { + "type": "input", + "block_id": "task_name_input", + "element": { + "type": "plain_text_input", + "action_id": "name", + "placeholder": {"type": "plain_text", "text":"Add a task name"}, + }, + "label": {"type": "plain_text", "text":"Task name"}, + }, + { + "type": "input", + "block_id": "task_description_input", + "element": { + "type": "plain_text_input", + "action_id": "description", + "placeholder": {"type": "plain_text", "text":"Add a task description"}, + }, + "label": {"type": "plain_text", "text":"Task description"}, + }, + ] + configure(blocks=blocks) + +ws = WorkflowStep( + callback_id="add_task", + edit=edit, + save=save, + execute=execute, +) +app.step(ws) +``` + +## ステップの設定の保存 + +設定モーダルを開いた後、アプリは `view_submission` イベントをリッスンします。このイベントがアプリに届くと、`WorkflowStep` で設定した `save` コールバックが実行されます。 + +`save` コールバック内では、`update()` メソッドを使って、ワークフローに追加されたステップの設定を保存することができます。このメソッドには次の引数を指定します。 + +- `inputs` : ユーザーがワークフローステップを実行したときにアプリが受け取る予定のデータを表す辞書型の値です。 +- `outputs` : ワークフローステップの完了時にアプリが出力するデータが設定されたオブジェクトのリストです。この outputs は、ワークフローの後続のステップで利用することができます。 +- `step_name` : ステップのデフォルトの名前をオーバーライドします。 +- `step_image_url` : ステップのデフォルトの画像をオーバーライドします。 + +指定可能な引数の一覧はモジュールドキュメントを参考にしてください([共通](https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html) / [ステップ用](https://docs.slack.dev/tools/bolt-python/reference/workflows/step/utilities/index.html)) + +```python +def save(ack, view, update): + ack() + + values = view["state"]["values"] + task_name = values["task_name_input"]["name"] + task_description = values["task_description_input"]["description"] + + inputs = { + "task_name": {"value": task_name["value"]}, + "task_description": {"value": task_description["value"]} + } + outputs = [ + { + "type": "text", + "name": "task_name", + "label":"Task name", + }, + { + "type": "text", + "name": "task_description", + "label":"Task description", + } + ] + update(inputs=inputs, outputs=outputs) + +ws = WorkflowStep( + callback_id="add_task", + edit=edit, + save=save, + execute=execute, +) +app.step(ws) +``` + +## ステップの実行 + +エンドユーザーがワークフローステップを実行すると、アプリに `workflow_step_execute` イベントが送信されます。このイベントがアプリに届くと、`WorkflowStep` で設定した `execute` コールバックが実行されます。 + +`save` コールバックで取り出した `inputs` を使って、サードパーティの API を呼び出す、情報をデータベースに保存する、ユーザーのホームタブを更新するといった処理を実行することができます。また、ワークフローの後続のステップで利用する出力値を `outputs` オブジェクトに設定します。 + +`execute` コールバック内では、`complete()` を呼び出してステップの実行が成功したことを示すか、`fail()` を呼び出してステップの実行が失敗したことを示す必要があります。 + +指定可能な引数の一覧はモジュールドキュメントを参考にしてください([共通](https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html) / [ステップ用](https://docs.slack.dev/tools/bolt-python/reference/workflows/step/utilities/index.html)) + +```python +def execute(step, complete, fail): + inputs = step["inputs"] + # すべての処理が成功した場合 + outputs = { + "task_name": inputs["task_name"]["value"], + "task_description": inputs["task_description"]["value"], + } + complete(outputs=outputs) + + # 失敗した処理がある場合 + error = {"message":"Just testing step failure!"} + fail(error=error) + +ws = WorkflowStep( + callback_id="add_task", + edit=edit, + save=save, + execute=execute, +) +app.step(ws) +``` \ No newline at end of file diff --git a/docs/navbarConfig.js b/docs/navbarConfig.js deleted file mode 100644 index 4e7260e49..000000000 --- a/docs/navbarConfig.js +++ /dev/null @@ -1,98 +0,0 @@ -const navbar = { - title: 'Slack Developer Tools', - logo: { - src: 'img/slack-logo.svg', - href: 'https://tools.slack.dev', - }, - items: [ - { - type: 'dropdown', - label: 'Bolt', - position: 'left', - items: [ - { - label: 'Java', - to: 'https://tools.slack.dev/java-slack-sdk/guides/bolt-basics', - target: '_self', - }, - { - label: 'JavaScript', - to: 'https://tools.slack.dev/bolt-js', - target: '_self', - }, - { - label: 'Python', - to: 'https://tools.slack.dev/bolt-python', - target: '_self', - }, - ], - }, - { - type: 'dropdown', - label: 'SDKs', - position: 'left', - items: [ - { - label: 'Java Slack SDK', - to: 'https://tools.slack.dev/java-slack-sdk/', - target: '_self', - }, - { - label: 'Node Slack SDK', - to: 'https://tools.slack.dev/node-slack-sdk/', - target: '_self', - }, - { - label: 'Python Slack SDK', - to: 'https://tools.slack.dev/python-slack-sdk/', - target: '_self', - }, - { - label: 'Deno Slack SDK', - to: 'https://tools.slack.dev/deno-slack-sdk/', - target: '_self', - }, - ], - }, - { - to: 'https://tools.slack.dev/slack-cli', - label: 'Slack CLI', - target: '_self', - }, - { - type: 'dropdown', - label: 'Community', - position: 'right', - items: [ - { - label: 'Community tools', - to: 'https://tools.slack.dev/community-tools', - target: '_self', - }, - { - label: 'Slack Community', - to: 'https://slackcommunity.com/', - target: '_self', - }, - ], - }, - { - to: 'https://api.slack.com/docs', - label: 'API Docs', - target: '_self', - }, - { - type: 'localeDropdown', - position: 'right', - }, - { - 'aria-label': 'GitHub Repository', - className: 'navbar-github-link', - href: 'https://github.com/slackapi', - position: 'right', - target: '_self', - }, - ], -}; - -module.exports = navbar; diff --git a/docs/package-lock.json b/docs/package-lock.json deleted file mode 100644 index 6aae70f68..000000000 --- a/docs/package-lock.json +++ /dev/null @@ -1,16761 +0,0 @@ -{ - "name": "website", - "version": "2024.08.01", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "website", - "version": "2024.08.01", - "dependencies": { - "@docusaurus/core": "3.6.3", - "@docusaurus/plugin-client-redirects": "^3.6.3", - "@docusaurus/preset-classic": "3.6.3", - "@mdx-js/react": "^3.1.0", - "clsx": "^2.0.0", - "docusaurus-theme-github-codeblock": "^2.0.2", - "prism-react-renderer": "^2.4.1", - "react": "^18.0.0", - "react-dom": "^18.0.0" - }, - "devDependencies": { - "@docusaurus/module-type-aliases": "3.6.3", - "@docusaurus/types": "3.6.3" - }, - "engines": { - "node": ">=20.0" - } - }, - "node_modules/@algolia/autocomplete-core": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.17.7.tgz", - "integrity": "sha512-BjiPOW6ks90UKl7TwMv7oNQMnzU+t/wk9mgIDi6b1tXpUek7MW0lbNOUHpvam9pe3lVCf4xPFT+lK7s+e+fs7Q==", - "dependencies": { - "@algolia/autocomplete-plugin-algolia-insights": "1.17.7", - "@algolia/autocomplete-shared": "1.17.7" - } - }, - "node_modules/@algolia/autocomplete-plugin-algolia-insights": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.17.7.tgz", - "integrity": "sha512-Jca5Ude6yUOuyzjnz57og7Et3aXjbwCSDf/8onLHSQgw1qW3ALl9mrMWaXb5FmPVkV3EtkD2F/+NkT6VHyPu9A==", - "dependencies": { - "@algolia/autocomplete-shared": "1.17.7" - }, - "peerDependencies": { - "search-insights": ">= 1 < 3" - } - }, - "node_modules/@algolia/autocomplete-preset-algolia": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.17.7.tgz", - "integrity": "sha512-ggOQ950+nwbWROq2MOCIL71RE0DdQZsceqrg32UqnhDz8FlO9rL8ONHNsI2R1MH0tkgVIDKI/D0sMiUchsFdWA==", - "dependencies": { - "@algolia/autocomplete-shared": "1.17.7" - }, - "peerDependencies": { - "@algolia/client-search": ">= 4.9.1 < 6", - "algoliasearch": ">= 4.9.1 < 6" - } - }, - "node_modules/@algolia/autocomplete-shared": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.17.7.tgz", - "integrity": "sha512-o/1Vurr42U/qskRSuhBH+VKxMvkkUVTLU6WZQr+L5lGZZLYWyhdzWjW0iGXY7EkwRTjBqvN2EsR81yCTGV/kmg==", - "peerDependencies": { - "@algolia/client-search": ">= 4.9.1 < 6", - "algoliasearch": ">= 4.9.1 < 6" - } - }, - "node_modules/@algolia/cache-browser-local-storage": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.24.0.tgz", - "integrity": "sha512-t63W9BnoXVrGy9iYHBgObNXqYXM3tYXCjDSHeNwnsc324r4o5UiVKUiAB4THQ5z9U5hTj6qUvwg/Ez43ZD85ww==", - "dependencies": { - "@algolia/cache-common": "4.24.0" - } - }, - "node_modules/@algolia/cache-common": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.24.0.tgz", - "integrity": "sha512-emi+v+DmVLpMGhp0V9q9h5CdkURsNmFC+cOS6uK9ndeJm9J4TiqSvPYVu+THUP8P/S08rxf5x2P+p3CfID0Y4g==" - }, - "node_modules/@algolia/cache-in-memory": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.24.0.tgz", - "integrity": "sha512-gDrt2so19jW26jY3/MkFg5mEypFIPbPoXsQGQWAi6TrCPsNOSEYepBMPlucqWigsmEy/prp5ug2jy/N3PVG/8w==", - "dependencies": { - "@algolia/cache-common": "4.24.0" - } - }, - "node_modules/@algolia/client-abtesting": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.15.0.tgz", - "integrity": "sha512-FaEM40iuiv1mAipYyiptP4EyxkJ8qHfowCpEeusdHUC4C7spATJYArD2rX3AxkVeREkDIgYEOuXcwKUbDCr7Nw==", - "dependencies": { - "@algolia/client-common": "5.15.0", - "@algolia/requester-browser-xhr": "5.15.0", - "@algolia/requester-fetch": "5.15.0", - "@algolia/requester-node-http": "5.15.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/client-account": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.24.0.tgz", - "integrity": "sha512-adcvyJ3KjPZFDybxlqnf+5KgxJtBjwTPTeyG2aOyoJvx0Y8dUQAEOEVOJ/GBxX0WWNbmaSrhDURMhc+QeevDsA==", - "dependencies": { - "@algolia/client-common": "4.24.0", - "@algolia/client-search": "4.24.0", - "@algolia/transporter": "4.24.0" - } - }, - "node_modules/@algolia/client-account/node_modules/@algolia/client-common": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", - "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", - "dependencies": { - "@algolia/requester-common": "4.24.0", - "@algolia/transporter": "4.24.0" - } - }, - "node_modules/@algolia/client-account/node_modules/@algolia/client-search": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.24.0.tgz", - "integrity": "sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==", - "dependencies": { - "@algolia/client-common": "4.24.0", - "@algolia/requester-common": "4.24.0", - "@algolia/transporter": "4.24.0" - } - }, - "node_modules/@algolia/client-analytics": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.24.0.tgz", - "integrity": "sha512-y8jOZt1OjwWU4N2qr8G4AxXAzaa8DBvyHTWlHzX/7Me1LX8OayfgHexqrsL4vSBcoMmVw2XnVW9MhL+Y2ZDJXg==", - "dependencies": { - "@algolia/client-common": "4.24.0", - "@algolia/client-search": "4.24.0", - "@algolia/requester-common": "4.24.0", - "@algolia/transporter": "4.24.0" - } - }, - "node_modules/@algolia/client-analytics/node_modules/@algolia/client-common": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", - "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", - "dependencies": { - "@algolia/requester-common": "4.24.0", - "@algolia/transporter": "4.24.0" - } - }, - "node_modules/@algolia/client-analytics/node_modules/@algolia/client-search": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.24.0.tgz", - "integrity": "sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==", - "dependencies": { - "@algolia/client-common": "4.24.0", - "@algolia/requester-common": "4.24.0", - "@algolia/transporter": "4.24.0" - } - }, - "node_modules/@algolia/client-common": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.15.0.tgz", - "integrity": "sha512-IofrVh213VLsDkPoSKMeM9Dshrv28jhDlBDLRcVJQvlL8pzue7PEB1EZ4UoJFYS3NSn7JOcJ/V+olRQzXlJj1w==", - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/client-insights": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.15.0.tgz", - "integrity": "sha512-bDDEQGfFidDi0UQUCbxXOCdphbVAgbVmxvaV75cypBTQkJ+ABx/Npw7LkFGw1FsoVrttlrrQbwjvUB6mLVKs/w==", - "dependencies": { - "@algolia/client-common": "5.15.0", - "@algolia/requester-browser-xhr": "5.15.0", - "@algolia/requester-fetch": "5.15.0", - "@algolia/requester-node-http": "5.15.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/client-personalization": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.24.0.tgz", - "integrity": "sha512-l5FRFm/yngztweU0HdUzz1rC4yoWCFo3IF+dVIVTfEPg906eZg5BOd1k0K6rZx5JzyyoP4LdmOikfkfGsKVE9w==", - "dependencies": { - "@algolia/client-common": "4.24.0", - "@algolia/requester-common": "4.24.0", - "@algolia/transporter": "4.24.0" - } - }, - "node_modules/@algolia/client-personalization/node_modules/@algolia/client-common": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", - "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", - "dependencies": { - "@algolia/requester-common": "4.24.0", - "@algolia/transporter": "4.24.0" - } - }, - "node_modules/@algolia/client-query-suggestions": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.15.0.tgz", - "integrity": "sha512-wu8GVluiZ5+il8WIRsGKu8VxMK9dAlr225h878GGtpTL6VBvwyJvAyLdZsfFIpY0iN++jiNb31q2C1PlPL+n/A==", - "dependencies": { - "@algolia/client-common": "5.15.0", - "@algolia/requester-browser-xhr": "5.15.0", - "@algolia/requester-fetch": "5.15.0", - "@algolia/requester-node-http": "5.15.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/client-search": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.15.0.tgz", - "integrity": "sha512-Z32gEMrRRpEta5UqVQA612sLdoqY3AovvUPClDfMxYrbdDAebmGDVPtSogUba1FZ4pP5dx20D3OV3reogLKsRA==", - "dependencies": { - "@algolia/client-common": "5.15.0", - "@algolia/requester-browser-xhr": "5.15.0", - "@algolia/requester-fetch": "5.15.0", - "@algolia/requester-node-http": "5.15.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/events": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@algolia/events/-/events-4.0.1.tgz", - "integrity": "sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ==" - }, - "node_modules/@algolia/ingestion": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.15.0.tgz", - "integrity": "sha512-MkqkAxBQxtQ5if/EX2IPqFA7LothghVyvPoRNA/meS2AW2qkHwcxjuiBxv4H6mnAVEPfJlhu9rkdVz9LgCBgJg==", - "dependencies": { - "@algolia/client-common": "5.15.0", - "@algolia/requester-browser-xhr": "5.15.0", - "@algolia/requester-fetch": "5.15.0", - "@algolia/requester-node-http": "5.15.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/logger-common": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.24.0.tgz", - "integrity": "sha512-LLUNjkahj9KtKYrQhFKCzMx0BY3RnNP4FEtO+sBybCjJ73E8jNdaKJ/Dd8A/VA4imVHP5tADZ8pn5B8Ga/wTMA==" - }, - "node_modules/@algolia/logger-console": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.24.0.tgz", - "integrity": "sha512-X4C8IoHgHfiUROfoRCV+lzSy+LHMgkoEEU1BbKcsfnV0i0S20zyy0NLww9dwVHUWNfPPxdMU+/wKmLGYf96yTg==", - "dependencies": { - "@algolia/logger-common": "4.24.0" - } - }, - "node_modules/@algolia/monitoring": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.15.0.tgz", - "integrity": "sha512-QPrFnnGLMMdRa8t/4bs7XilPYnoUXDY8PMQJ1sf9ZFwhUysYYhQNX34/enoO0LBjpoOY6rLpha39YQEFbzgKyQ==", - "dependencies": { - "@algolia/client-common": "5.15.0", - "@algolia/requester-browser-xhr": "5.15.0", - "@algolia/requester-fetch": "5.15.0", - "@algolia/requester-node-http": "5.15.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/recommend": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-4.24.0.tgz", - "integrity": "sha512-P9kcgerfVBpfYHDfVZDvvdJv0lEoCvzNlOy2nykyt5bK8TyieYyiD0lguIJdRZZYGre03WIAFf14pgE+V+IBlw==", - "dependencies": { - "@algolia/cache-browser-local-storage": "4.24.0", - "@algolia/cache-common": "4.24.0", - "@algolia/cache-in-memory": "4.24.0", - "@algolia/client-common": "4.24.0", - "@algolia/client-search": "4.24.0", - "@algolia/logger-common": "4.24.0", - "@algolia/logger-console": "4.24.0", - "@algolia/requester-browser-xhr": "4.24.0", - "@algolia/requester-common": "4.24.0", - "@algolia/requester-node-http": "4.24.0", - "@algolia/transporter": "4.24.0" - } - }, - "node_modules/@algolia/recommend/node_modules/@algolia/client-common": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", - "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", - "dependencies": { - "@algolia/requester-common": "4.24.0", - "@algolia/transporter": "4.24.0" - } - }, - "node_modules/@algolia/recommend/node_modules/@algolia/client-search": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.24.0.tgz", - "integrity": "sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==", - "dependencies": { - "@algolia/client-common": "4.24.0", - "@algolia/requester-common": "4.24.0", - "@algolia/transporter": "4.24.0" - } - }, - "node_modules/@algolia/recommend/node_modules/@algolia/requester-browser-xhr": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.24.0.tgz", - "integrity": "sha512-Z2NxZMb6+nVXSjF13YpjYTdvV3032YTBSGm2vnYvYPA6mMxzM3v5rsCiSspndn9rzIW4Qp1lPHBvuoKJV6jnAA==", - "dependencies": { - "@algolia/requester-common": "4.24.0" - } - }, - "node_modules/@algolia/recommend/node_modules/@algolia/requester-node-http": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.24.0.tgz", - "integrity": "sha512-JF18yTjNOVYvU/L3UosRcvbPMGT9B+/GQWNWnenIImglzNVGpyzChkXLnrSf6uxwVNO6ESGu6oN8MqcGQcjQJw==", - "dependencies": { - "@algolia/requester-common": "4.24.0" - } - }, - "node_modules/@algolia/requester-browser-xhr": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.15.0.tgz", - "integrity": "sha512-Po/GNib6QKruC3XE+WKP1HwVSfCDaZcXu48kD+gwmtDlqHWKc7Bq9lrS0sNZ456rfCKhXksOmMfUs4wRM/Y96w==", - "dependencies": { - "@algolia/client-common": "5.15.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/requester-common": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.24.0.tgz", - "integrity": "sha512-k3CXJ2OVnvgE3HMwcojpvY6d9kgKMPRxs/kVohrwF5WMr2fnqojnycZkxPoEg+bXm8fi5BBfFmOqgYztRtHsQA==" - }, - "node_modules/@algolia/requester-fetch": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.15.0.tgz", - "integrity": "sha512-rOZ+c0P7ajmccAvpeeNrUmEKoliYFL8aOR5qGW5pFq3oj3Iept7Y5mEtEsOBYsRt6qLnaXn4zUKf+N8nvJpcIw==", - "dependencies": { - "@algolia/client-common": "5.15.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/requester-node-http": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.15.0.tgz", - "integrity": "sha512-b1jTpbFf9LnQHEJP5ddDJKE2sAlhYd7EVSOWgzo/27n/SfCoHfqD0VWntnWYD83PnOKvfe8auZ2+xCb0TXotrQ==", - "dependencies": { - "@algolia/client-common": "5.15.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/transporter": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.24.0.tgz", - "integrity": "sha512-86nI7w6NzWxd1Zp9q3413dRshDqAzSbsQjhcDhPIatEFiZrL1/TjnHL8S7jVKFePlIMzDsZWXAXwXzcok9c5oA==", - "dependencies": { - "@algolia/cache-common": "4.24.0", - "@algolia/logger-common": "4.24.0", - "@algolia/requester-common": "4.24.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==", - "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==", - "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.2", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.2.tgz", - "integrity": "sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz", - "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==", - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.26.0", - "@babel/generator": "^7.26.0", - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-module-transforms": "^7.26.0", - "@babel/helpers": "^7.26.0", - "@babel/parser": "^7.26.0", - "@babel/template": "^7.25.9", - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.26.0", - "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/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/generator": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.2.tgz", - "integrity": "sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==", - "dependencies": { - "@babel/parser": "^7.26.2", - "@babel/types": "^7.26.0", - "@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-annotate-as-pure": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", - "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", - "dependencies": { - "@babel/types": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.25.9.tgz", - "integrity": "sha512-C47lC7LIDCnz0h4vai/tpNOI95tCd5ZT3iBt/DBH5lXKHZsyNQv18yf1wIIg2ntiQNgmAvA+DgZ82iW8Qdym8g==", - "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz", - "integrity": "sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==", - "dependencies": { - "@babel/compat-data": "^7.25.9", - "@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-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.9.tgz", - "integrity": "sha512-UTZQMvt0d/rSz6KI+qdu7GQze5TIajwTS++GUozlw8VBJDEOAqSXwm1WvmYEZwqdqSGQshRocPDqrt4HBZB3fQ==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-member-expression-to-functions": "^7.25.9", - "@babel/helper-optimise-call-expression": "^7.25.9", - "@babel/helper-replace-supers": "^7.25.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", - "@babel/traverse": "^7.25.9", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.25.9.tgz", - "integrity": "sha512-ORPNZ3h6ZRkOyAa/SaHU+XsLZr0UQzRwuDQ0cczIA17nAzZ+85G5cVkOJIj7QavLZGSe8QXUmNFxSZzjcZF9bw==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "regexpu-core": "^6.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz", - "integrity": "sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==", - "dependencies": { - "@babel/helper-compilation-targets": "^7.22.6", - "@babel/helper-plugin-utils": "^7.22.5", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz", - "integrity": "sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==", - "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" - }, - "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==", - "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==", - "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-optimise-call-expression": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz", - "integrity": "sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==", - "dependencies": { - "@babel/types": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz", - "integrity": "sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.9.tgz", - "integrity": "sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-wrap-function": "^7.25.9", - "@babel/traverse": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.25.9.tgz", - "integrity": "sha512-IiDqTOTBQy0sWyeXyGSC5TBJpGFXBkRynjBeXsvbhQFKj2viwJC76Epz35YLU1fpe/Am6Vppb7W7zM4fPQzLsQ==", - "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.25.9", - "@babel/helper-optimise-call-expression": "^7.25.9", - "@babel/traverse": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-simple-access": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.25.9.tgz", - "integrity": "sha512-c6WHXuiaRsJTyHYLJV75t9IqsmTbItYfdj99PnzYGQZkYKvan5/2jKJ7gu31J3/BJ/A18grImSPModuyG/Eo0Q==", - "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz", - "integrity": "sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==", - "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" - }, - "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==", - "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==", - "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==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-wrap-function": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.9.tgz", - "integrity": "sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g==", - "dependencies": { - "@babel/template": "^7.25.9", - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz", - "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==", - "dependencies": { - "@babel/template": "^7.25.9", - "@babel/types": "^7.26.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz", - "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==", - "dependencies": { - "@babel/types": "^7.26.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz", - "integrity": "sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/traverse": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.9.tgz", - "integrity": "sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.9.tgz", - "integrity": "sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.25.9.tgz", - "integrity": "sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", - "@babel/plugin-transform-optional-chaining": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.13.0" - } - }, - "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.9.tgz", - "integrity": "sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/traverse": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.21.0-placeholder-for-preset-env.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", - "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.26.0.tgz", - "integrity": "sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz", - "integrity": "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz", - "integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz", - "integrity": "sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-unicode-sets-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", - "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.9.tgz", - "integrity": "sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg==", - "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-async-generator-functions": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.9.tgz", - "integrity": "sha512-RXV6QAzTBbhDMO9fWwOmwwTuYaiPbggWQ9INdZqAYeSHyG7FzQ+nOZaUUjNwKv9pV3aE4WFqFm1Hnbci5tBCAw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-remap-async-to-generator": "^7.25.9", - "@babel/traverse": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.25.9.tgz", - "integrity": "sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ==", - "dependencies": { - "@babel/helper-module-imports": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-remap-async-to-generator": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.25.9.tgz", - "integrity": "sha512-toHc9fzab0ZfenFpsyYinOX0J/5dgJVA2fm64xPewu7CoYHWEivIWKxkK2rMi4r3yQqLnVmheMXRdG+k239CgA==", - "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-block-scoping": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.9.tgz", - "integrity": "sha512-1F05O7AYjymAtqbsFETboN1NvBdcnzMerO+zlMyJBEz6WkMdejvGWw9p05iTSjC85RLlBseHHQpYaM4gzJkBGg==", - "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-class-properties": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.9.tgz", - "integrity": "sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q==", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.26.0.tgz", - "integrity": "sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ==", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0" - } - }, - "node_modules/@babel/plugin-transform-classes": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.9.tgz", - "integrity": "sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-replace-supers": "^7.25.9", - "@babel/traverse": "^7.25.9", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.9.tgz", - "integrity": "sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/template": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.9.tgz", - "integrity": "sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ==", - "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-dotall-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.25.9.tgz", - "integrity": "sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA==", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.25.9.tgz", - "integrity": "sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw==", - "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-duplicate-named-capturing-groups-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.9.tgz", - "integrity": "sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog==", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.9.tgz", - "integrity": "sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==", - "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-exponentiation-operator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.25.9.tgz", - "integrity": "sha512-KRhdhlVk2nObA5AYa7QMgTMTVJdfHprfpAk4DjZVtllqRg9qarilstTKEhpVjyt+Npi8ThRyiV8176Am3CodPA==", - "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.9.tgz", - "integrity": "sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww==", - "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-for-of": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.25.9.tgz", - "integrity": "sha512-LqHxduHoaGELJl2uhImHwRQudhCM50pT46rIBNvtT/Oql3nqiS3wOwP+5ten7NpYSXrrVLgtZU3DZmPtWZo16A==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-function-name": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.9.tgz", - "integrity": "sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA==", - "dependencies": { - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/traverse": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.9.tgz", - "integrity": "sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw==", - "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-literals": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.9.tgz", - "integrity": "sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ==", - "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-logical-assignment-operators": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.9.tgz", - "integrity": "sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q==", - "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-member-expression-literals": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.25.9.tgz", - "integrity": "sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA==", - "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-modules-amd": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.25.9.tgz", - "integrity": "sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw==", - "dependencies": { - "@babel/helper-module-transforms": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.25.9.tgz", - "integrity": "sha512-dwh2Ol1jWwL2MgkCzUSOvfmKElqQcuswAZypBSUsScMXvgdT8Ekq5YA6TtqpTVWH+4903NmboMuH1o9i8Rxlyg==", - "dependencies": { - "@babel/helper-module-transforms": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-simple-access": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.9.tgz", - "integrity": "sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA==", - "dependencies": { - "@babel/helper-module-transforms": "^7.25.9", - "@babel/helper-plugin-utils": "^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-0" - } - }, - "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.25.9.tgz", - "integrity": "sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw==", - "dependencies": { - "@babel/helper-module-transforms": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.25.9.tgz", - "integrity": "sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA==", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-new-target": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.25.9.tgz", - "integrity": "sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ==", - "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-nullish-coalescing-operator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.25.9.tgz", - "integrity": "sha512-ENfftpLZw5EItALAD4WsY/KUWvhUlZndm5GC7G3evUsVeSJB6p0pBeLQUnRnBCBx7zV0RKQjR9kCuwrsIrjWog==", - "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-numeric-separator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.9.tgz", - "integrity": "sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q==", - "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-object-rest-spread": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.9.tgz", - "integrity": "sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg==", - "dependencies": { - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/plugin-transform-parameters": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-object-super": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.25.9.tgz", - "integrity": "sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-replace-supers": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.9.tgz", - "integrity": "sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g==", - "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-optional-chaining": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.9.tgz", - "integrity": "sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-parameters": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.9.tgz", - "integrity": "sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g==", - "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-private-methods": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz", - "integrity": "sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw==", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.9.tgz", - "integrity": "sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.25.9.tgz", - "integrity": "sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA==", - "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-constant-elements": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.24.7.tgz", - "integrity": "sha512-7LidzZfUXyfZ8/buRW6qIIHBY8wAZ1OrY9c/wTr8YhZ6vMPo+Uc/CVFLYY1spZrEQlD4w5u8wjqk5NQ3OVqQKA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-display-name": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.25.9.tgz", - "integrity": "sha512-KJfMlYIUxQB1CJfO3e0+h0ZHWOTLCPP115Awhaz8U0Zpq36Gl/cXlpoyMRnUWlhNUBAzldnCiAZNvCDj7CrKxQ==", - "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": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.25.9.tgz", - "integrity": "sha512-s5XwpQYCqGerXl+Pu6VDL3x0j2d82eiV77UJ8a2mDHAW7j9SWRqQ2y1fNo1Z74CdcYipl5Z41zvjj4Nfzq36rw==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-module-imports": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/plugin-syntax-jsx": "^7.25.9", - "@babel/types": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-development": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.25.9.tgz", - "integrity": "sha512-9mj6rm7XVYs4mdLIpbZnHOYdpW42uoiBCTVowg7sP1thUOiANgMb4UtpRivR0pp5iL+ocvUv7X4mZgFRpJEzGw==", - "dependencies": { - "@babel/plugin-transform-react-jsx": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-pure-annotations": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.25.9.tgz", - "integrity": "sha512-KQ/Takk3T8Qzj5TppkS1be588lkbTp5uj7w6a0LeQaTMSckU/wK0oJ/pih+T690tkgI5jfmg2TqDJvd41Sj1Cg==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.25.9.tgz", - "integrity": "sha512-vwDcDNsgMPDGP0nMqzahDWE5/MLcX8sv96+wfX7as7LoF/kr97Bo/7fI00lXY4wUXYfVmwIIyG80fGZ1uvt2qg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "regenerator-transform": "^0.15.2" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-regexp-modifiers": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.26.0.tgz", - "integrity": "sha512-vN6saax7lrA2yA/Pak3sCxuD6F5InBjn9IcrIKQPjpsLvuHYLVroTxjdlVRHjjBWxKOqIwpTXDkOssYT4BFdRw==", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.9.tgz", - "integrity": "sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg==", - "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-runtime": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.25.9.tgz", - "integrity": "sha512-nZp7GlEl+yULJrClz0SwHPqir3lc0zsPrDHQUcxGspSL7AKrexNSEfTbfqnDNJUO13bgKyfuOLMF8Xqtu8j3YQ==", - "dependencies": { - "@babel/helper-module-imports": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "babel-plugin-polyfill-corejs2": "^0.4.10", - "babel-plugin-polyfill-corejs3": "^0.10.6", - "babel-plugin-polyfill-regenerator": "^0.6.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.9.tgz", - "integrity": "sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng==", - "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-spread": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.25.9.tgz", - "integrity": "sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.25.9.tgz", - "integrity": "sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA==", - "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-template-literals": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.25.9.tgz", - "integrity": "sha512-o97AE4syN71M/lxrCtQByzphAdlYluKPDBzDVzMmfCobUjjhAryZV0AIpRPrxN0eAkxXO6ZLEScmt+PNhj2OTw==", - "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-typeof-symbol": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.25.9.tgz", - "integrity": "sha512-v61XqUMiueJROUv66BVIOi0Fv/CUuZuZMl5NkRoCVxLAnMexZ0A3kMe7vvZ0nulxMuMp0Mk6S5hNh48yki08ZA==", - "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-typescript": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.25.9.tgz", - "integrity": "sha512-7PbZQZP50tzv2KGGnhh82GSyMB01yKY9scIjf1a+GfZCtInOWqUH5+1EBU4t9fyR5Oykkkc9vFTs4OHrhHXljQ==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", - "@babel/plugin-syntax-typescript": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.9.tgz", - "integrity": "sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q==", - "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-unicode-property-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.25.9.tgz", - "integrity": "sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg==", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.25.9.tgz", - "integrity": "sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA==", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.9.tgz", - "integrity": "sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ==", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/preset-env": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.26.0.tgz", - "integrity": "sha512-H84Fxq0CQJNdPFT2DrfnylZ3cf5K43rGfWK4LJGPpjKHiZlk0/RzwEus3PDDZZg+/Er7lCA03MVacueUuXdzfw==", - "dependencies": { - "@babel/compat-data": "^7.26.0", - "@babel/helper-compilation-targets": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-validator-option": "^7.25.9", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.9", - "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.9", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.9", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.25.9", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.9", - "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-import-assertions": "^7.26.0", - "@babel/plugin-syntax-import-attributes": "^7.26.0", - "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.25.9", - "@babel/plugin-transform-async-generator-functions": "^7.25.9", - "@babel/plugin-transform-async-to-generator": "^7.25.9", - "@babel/plugin-transform-block-scoped-functions": "^7.25.9", - "@babel/plugin-transform-block-scoping": "^7.25.9", - "@babel/plugin-transform-class-properties": "^7.25.9", - "@babel/plugin-transform-class-static-block": "^7.26.0", - "@babel/plugin-transform-classes": "^7.25.9", - "@babel/plugin-transform-computed-properties": "^7.25.9", - "@babel/plugin-transform-destructuring": "^7.25.9", - "@babel/plugin-transform-dotall-regex": "^7.25.9", - "@babel/plugin-transform-duplicate-keys": "^7.25.9", - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.9", - "@babel/plugin-transform-dynamic-import": "^7.25.9", - "@babel/plugin-transform-exponentiation-operator": "^7.25.9", - "@babel/plugin-transform-export-namespace-from": "^7.25.9", - "@babel/plugin-transform-for-of": "^7.25.9", - "@babel/plugin-transform-function-name": "^7.25.9", - "@babel/plugin-transform-json-strings": "^7.25.9", - "@babel/plugin-transform-literals": "^7.25.9", - "@babel/plugin-transform-logical-assignment-operators": "^7.25.9", - "@babel/plugin-transform-member-expression-literals": "^7.25.9", - "@babel/plugin-transform-modules-amd": "^7.25.9", - "@babel/plugin-transform-modules-commonjs": "^7.25.9", - "@babel/plugin-transform-modules-systemjs": "^7.25.9", - "@babel/plugin-transform-modules-umd": "^7.25.9", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.25.9", - "@babel/plugin-transform-new-target": "^7.25.9", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.25.9", - "@babel/plugin-transform-numeric-separator": "^7.25.9", - "@babel/plugin-transform-object-rest-spread": "^7.25.9", - "@babel/plugin-transform-object-super": "^7.25.9", - "@babel/plugin-transform-optional-catch-binding": "^7.25.9", - "@babel/plugin-transform-optional-chaining": "^7.25.9", - "@babel/plugin-transform-parameters": "^7.25.9", - "@babel/plugin-transform-private-methods": "^7.25.9", - "@babel/plugin-transform-private-property-in-object": "^7.25.9", - "@babel/plugin-transform-property-literals": "^7.25.9", - "@babel/plugin-transform-regenerator": "^7.25.9", - "@babel/plugin-transform-regexp-modifiers": "^7.26.0", - "@babel/plugin-transform-reserved-words": "^7.25.9", - "@babel/plugin-transform-shorthand-properties": "^7.25.9", - "@babel/plugin-transform-spread": "^7.25.9", - "@babel/plugin-transform-sticky-regex": "^7.25.9", - "@babel/plugin-transform-template-literals": "^7.25.9", - "@babel/plugin-transform-typeof-symbol": "^7.25.9", - "@babel/plugin-transform-unicode-escapes": "^7.25.9", - "@babel/plugin-transform-unicode-property-regex": "^7.25.9", - "@babel/plugin-transform-unicode-regex": "^7.25.9", - "@babel/plugin-transform-unicode-sets-regex": "^7.25.9", - "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.10", - "babel-plugin-polyfill-corejs3": "^0.10.6", - "babel-plugin-polyfill-regenerator": "^0.6.1", - "core-js-compat": "^3.38.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/preset-modules": { - "version": "0.1.6-no-external-plugins", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", - "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/@babel/preset-react": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.25.9.tgz", - "integrity": "sha512-D3to0uSPiWE7rBrdIICCd0tJSIGpLaaGptna2+w7Pft5xMqLpA1sz99DK5TZ1TjGbdQ/VI1eCSZ06dv3lT4JOw==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-validator-option": "^7.25.9", - "@babel/plugin-transform-react-display-name": "^7.25.9", - "@babel/plugin-transform-react-jsx": "^7.25.9", - "@babel/plugin-transform-react-jsx-development": "^7.25.9", - "@babel/plugin-transform-react-pure-annotations": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-typescript": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.26.0.tgz", - "integrity": "sha512-NMk1IGZ5I/oHhoXEElcm+xUnL/szL6xflkFZmoEU9xj1qSJXpiS7rsspYo92B4DRCDvZn2erT5LdsCeXAKNCkg==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-validator-option": "^7.25.9", - "@babel/plugin-syntax-jsx": "^7.25.9", - "@babel/plugin-transform-modules-commonjs": "^7.25.9", - "@babel/plugin-transform-typescript": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", - "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/runtime-corejs3": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.26.0.tgz", - "integrity": "sha512-YXHu5lN8kJCb1LOb9PgV6pvak43X2h4HvRApcN5SdWeaItQOzfn1hgP6jasD6KWQyJDBxrVmA9o9OivlnNJK/w==", - "dependencies": { - "core-js-pure": "^3.30.2", - "regenerator-runtime": "^0.14.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", - "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", - "dependencies": { - "@babel/code-frame": "^7.25.9", - "@babel/parser": "^7.25.9", - "@babel/types": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.9.tgz", - "integrity": "sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==", - "dependencies": { - "@babel/code-frame": "^7.25.9", - "@babel/generator": "^7.25.9", - "@babel/parser": "^7.25.9", - "@babel/template": "^7.25.9", - "@babel/types": "^7.25.9", - "debug": "^4.3.1", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", - "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", - "dependencies": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", - "optional": true, - "engines": { - "node": ">=0.1.90" - } - }, - "node_modules/@csstools/cascade-layer-name-parser": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-2.0.4.tgz", - "integrity": "sha512-7DFHlPuIxviKYZrOiwVU/PiHLm3lLUR23OMuEEtfEOQTOp9hzQ2JjdY6X5H18RVuUPJqSCI+qNnD5iOLMVE0bA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.4", - "@csstools/css-tokenizer": "^3.0.3" - } - }, - "node_modules/@csstools/color-helpers": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.1.tgz", - "integrity": "sha512-MKtmkA0BX87PKaO1NFRTFH+UnkgnmySQOvNxJubsadusqPEC2aJ9MOQiMceZJJ6oitUl/i0L6u0M1IrmAOmgBA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@csstools/css-calc": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.0.tgz", - "integrity": "sha512-X69PmFOrjTZfN5ijxtI8hZ9kRADFSLrmmQ6hgDJ272Il049WGKpDY64KhrFm/7rbWve0z81QepawzjkKlqkNGw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.4", - "@csstools/css-tokenizer": "^3.0.3" - } - }, - "node_modules/@csstools/css-color-parser": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.6.tgz", - "integrity": "sha512-S/IjXqTHdpI4EtzGoNCHfqraXF37x12ZZHA1Lk7zoT5pm2lMjFuqhX/89L7dqX4CcMacKK+6ZCs5TmEGb/+wKw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/color-helpers": "^5.0.1", - "@csstools/css-calc": "^2.1.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.4", - "@csstools/css-tokenizer": "^3.0.3" - } - }, - "node_modules/@csstools/css-parser-algorithms": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.4.tgz", - "integrity": "sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-tokenizer": "^3.0.3" - } - }, - "node_modules/@csstools/css-tokenizer": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.3.tgz", - "integrity": "sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@csstools/media-query-list-parser": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-4.0.2.tgz", - "integrity": "sha512-EUos465uvVvMJehckATTlNqGj4UJWkTmdWuDMjqvSUkjGpmOyFZBVwb4knxCm/k2GMTXY+c/5RkdndzFYWeX5A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.4", - "@csstools/css-tokenizer": "^3.0.3" - } - }, - "node_modules/@csstools/postcss-cascade-layers": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-5.0.1.tgz", - "integrity": "sha512-XOfhI7GShVcKiKwmPAnWSqd2tBR0uxt+runAxttbSp/LY2U16yAVPmAf7e9q4JJ0d+xMNmpwNDLBXnmRCl3HMQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/selector-specificity": "^5.0.0", - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-cascade-layers/node_modules/@csstools/selector-specificity": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", - "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss-selector-parser": "^7.0.0" - } - }, - "node_modules/@csstools/postcss-cascade-layers/node_modules/postcss-selector-parser": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", - "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@csstools/postcss-color-function": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-4.0.6.tgz", - "integrity": "sha512-EcvXfC60cTIumzpsxWuvVjb7rsJEHPvqn3jeMEBUaE3JSc4FRuP7mEQ+1eicxWmIrs3FtzMH9gR3sgA5TH+ebQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/css-color-parser": "^3.0.6", - "@csstools/css-parser-algorithms": "^3.0.4", - "@csstools/css-tokenizer": "^3.0.3", - "@csstools/postcss-progressive-custom-properties": "^4.0.0", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-color-mix-function": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@csstools/postcss-color-mix-function/-/postcss-color-mix-function-3.0.6.tgz", - "integrity": "sha512-jVKdJn4+JkASYGhyPO+Wa5WXSx1+oUgaXb3JsjJn/BlrtFh5zjocCY7pwWi0nuP24V1fY7glQsxEYcYNy0dMFg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/css-color-parser": "^3.0.6", - "@csstools/css-parser-algorithms": "^3.0.4", - "@csstools/css-tokenizer": "^3.0.3", - "@csstools/postcss-progressive-custom-properties": "^4.0.0", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-content-alt-text": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@csstools/postcss-content-alt-text/-/postcss-content-alt-text-2.0.4.tgz", - "integrity": "sha512-YItlZUOuZJCBlRaCf8Aucc1lgN41qYGALMly0qQllrxYJhiyzlI6RxOTMUvtWk+KhS8GphMDsDhKQ7KTPfEMSw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/css-parser-algorithms": "^3.0.4", - "@csstools/css-tokenizer": "^3.0.3", - "@csstools/postcss-progressive-custom-properties": "^4.0.0", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-exponential-functions": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@csstools/postcss-exponential-functions/-/postcss-exponential-functions-2.0.5.tgz", - "integrity": "sha512-mi8R6dVfA2nDoKM3wcEi64I8vOYEgQVtVKCfmLHXupeLpACfGAided5ddMt5f+CnEodNu4DifuVwb0I6fQDGGQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/css-calc": "^2.1.0", - "@csstools/css-parser-algorithms": "^3.0.4", - "@csstools/css-tokenizer": "^3.0.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-font-format-keywords": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-4.0.0.tgz", - "integrity": "sha512-usBzw9aCRDvchpok6C+4TXC57btc4bJtmKQWOHQxOVKen1ZfVqBUuCZ/wuqdX5GHsD0NRSr9XTP+5ID1ZZQBXw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/utilities": "^2.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-gamut-mapping": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@csstools/postcss-gamut-mapping/-/postcss-gamut-mapping-2.0.6.tgz", - "integrity": "sha512-0ke7fmXfc8H+kysZz246yjirAH6JFhyX9GTlyRnM0exHO80XcA9zeJpy5pOp5zo/AZiC/q5Pf+Hw7Pd6/uAoYA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/css-color-parser": "^3.0.6", - "@csstools/css-parser-algorithms": "^3.0.4", - "@csstools/css-tokenizer": "^3.0.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-gradients-interpolation-method": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@csstools/postcss-gradients-interpolation-method/-/postcss-gradients-interpolation-method-5.0.6.tgz", - "integrity": "sha512-Itrbx6SLUzsZ6Mz3VuOlxhbfuyLTogG5DwEF1V8dAi24iMuvQPIHd7Ti+pNDp7j6WixndJGZaoNR0f9VSzwuTg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/css-color-parser": "^3.0.6", - "@csstools/css-parser-algorithms": "^3.0.4", - "@csstools/css-tokenizer": "^3.0.3", - "@csstools/postcss-progressive-custom-properties": "^4.0.0", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-hwb-function": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-4.0.6.tgz", - "integrity": "sha512-927Pqy3a1uBP7U8sTfaNdZVB0mNXzIrJO/GZ8us9219q9n06gOqCdfZ0E6d1P66Fm0fYHvxfDbfcUuwAn5UwhQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/css-color-parser": "^3.0.6", - "@csstools/css-parser-algorithms": "^3.0.4", - "@csstools/css-tokenizer": "^3.0.3", - "@csstools/postcss-progressive-custom-properties": "^4.0.0", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-ic-unit": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-4.0.0.tgz", - "integrity": "sha512-9QT5TDGgx7wD3EEMN3BSUG6ckb6Eh5gSPT5kZoVtUuAonfPmLDJyPhqR4ntPpMYhUKAMVKAg3I/AgzqHMSeLhA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/postcss-progressive-custom-properties": "^4.0.0", - "@csstools/utilities": "^2.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-initial": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-initial/-/postcss-initial-2.0.0.tgz", - "integrity": "sha512-dv2lNUKR+JV+OOhZm9paWzYBXOCi+rJPqJ2cJuhh9xd8USVrd0cBEPczla81HNOyThMQWeCcdln3gZkQV2kYxA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-is-pseudo-class": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-5.0.1.tgz", - "integrity": "sha512-JLp3POui4S1auhDR0n8wHd/zTOWmMsmK3nQd3hhL6FhWPaox5W7j1se6zXOG/aP07wV2ww0lxbKYGwbBszOtfQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/selector-specificity": "^5.0.0", - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-is-pseudo-class/node_modules/@csstools/selector-specificity": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", - "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss-selector-parser": "^7.0.0" - } - }, - "node_modules/@csstools/postcss-is-pseudo-class/node_modules/postcss-selector-parser": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", - "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@csstools/postcss-light-dark-function": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@csstools/postcss-light-dark-function/-/postcss-light-dark-function-2.0.7.tgz", - "integrity": "sha512-ZZ0rwlanYKOHekyIPaU+sVm3BEHCe+Ha0/px+bmHe62n0Uc1lL34vbwrLYn6ote8PHlsqzKeTQdIejQCJ05tfw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/css-parser-algorithms": "^3.0.4", - "@csstools/css-tokenizer": "^3.0.3", - "@csstools/postcss-progressive-custom-properties": "^4.0.0", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-logical-float-and-clear": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-float-and-clear/-/postcss-logical-float-and-clear-3.0.0.tgz", - "integrity": "sha512-SEmaHMszwakI2rqKRJgE+8rpotFfne1ZS6bZqBoQIicFyV+xT1UF42eORPxJkVJVrH9C0ctUgwMSn3BLOIZldQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-logical-overflow": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-overflow/-/postcss-logical-overflow-2.0.0.tgz", - "integrity": "sha512-spzR1MInxPuXKEX2csMamshR4LRaSZ3UXVaRGjeQxl70ySxOhMpP2252RAFsg8QyyBXBzuVOOdx1+bVO5bPIzA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-logical-overscroll-behavior": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-overscroll-behavior/-/postcss-logical-overscroll-behavior-2.0.0.tgz", - "integrity": "sha512-e/webMjoGOSYfqLunyzByZj5KKe5oyVg/YSbie99VEaSDE2kimFm0q1f6t/6Jo+VVCQ/jbe2Xy+uX+C4xzWs4w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-logical-resize": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-resize/-/postcss-logical-resize-3.0.0.tgz", - "integrity": "sha512-DFbHQOFW/+I+MY4Ycd/QN6Dg4Hcbb50elIJCfnwkRTCX05G11SwViI5BbBlg9iHRl4ytB7pmY5ieAFk3ws7yyg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-logical-viewport-units": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-viewport-units/-/postcss-logical-viewport-units-3.0.3.tgz", - "integrity": "sha512-OC1IlG/yoGJdi0Y+7duz/kU/beCwO+Gua01sD6GtOtLi7ByQUpcIqs7UE/xuRPay4cHgOMatWdnDdsIDjnWpPw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/css-tokenizer": "^3.0.3", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-media-minmax": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@csstools/postcss-media-minmax/-/postcss-media-minmax-2.0.5.tgz", - "integrity": "sha512-sdh5i5GToZOIAiwhdntRWv77QDtsxP2r2gXW/WbLSCoLr00KTq/yiF1qlQ5XX2+lmiFa8rATKMcbwl3oXDMNew==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/css-calc": "^2.1.0", - "@csstools/css-parser-algorithms": "^3.0.4", - "@csstools/css-tokenizer": "^3.0.3", - "@csstools/media-query-list-parser": "^4.0.2" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-media-queries-aspect-ratio-number-values": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@csstools/postcss-media-queries-aspect-ratio-number-values/-/postcss-media-queries-aspect-ratio-number-values-3.0.4.tgz", - "integrity": "sha512-AnGjVslHMm5xw9keusQYvjVWvuS7KWK+OJagaG0+m9QnIjZsrysD2kJP/tr/UJIyYtMCtu8OkUd+Rajb4DqtIQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/css-parser-algorithms": "^3.0.4", - "@csstools/css-tokenizer": "^3.0.3", - "@csstools/media-query-list-parser": "^4.0.2" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-nested-calc": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-nested-calc/-/postcss-nested-calc-4.0.0.tgz", - "integrity": "sha512-jMYDdqrQQxE7k9+KjstC3NbsmC063n1FTPLCgCRS2/qHUbHM0mNy9pIn4QIiQGs9I/Bg98vMqw7mJXBxa0N88A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/utilities": "^2.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-normalize-display-values": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.0.tgz", - "integrity": "sha512-HlEoG0IDRoHXzXnkV4in47dzsxdsjdz6+j7MLjaACABX2NfvjFS6XVAnpaDyGesz9gK2SC7MbNwdCHusObKJ9Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-oklab-function": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-4.0.6.tgz", - "integrity": "sha512-Hptoa0uX+XsNacFBCIQKTUBrFKDiplHan42X73EklG6XmQLG7/aIvxoNhvZ7PvOWMt67Pw3bIlUY2nD6p5vL8A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/css-color-parser": "^3.0.6", - "@csstools/css-parser-algorithms": "^3.0.4", - "@csstools/css-tokenizer": "^3.0.3", - "@csstools/postcss-progressive-custom-properties": "^4.0.0", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-progressive-custom-properties": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-4.0.0.tgz", - "integrity": "sha512-XQPtROaQjomnvLUSy/bALTR5VCtTVUFwYs1SblvYgLSeTo2a/bMNwUwo2piXw5rTv/FEYiy5yPSXBqg9OKUx7Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-random-function": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-random-function/-/postcss-random-function-1.0.1.tgz", - "integrity": "sha512-Ab/tF8/RXktQlFwVhiC70UNfpFQRhtE5fQQoP2pO+KCPGLsLdWFiOuHgSRtBOqEshCVAzR4H6o38nhvRZq8deA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/css-calc": "^2.1.0", - "@csstools/css-parser-algorithms": "^3.0.4", - "@csstools/css-tokenizer": "^3.0.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-relative-color-syntax": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@csstools/postcss-relative-color-syntax/-/postcss-relative-color-syntax-3.0.6.tgz", - "integrity": "sha512-yxP618Xb+ji1I624jILaYM62uEmZcmbdmFoZHoaThw896sq0vU39kqTTF+ZNic9XyPtPMvq0vyvbgmHaszq8xg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/css-color-parser": "^3.0.6", - "@csstools/css-parser-algorithms": "^3.0.4", - "@csstools/css-tokenizer": "^3.0.3", - "@csstools/postcss-progressive-custom-properties": "^4.0.0", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-scope-pseudo-class": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-scope-pseudo-class/-/postcss-scope-pseudo-class-4.0.1.tgz", - "integrity": "sha512-IMi9FwtH6LMNuLea1bjVMQAsUhFxJnyLSgOp/cpv5hrzWmrUYU5fm0EguNDIIOHUqzXode8F/1qkC/tEo/qN8Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-scope-pseudo-class/node_modules/postcss-selector-parser": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", - "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@csstools/postcss-sign-functions": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-sign-functions/-/postcss-sign-functions-1.1.0.tgz", - "integrity": "sha512-SLcc20Nujx/kqbSwDmj6oaXgpy3UjFhBy1sfcqPgDkHfOIfUtUVH7OXO+j7BU4v/At5s61N5ZX6shvgPwluhsA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/css-calc": "^2.1.0", - "@csstools/css-parser-algorithms": "^3.0.4", - "@csstools/css-tokenizer": "^3.0.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-stepped-value-functions": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-4.0.5.tgz", - "integrity": "sha512-G6SJ6hZJkhxo6UZojVlLo14MohH4J5J7z8CRBrxxUYy9JuZiIqUo5TBYyDGcE0PLdzpg63a7mHSJz3VD+gMwqw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/css-calc": "^2.1.0", - "@csstools/css-parser-algorithms": "^3.0.4", - "@csstools/css-tokenizer": "^3.0.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-text-decoration-shorthand": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-4.0.1.tgz", - "integrity": "sha512-xPZIikbx6jyzWvhms27uugIc0I4ykH4keRvoa3rxX5K7lEhkbd54rjj/dv60qOCTisoS+3bmwJTeyV1VNBrXaw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/color-helpers": "^5.0.1", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-trigonometric-functions": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-4.0.5.tgz", - "integrity": "sha512-/YQThYkt5MLvAmVu7zxjhceCYlKrYddK6LEmK5I4ojlS6BmO9u2yO4+xjXzu2+NPYmHSTtP4NFSamBCMmJ1NJA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/css-calc": "^2.1.0", - "@csstools/css-parser-algorithms": "^3.0.4", - "@csstools/css-tokenizer": "^3.0.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-unset-value": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-unset-value/-/postcss-unset-value-4.0.0.tgz", - "integrity": "sha512-cBz3tOCI5Fw6NIFEwU3RiwK6mn3nKegjpJuzCndoGq3BZPkUjnsq7uQmIeMNeMbMk7YD2MfKcgCpZwX5jyXqCA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/utilities": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@csstools/utilities/-/utilities-2.0.0.tgz", - "integrity": "sha512-5VdOr0Z71u+Yp3ozOx8T11N703wIFGVRgOWbOZMKgglPJsWA54MRIoMNVMa7shUToIhx5J8vX4sOZgD2XiihiQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@discoveryjs/json-ext": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", - "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@docsearch/css": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.8.0.tgz", - "integrity": "sha512-pieeipSOW4sQ0+bE5UFC51AOZp9NGxg89wAlZ1BAQFaiRAGK1IKUaPQ0UGZeNctJXyqZ1UvBtOQh2HH+U5GtmA==" - }, - "node_modules/@docsearch/react": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.8.0.tgz", - "integrity": "sha512-WnFK720+iwTVt94CxY3u+FgX6exb3BfN5kE9xUY6uuAH/9W/UFboBZFLlrw/zxFRHoHZCOXRtOylsXF+6LHI+Q==", - "dependencies": { - "@algolia/autocomplete-core": "1.17.7", - "@algolia/autocomplete-preset-algolia": "1.17.7", - "@docsearch/css": "3.8.0", - "algoliasearch": "^5.12.0" - }, - "peerDependencies": { - "@types/react": ">= 16.8.0 < 19.0.0", - "react": ">= 16.8.0 < 19.0.0", - "react-dom": ">= 16.8.0 < 19.0.0", - "search-insights": ">= 1 < 3" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "react": { - "optional": true - }, - "react-dom": { - "optional": true - }, - "search-insights": { - "optional": true - } - } - }, - "node_modules/@docsearch/react/node_modules/@algolia/client-analytics": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.15.0.tgz", - "integrity": "sha512-lho0gTFsQDIdCwyUKTtMuf9nCLwq9jOGlLGIeQGKDxXF7HbiAysFIu5QW/iQr1LzMgDyM9NH7K98KY+BiIFriQ==", - "dependencies": { - "@algolia/client-common": "5.15.0", - "@algolia/requester-browser-xhr": "5.15.0", - "@algolia/requester-fetch": "5.15.0", - "@algolia/requester-node-http": "5.15.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@docsearch/react/node_modules/@algolia/client-personalization": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.15.0.tgz", - "integrity": "sha512-LfaZqLUWxdYFq44QrasCDED5bSYOswpQjSiIL7Q5fYlefAAUO95PzBPKCfUhSwhb4rKxigHfDkd81AvEicIEoA==", - "dependencies": { - "@algolia/client-common": "5.15.0", - "@algolia/requester-browser-xhr": "5.15.0", - "@algolia/requester-fetch": "5.15.0", - "@algolia/requester-node-http": "5.15.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@docsearch/react/node_modules/@algolia/recommend": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.15.0.tgz", - "integrity": "sha512-5eupMwSqMLDObgSMF0XG958zR6GJP3f7jHDQ3/WlzCM9/YIJiWIUoJFGsko9GYsA5xbLDHE/PhWtq4chcCdaGQ==", - "dependencies": { - "@algolia/client-common": "5.15.0", - "@algolia/requester-browser-xhr": "5.15.0", - "@algolia/requester-fetch": "5.15.0", - "@algolia/requester-node-http": "5.15.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@docsearch/react/node_modules/algoliasearch": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.15.0.tgz", - "integrity": "sha512-Yf3Swz1s63hjvBVZ/9f2P1Uu48GjmjCN+Esxb6MAONMGtZB1fRX8/S1AhUTtsuTlcGovbYLxpHgc7wEzstDZBw==", - "dependencies": { - "@algolia/client-abtesting": "5.15.0", - "@algolia/client-analytics": "5.15.0", - "@algolia/client-common": "5.15.0", - "@algolia/client-insights": "5.15.0", - "@algolia/client-personalization": "5.15.0", - "@algolia/client-query-suggestions": "5.15.0", - "@algolia/client-search": "5.15.0", - "@algolia/ingestion": "1.15.0", - "@algolia/monitoring": "1.15.0", - "@algolia/recommend": "5.15.0", - "@algolia/requester-browser-xhr": "5.15.0", - "@algolia/requester-fetch": "5.15.0", - "@algolia/requester-node-http": "5.15.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@docusaurus/babel": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/@docusaurus/babel/-/babel-3.6.3.tgz", - "integrity": "sha512-7dW9Hat9EHYCVicFXYA4hjxBY38+hPuCURL8oRF9fySRm7vzNWuEOghA1TXcykuXZp0HLG2td4RhDxCvGG7tNw==", - "dependencies": { - "@babel/core": "^7.25.9", - "@babel/generator": "^7.25.9", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-transform-runtime": "^7.25.9", - "@babel/preset-env": "^7.25.9", - "@babel/preset-react": "^7.25.9", - "@babel/preset-typescript": "^7.25.9", - "@babel/runtime": "^7.25.9", - "@babel/runtime-corejs3": "^7.25.9", - "@babel/traverse": "^7.25.9", - "@docusaurus/logger": "3.6.3", - "@docusaurus/utils": "3.6.3", - "babel-plugin-dynamic-import-node": "^2.3.3", - "fs-extra": "^11.1.1", - "tslib": "^2.6.0" - }, - "engines": { - "node": ">=18.0" - } - }, - "node_modules/@docusaurus/bundler": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/@docusaurus/bundler/-/bundler-3.6.3.tgz", - "integrity": "sha512-47JLuc8D4wA+6VOvmMd5fUC9rFppBQpQOnxDYiVXffm/DeV/wmm3sbpNd5Y+O+G2+nevLTRnvCm/qyancv0Y3A==", - "dependencies": { - "@babel/core": "^7.25.9", - "@docusaurus/babel": "3.6.3", - "@docusaurus/cssnano-preset": "3.6.3", - "@docusaurus/logger": "3.6.3", - "@docusaurus/types": "3.6.3", - "@docusaurus/utils": "3.6.3", - "babel-loader": "^9.2.1", - "clean-css": "^5.3.2", - "copy-webpack-plugin": "^11.0.0", - "css-loader": "^6.8.1", - "css-minimizer-webpack-plugin": "^5.0.1", - "cssnano": "^6.1.2", - "file-loader": "^6.2.0", - "html-minifier-terser": "^7.2.0", - "mini-css-extract-plugin": "^2.9.1", - "null-loader": "^4.0.1", - "postcss": "^8.4.26", - "postcss-loader": "^7.3.3", - "postcss-preset-env": "^10.1.0", - "react-dev-utils": "^12.0.1", - "terser-webpack-plugin": "^5.3.9", - "tslib": "^2.6.0", - "url-loader": "^4.1.1", - "webpack": "^5.95.0", - "webpackbar": "^6.0.1" - }, - "engines": { - "node": ">=18.0" - }, - "peerDependencies": { - "@docusaurus/faster": "*" - }, - "peerDependenciesMeta": { - "@docusaurus/faster": { - "optional": true - } - } - }, - "node_modules/@docusaurus/core": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-3.6.3.tgz", - "integrity": "sha512-xL7FRY9Jr5DWqB6pEnqgKqcMPJOX5V0pgWXi5lCiih11sUBmcFKM7c3+GyxcVeeWFxyYSDP3grLTWqJoP4P9Vw==", - "dependencies": { - "@docusaurus/babel": "3.6.3", - "@docusaurus/bundler": "3.6.3", - "@docusaurus/logger": "3.6.3", - "@docusaurus/mdx-loader": "3.6.3", - "@docusaurus/utils": "3.6.3", - "@docusaurus/utils-common": "3.6.3", - "@docusaurus/utils-validation": "3.6.3", - "boxen": "^6.2.1", - "chalk": "^4.1.2", - "chokidar": "^3.5.3", - "cli-table3": "^0.6.3", - "combine-promises": "^1.1.0", - "commander": "^5.1.0", - "core-js": "^3.31.1", - "del": "^6.1.1", - "detect-port": "^1.5.1", - "escape-html": "^1.0.3", - "eta": "^2.2.0", - "eval": "^0.1.8", - "fs-extra": "^11.1.1", - "html-tags": "^3.3.1", - "html-webpack-plugin": "^5.6.0", - "leven": "^3.1.0", - "lodash": "^4.17.21", - "p-map": "^4.0.0", - "prompts": "^2.4.2", - "react-dev-utils": "^12.0.1", - "react-helmet-async": "^1.3.0", - "react-loadable": "npm:@docusaurus/react-loadable@6.0.0", - "react-loadable-ssr-addon-v5-slorber": "^1.0.1", - "react-router": "^5.3.4", - "react-router-config": "^5.1.1", - "react-router-dom": "^5.3.4", - "rtl-detect": "^1.0.4", - "semver": "^7.5.4", - "serve-handler": "^6.1.6", - "shelljs": "^0.8.5", - "tslib": "^2.6.0", - "update-notifier": "^6.0.2", - "webpack": "^5.95.0", - "webpack-bundle-analyzer": "^4.10.2", - "webpack-dev-server": "^4.15.2", - "webpack-merge": "^6.0.1" - }, - "bin": { - "docusaurus": "bin/docusaurus.mjs" - }, - "engines": { - "node": ">=18.0" - }, - "peerDependencies": { - "@mdx-js/react": "^3.0.0", - "react": "^18.0.0", - "react-dom": "^18.0.0" - } - }, - "node_modules/@docusaurus/core/node_modules/webpack-merge": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", - "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", - "dependencies": { - "clone-deep": "^4.0.1", - "flat": "^5.0.2", - "wildcard": "^2.0.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@docusaurus/cssnano-preset": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-3.6.3.tgz", - "integrity": "sha512-qP7SXrwZ+23GFJdPN4aIHQrZW+oH/7tzwEuc/RNL0+BdZdmIjYQqUxdXsjE4lFxLNZjj0eUrSNYIS6xwfij+5Q==", - "dependencies": { - "cssnano-preset-advanced": "^6.1.2", - "postcss": "^8.4.38", - "postcss-sort-media-queries": "^5.2.0", - "tslib": "^2.6.0" - }, - "engines": { - "node": ">=18.0" - } - }, - "node_modules/@docusaurus/logger": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-3.6.3.tgz", - "integrity": "sha512-xSubJixcNyMV9wMV4q0s47CBz3Rlc5jbcCCuij8pfQP8qn/DIpt0ks8W6hQWzHAedg/J/EwxxUOUrnEoKzJo8g==", - "dependencies": { - "chalk": "^4.1.2", - "tslib": "^2.6.0" - }, - "engines": { - "node": ">=18.0" - } - }, - "node_modules/@docusaurus/mdx-loader": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-3.6.3.tgz", - "integrity": "sha512-3iJdiDz9540ppBseeI93tWTDtUGVkxzh59nMq4ignylxMuXBLK8dFqVeaEor23v1vx6TrGKZ2FuLaTB+U7C0QQ==", - "dependencies": { - "@docusaurus/logger": "3.6.3", - "@docusaurus/utils": "3.6.3", - "@docusaurus/utils-validation": "3.6.3", - "@mdx-js/mdx": "^3.0.0", - "@slorber/remark-comment": "^1.0.0", - "escape-html": "^1.0.3", - "estree-util-value-to-estree": "^3.0.1", - "file-loader": "^6.2.0", - "fs-extra": "^11.1.1", - "image-size": "^1.0.2", - "mdast-util-mdx": "^3.0.0", - "mdast-util-to-string": "^4.0.0", - "rehype-raw": "^7.0.0", - "remark-directive": "^3.0.0", - "remark-emoji": "^4.0.0", - "remark-frontmatter": "^5.0.0", - "remark-gfm": "^4.0.0", - "stringify-object": "^3.3.0", - "tslib": "^2.6.0", - "unified": "^11.0.3", - "unist-util-visit": "^5.0.0", - "url-loader": "^4.1.1", - "vfile": "^6.0.1", - "webpack": "^5.88.1" - }, - "engines": { - "node": ">=18.0" - }, - "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" - } - }, - "node_modules/@docusaurus/module-type-aliases": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.6.3.tgz", - "integrity": "sha512-MjaXX9PN/k5ugNvfRZdWyKWq4FsrhN4LEXaj0pEmMebJuBNlFeGyKQUa9DRhJHpadNaiMLrbo9m3U7Ig5YlsZg==", - "dependencies": { - "@docusaurus/types": "3.6.3", - "@types/history": "^4.7.11", - "@types/react": "*", - "@types/react-router-config": "*", - "@types/react-router-dom": "*", - "react-helmet-async": "*", - "react-loadable": "npm:@docusaurus/react-loadable@6.0.0" - }, - "peerDependencies": { - "react": "*", - "react-dom": "*" - } - }, - "node_modules/@docusaurus/plugin-client-redirects": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-client-redirects/-/plugin-client-redirects-3.6.3.tgz", - "integrity": "sha512-fQDCxoJCO1jXNQGQmhgYoX3Yx+Z2xSbrLf3PBET6pHnsRk6gGW/VuCHcfQuZlJzbTxN0giQ5u3XcQQ/LzXftJA==", - "dependencies": { - "@docusaurus/core": "3.6.3", - "@docusaurus/logger": "3.6.3", - "@docusaurus/utils": "3.6.3", - "@docusaurus/utils-common": "3.6.3", - "@docusaurus/utils-validation": "3.6.3", - "eta": "^2.2.0", - "fs-extra": "^11.1.1", - "lodash": "^4.17.21", - "tslib": "^2.6.0" - }, - "engines": { - "node": ">=18.0" - }, - "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" - } - }, - "node_modules/@docusaurus/plugin-content-blog": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.6.3.tgz", - "integrity": "sha512-k0ogWwwJU3pFRFfvW1kRVHxzf2DutLGaaLjAnHVEU6ju+aRP0Z5ap/13DHyPOfHeE4WKpn/M0TqjdwZAcY3kAw==", - "dependencies": { - "@docusaurus/core": "3.6.3", - "@docusaurus/logger": "3.6.3", - "@docusaurus/mdx-loader": "3.6.3", - "@docusaurus/theme-common": "3.6.3", - "@docusaurus/types": "3.6.3", - "@docusaurus/utils": "3.6.3", - "@docusaurus/utils-common": "3.6.3", - "@docusaurus/utils-validation": "3.6.3", - "cheerio": "1.0.0-rc.12", - "feed": "^4.2.2", - "fs-extra": "^11.1.1", - "lodash": "^4.17.21", - "reading-time": "^1.5.0", - "srcset": "^4.0.0", - "tslib": "^2.6.0", - "unist-util-visit": "^5.0.0", - "utility-types": "^3.10.0", - "webpack": "^5.88.1" - }, - "engines": { - "node": ">=18.0" - }, - "peerDependencies": { - "@docusaurus/plugin-content-docs": "*", - "react": "^18.0.0", - "react-dom": "^18.0.0" - } - }, - "node_modules/@docusaurus/plugin-content-docs": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.6.3.tgz", - "integrity": "sha512-r2wS8y/fsaDcxkm20W5bbYJFPzdWdEaTWVYjNxlHlcmX086eqQR1Fomlg9BHTJ0dLXPzAlbC8EN4XqMr3QzNCQ==", - "dependencies": { - "@docusaurus/core": "3.6.3", - "@docusaurus/logger": "3.6.3", - "@docusaurus/mdx-loader": "3.6.3", - "@docusaurus/module-type-aliases": "3.6.3", - "@docusaurus/theme-common": "3.6.3", - "@docusaurus/types": "3.6.3", - "@docusaurus/utils": "3.6.3", - "@docusaurus/utils-common": "3.6.3", - "@docusaurus/utils-validation": "3.6.3", - "@types/react-router-config": "^5.0.7", - "combine-promises": "^1.1.0", - "fs-extra": "^11.1.1", - "js-yaml": "^4.1.0", - "lodash": "^4.17.21", - "tslib": "^2.6.0", - "utility-types": "^3.10.0", - "webpack": "^5.88.1" - }, - "engines": { - "node": ">=18.0" - }, - "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" - } - }, - "node_modules/@docusaurus/plugin-content-pages": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.6.3.tgz", - "integrity": "sha512-eHrmTgjgLZsuqfsYr5X2xEwyIcck0wseSofWrjTwT9FLOWp+KDmMAuVK+wRo7sFImWXZk3oV/xX/g9aZrhD7OA==", - "dependencies": { - "@docusaurus/core": "3.6.3", - "@docusaurus/mdx-loader": "3.6.3", - "@docusaurus/types": "3.6.3", - "@docusaurus/utils": "3.6.3", - "@docusaurus/utils-validation": "3.6.3", - "fs-extra": "^11.1.1", - "tslib": "^2.6.0", - "webpack": "^5.88.1" - }, - "engines": { - "node": ">=18.0" - }, - "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" - } - }, - "node_modules/@docusaurus/plugin-debug": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-3.6.3.tgz", - "integrity": "sha512-zB9GXfIZNPRfzKnNjU6xGVrqn9bPXuGhpjgsuc/YtcTDjnjhasg38NdYd5LEqXex5G/zIorQgWB3n6x/Ut62vQ==", - "dependencies": { - "@docusaurus/core": "3.6.3", - "@docusaurus/types": "3.6.3", - "@docusaurus/utils": "3.6.3", - "fs-extra": "^11.1.1", - "react-json-view-lite": "^1.2.0", - "tslib": "^2.6.0" - }, - "engines": { - "node": ">=18.0" - }, - "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" - } - }, - "node_modules/@docusaurus/plugin-google-analytics": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.6.3.tgz", - "integrity": "sha512-rCDNy1QW8Dag7nZq67pcum0bpFLrwvxJhYuVprhFh8BMBDxV0bY+bAkGHbSf68P3Bk9C3hNOAXX1srGLIDvcTA==", - "dependencies": { - "@docusaurus/core": "3.6.3", - "@docusaurus/types": "3.6.3", - "@docusaurus/utils-validation": "3.6.3", - "tslib": "^2.6.0" - }, - "engines": { - "node": ">=18.0" - }, - "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" - } - }, - "node_modules/@docusaurus/plugin-google-gtag": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.6.3.tgz", - "integrity": "sha512-+OyDvhM6rqVkQOmLVkQWVJAizEEfkPzVWtIHXlWPOCFGK9X4/AWeBSrU0WG4iMg9Z4zD4YDRrU+lvI4s6DSC+w==", - "dependencies": { - "@docusaurus/core": "3.6.3", - "@docusaurus/types": "3.6.3", - "@docusaurus/utils-validation": "3.6.3", - "@types/gtag.js": "^0.0.12", - "tslib": "^2.6.0" - }, - "engines": { - "node": ">=18.0" - }, - "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" - } - }, - "node_modules/@docusaurus/plugin-google-tag-manager": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.6.3.tgz", - "integrity": "sha512-1M6UPB13gWUtN2UHX083/beTn85PlRI9ABItTl/JL1FJ5dJTWWFXXsHf9WW/6hrVwthwTeV/AGbGKvLKV+IlCA==", - "dependencies": { - "@docusaurus/core": "3.6.3", - "@docusaurus/types": "3.6.3", - "@docusaurus/utils-validation": "3.6.3", - "tslib": "^2.6.0" - }, - "engines": { - "node": ">=18.0" - }, - "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" - } - }, - "node_modules/@docusaurus/plugin-sitemap": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.6.3.tgz", - "integrity": "sha512-94qOO4M9Fwv9KfVQJsgbe91k+fPJ4byf1L3Ez8TUa6TAFPo/BrLwQ80zclHkENlL1824TuxkcMKv33u6eydQCg==", - "dependencies": { - "@docusaurus/core": "3.6.3", - "@docusaurus/logger": "3.6.3", - "@docusaurus/types": "3.6.3", - "@docusaurus/utils": "3.6.3", - "@docusaurus/utils-common": "3.6.3", - "@docusaurus/utils-validation": "3.6.3", - "fs-extra": "^11.1.1", - "sitemap": "^7.1.1", - "tslib": "^2.6.0" - }, - "engines": { - "node": ">=18.0" - }, - "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" - } - }, - "node_modules/@docusaurus/preset-classic": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-3.6.3.tgz", - "integrity": "sha512-VHSYWROT3flvNNI1SrnMOtW1EsjeHNK9dhU6s9eY5hryZe79lUqnZJyze/ymDe2LXAqzyj6y5oYvyBoZZk6ErA==", - "dependencies": { - "@docusaurus/core": "3.6.3", - "@docusaurus/plugin-content-blog": "3.6.3", - "@docusaurus/plugin-content-docs": "3.6.3", - "@docusaurus/plugin-content-pages": "3.6.3", - "@docusaurus/plugin-debug": "3.6.3", - "@docusaurus/plugin-google-analytics": "3.6.3", - "@docusaurus/plugin-google-gtag": "3.6.3", - "@docusaurus/plugin-google-tag-manager": "3.6.3", - "@docusaurus/plugin-sitemap": "3.6.3", - "@docusaurus/theme-classic": "3.6.3", - "@docusaurus/theme-common": "3.6.3", - "@docusaurus/theme-search-algolia": "3.6.3", - "@docusaurus/types": "3.6.3" - }, - "engines": { - "node": ">=18.0" - }, - "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" - } - }, - "node_modules/@docusaurus/theme-classic": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-3.6.3.tgz", - "integrity": "sha512-1RRLK1tSArI2c00qugWYO3jRocjOZwGF1mBzPPylDVRwWCS/rnWWR91ChdbbaxIupRJ+hX8ZBYrwr5bbU0oztQ==", - "dependencies": { - "@docusaurus/core": "3.6.3", - "@docusaurus/logger": "3.6.3", - "@docusaurus/mdx-loader": "3.6.3", - "@docusaurus/module-type-aliases": "3.6.3", - "@docusaurus/plugin-content-blog": "3.6.3", - "@docusaurus/plugin-content-docs": "3.6.3", - "@docusaurus/plugin-content-pages": "3.6.3", - "@docusaurus/theme-common": "3.6.3", - "@docusaurus/theme-translations": "3.6.3", - "@docusaurus/types": "3.6.3", - "@docusaurus/utils": "3.6.3", - "@docusaurus/utils-common": "3.6.3", - "@docusaurus/utils-validation": "3.6.3", - "@mdx-js/react": "^3.0.0", - "clsx": "^2.0.0", - "copy-text-to-clipboard": "^3.2.0", - "infima": "0.2.0-alpha.45", - "lodash": "^4.17.21", - "nprogress": "^0.2.0", - "postcss": "^8.4.26", - "prism-react-renderer": "^2.3.0", - "prismjs": "^1.29.0", - "react-router-dom": "^5.3.4", - "rtlcss": "^4.1.0", - "tslib": "^2.6.0", - "utility-types": "^3.10.0" - }, - "engines": { - "node": ">=18.0" - }, - "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" - } - }, - "node_modules/@docusaurus/theme-common": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.6.3.tgz", - "integrity": "sha512-b8ZkhczXHDxWWyvz+YJy4t/PlPbEogTTbgnHoflYnH7rmRtyoodTsu8WVM12la5LmlMJBclBXFl29OH8kPE7gg==", - "dependencies": { - "@docusaurus/mdx-loader": "3.6.3", - "@docusaurus/module-type-aliases": "3.6.3", - "@docusaurus/utils": "3.6.3", - "@docusaurus/utils-common": "3.6.3", - "@types/history": "^4.7.11", - "@types/react": "*", - "@types/react-router-config": "*", - "clsx": "^2.0.0", - "parse-numeric-range": "^1.3.0", - "prism-react-renderer": "^2.3.0", - "tslib": "^2.6.0", - "utility-types": "^3.10.0" - }, - "engines": { - "node": ">=18.0" - }, - "peerDependencies": { - "@docusaurus/plugin-content-docs": "*", - "react": "^18.0.0", - "react-dom": "^18.0.0" - } - }, - "node_modules/@docusaurus/theme-search-algolia": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.6.3.tgz", - "integrity": "sha512-rt+MGCCpYgPyWCGXtbxlwFbTSobu15jWBTPI2LHsHNa5B0zSmOISX6FWYAPt5X1rNDOqMGM0FATnh7TBHRohVA==", - "dependencies": { - "@docsearch/react": "^3.5.2", - "@docusaurus/core": "3.6.3", - "@docusaurus/logger": "3.6.3", - "@docusaurus/plugin-content-docs": "3.6.3", - "@docusaurus/theme-common": "3.6.3", - "@docusaurus/theme-translations": "3.6.3", - "@docusaurus/utils": "3.6.3", - "@docusaurus/utils-validation": "3.6.3", - "algoliasearch": "^4.18.0", - "algoliasearch-helper": "^3.13.3", - "clsx": "^2.0.0", - "eta": "^2.2.0", - "fs-extra": "^11.1.1", - "lodash": "^4.17.21", - "tslib": "^2.6.0", - "utility-types": "^3.10.0" - }, - "engines": { - "node": ">=18.0" - }, - "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" - } - }, - "node_modules/@docusaurus/theme-translations": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-3.6.3.tgz", - "integrity": "sha512-Gb0regclToVlngSIIwUCtBMQBq48qVUaN1XQNKW4XwlsgUyk0vP01LULdqbem7czSwIeBAFXFoORJ0RPX7ht/w==", - "dependencies": { - "fs-extra": "^11.1.1", - "tslib": "^2.6.0" - }, - "engines": { - "node": ">=18.0" - } - }, - "node_modules/@docusaurus/types": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.6.3.tgz", - "integrity": "sha512-xD9oTGDrouWzefkhe9ogB2fDV96/82cRpNGx2HIvI5L87JHNhQVIWimQ/3JIiiX/TEd5S9s+VO6FFguwKNRVow==", - "dependencies": { - "@mdx-js/mdx": "^3.0.0", - "@types/history": "^4.7.11", - "@types/react": "*", - "commander": "^5.1.0", - "joi": "^17.9.2", - "react-helmet-async": "^1.3.0", - "utility-types": "^3.10.0", - "webpack": "^5.95.0", - "webpack-merge": "^5.9.0" - }, - "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" - } - }, - "node_modules/@docusaurus/utils": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-3.6.3.tgz", - "integrity": "sha512-0R/FR3bKVl4yl8QwbL4TYFfR+OXBRpVUaTJdENapBGR3YMwfM6/JnhGilWQO8AOwPJGtGoDK7ib8+8UF9f3OZQ==", - "dependencies": { - "@docusaurus/logger": "3.6.3", - "@docusaurus/types": "3.6.3", - "@docusaurus/utils-common": "3.6.3", - "@svgr/webpack": "^8.1.0", - "escape-string-regexp": "^4.0.0", - "file-loader": "^6.2.0", - "fs-extra": "^11.1.1", - "github-slugger": "^1.5.0", - "globby": "^11.1.0", - "gray-matter": "^4.0.3", - "jiti": "^1.20.0", - "js-yaml": "^4.1.0", - "lodash": "^4.17.21", - "micromatch": "^4.0.5", - "prompts": "^2.4.2", - "resolve-pathname": "^3.0.0", - "shelljs": "^0.8.5", - "tslib": "^2.6.0", - "url-loader": "^4.1.1", - "utility-types": "^3.10.0", - "webpack": "^5.88.1" - }, - "engines": { - "node": ">=18.0" - } - }, - "node_modules/@docusaurus/utils-common": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-3.6.3.tgz", - "integrity": "sha512-v4nKDaANLgT3pMBewHYEMAl/ufY0LkXao1QkFWzI5huWFOmNQ2UFzv2BiKeHX5Ownis0/w6cAyoxPhVdDonlSQ==", - "dependencies": { - "@docusaurus/types": "3.6.3", - "tslib": "^2.6.0" - }, - "engines": { - "node": ">=18.0" - } - }, - "node_modules/@docusaurus/utils-validation": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-3.6.3.tgz", - "integrity": "sha512-bhEGGiN5BE38h21vjqD70Gxg++j+PfYVddDUE5UFvLDup68QOcpD33CLr+2knPorlxRbEaNfz6HQDUMQ3HuqKw==", - "dependencies": { - "@docusaurus/logger": "3.6.3", - "@docusaurus/utils": "3.6.3", - "@docusaurus/utils-common": "3.6.3", - "fs-extra": "^11.2.0", - "joi": "^17.9.2", - "js-yaml": "^4.1.0", - "lodash": "^4.17.21", - "tslib": "^2.6.0" - }, - "engines": { - "node": ">=18.0" - } - }, - "node_modules/@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" - }, - "node_modules/@hapi/topo": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", - "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", - "dependencies": { - "@hapi/hoek": "^9.0.0" - } - }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", - "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==", - "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==", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", - "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" - }, - "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==", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@leichtgewicht/ip-codec": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", - "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==" - }, - "node_modules/@mdx-js/mdx": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.0.1.tgz", - "integrity": "sha512-eIQ4QTrOWyL3LWEe/bu6Taqzq2HQvHcyTMaOrI95P2/LmJE7AsfPfgJGuFLPVqBUE1BC1rik3VIhU+s9u72arA==", - "dependencies": { - "@types/estree": "^1.0.0", - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/mdx": "^2.0.0", - "collapse-white-space": "^2.0.0", - "devlop": "^1.0.0", - "estree-util-build-jsx": "^3.0.0", - "estree-util-is-identifier-name": "^3.0.0", - "estree-util-to-js": "^2.0.0", - "estree-walker": "^3.0.0", - "hast-util-to-estree": "^3.0.0", - "hast-util-to-jsx-runtime": "^2.0.0", - "markdown-extensions": "^2.0.0", - "periscopic": "^3.0.0", - "remark-mdx": "^3.0.0", - "remark-parse": "^11.0.0", - "remark-rehype": "^11.0.0", - "source-map": "^0.7.0", - "unified": "^11.0.0", - "unist-util-position-from-estree": "^2.0.0", - "unist-util-stringify-position": "^4.0.0", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/@mdx-js/react": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.1.0.tgz", - "integrity": "sha512-QjHtSaoameoalGnKDT3FoIl4+9RwyTmo9ZJGBdLOks/YOiWHoRDI3PUwEzOE7kEmGcV3AFcp9K6dYu9rEuKLAQ==", - "dependencies": { - "@types/mdx": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - }, - "peerDependencies": { - "@types/react": ">=16", - "react": ">=16" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@pnpm/config.env-replace": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz", - "integrity": "sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==", - "engines": { - "node": ">=12.22.0" - } - }, - "node_modules/@pnpm/network.ca-file": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz", - "integrity": "sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==", - "dependencies": { - "graceful-fs": "4.2.10" - }, - "engines": { - "node": ">=12.22.0" - } - }, - "node_modules/@pnpm/network.ca-file/node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" - }, - "node_modules/@pnpm/npm-conf": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-2.2.2.tgz", - "integrity": "sha512-UA91GwWPhFExt3IizW6bOeY/pQ0BkuNwKjk9iQW9KqxluGCrg4VenZ0/L+2Y0+ZOtme72EVvg6v0zo3AMQRCeA==", - "dependencies": { - "@pnpm/config.env-replace": "^1.1.0", - "@pnpm/network.ca-file": "^1.0.1", - "config-chain": "^1.1.11" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@polka/url": { - "version": "1.0.0-next.25", - "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.25.tgz", - "integrity": "sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==" - }, - "node_modules/@sideway/address": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", - "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", - "dependencies": { - "@hapi/hoek": "^9.0.0" - } - }, - "node_modules/@sideway/formula": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", - "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==" - }, - "node_modules/@sideway/pinpoint": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", - "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" - }, - "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==" - }, - "node_modules/@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" - } - }, - "node_modules/@slorber/remark-comment": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@slorber/remark-comment/-/remark-comment-1.0.0.tgz", - "integrity": "sha512-RCE24n7jsOj1M0UPvIQCHTe7fI0sFL4S2nwKVWwHyVr/wI/H8GosgsJGyhnsZoGFnD/P2hLf1mSbrrgSLN93NA==", - "dependencies": { - "micromark-factory-space": "^1.0.0", - "micromark-util-character": "^1.1.0", - "micromark-util-symbol": "^1.0.1" - } - }, - "node_modules/@svgr/babel-plugin-add-jsx-attribute": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", - "integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==", - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/babel-plugin-remove-jsx-attribute": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz", - "integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==", - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz", - "integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==", - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz", - "integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==", - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/babel-plugin-svg-dynamic-title": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz", - "integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==", - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/babel-plugin-svg-em-dimensions": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz", - "integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==", - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/babel-plugin-transform-react-native-svg": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz", - "integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==", - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/babel-plugin-transform-svg-component": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz", - "integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==", - "engines": { - "node": ">=12" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/babel-preset": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz", - "integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==", - "dependencies": { - "@svgr/babel-plugin-add-jsx-attribute": "8.0.0", - "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0", - "@svgr/babel-plugin-remove-jsx-empty-expression": "8.0.0", - "@svgr/babel-plugin-replace-jsx-attribute-value": "8.0.0", - "@svgr/babel-plugin-svg-dynamic-title": "8.0.0", - "@svgr/babel-plugin-svg-em-dimensions": "8.0.0", - "@svgr/babel-plugin-transform-react-native-svg": "8.1.0", - "@svgr/babel-plugin-transform-svg-component": "8.0.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/core": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", - "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", - "dependencies": { - "@babel/core": "^7.21.3", - "@svgr/babel-preset": "8.1.0", - "camelcase": "^6.2.0", - "cosmiconfig": "^8.1.3", - "snake-case": "^3.0.4" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@svgr/hast-util-to-babel-ast": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz", - "integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==", - "dependencies": { - "@babel/types": "^7.21.3", - "entities": "^4.4.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@svgr/plugin-jsx": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz", - "integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==", - "dependencies": { - "@babel/core": "^7.21.3", - "@svgr/babel-preset": "8.1.0", - "@svgr/hast-util-to-babel-ast": "8.0.0", - "svg-parser": "^2.0.4" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@svgr/core": "*" - } - }, - "node_modules/@svgr/plugin-svgo": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-8.1.0.tgz", - "integrity": "sha512-Ywtl837OGO9pTLIN/onoWLmDQ4zFUycI1g76vuKGEz6evR/ZTJlJuz3G/fIkb6OVBJ2g0o6CGJzaEjfmEo3AHA==", - "dependencies": { - "cosmiconfig": "^8.1.3", - "deepmerge": "^4.3.1", - "svgo": "^3.0.2" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@svgr/core": "*" - } - }, - "node_modules/@svgr/webpack": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-8.1.0.tgz", - "integrity": "sha512-LnhVjMWyMQV9ZmeEy26maJk+8HTIbd59cH4F2MJ439k9DqejRisfFNGAPvRYlKETuh9LrImlS8aKsBgKjMA8WA==", - "dependencies": { - "@babel/core": "^7.21.3", - "@babel/plugin-transform-react-constant-elements": "^7.21.3", - "@babel/preset-env": "^7.20.2", - "@babel/preset-react": "^7.18.6", - "@babel/preset-typescript": "^7.21.0", - "@svgr/core": "8.1.0", - "@svgr/plugin-jsx": "8.1.0", - "@svgr/plugin-svgo": "8.1.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@szmarczak/http-timer": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", - "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", - "dependencies": { - "defer-to-connect": "^2.0.1" - }, - "engines": { - "node": ">=14.16" - } - }, - "node_modules/@trysound/sax": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", - "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/@types/acorn": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@types/acorn/-/acorn-4.0.6.tgz", - "integrity": "sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==", - "dependencies": { - "@types/estree": "*" - } - }, - "node_modules/@types/body-parser": { - "version": "1.19.5", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", - "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "node_modules/@types/bonjour": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", - "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/connect": { - "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", - "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/connect-history-api-fallback": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", - "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", - "dependencies": { - "@types/express-serve-static-core": "*", - "@types/node": "*" - } - }, - "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==", - "dependencies": { - "@types/ms": "*" - } - }, - "node_modules/@types/eslint": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", - "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/eslint-scope": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", - "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, - "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==" - }, - "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==", - "dependencies": { - "@types/estree": "*" - } - }, - "node_modules/@types/express": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", - "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "node_modules/@types/express-serve-static-core": { - "version": "4.19.5", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.5.tgz", - "integrity": "sha512-y6W03tvrACO72aijJ5uF02FRq5cgDR9lUxddQ8vyF+GvmjJQqbzDcJngEjURc+ZsG31VI3hODNZJ2URj86pzmg==", - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, - "node_modules/@types/gtag.js": { - "version": "0.0.12", - "resolved": "https://registry.npmjs.org/@types/gtag.js/-/gtag.js-0.0.12.tgz", - "integrity": "sha512-YQV9bUsemkzG81Ea295/nF/5GijnD2Af7QhEofh7xu+kvCN6RdodgNwwGWXB5GMI3NoyvQo0odNctoH/qLMIpg==" - }, - "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==", - "dependencies": { - "@types/unist": "*" - } - }, - "node_modules/@types/history": { - "version": "4.7.11", - "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", - "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==" - }, - "node_modules/@types/html-minifier-terser": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", - "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==" - }, - "node_modules/@types/http-cache-semantics": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", - "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==" - }, - "node_modules/@types/http-errors": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", - "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==" - }, - "node_modules/@types/http-proxy": { - "version": "1.17.14", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.14.tgz", - "integrity": "sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==" - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", - "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" - }, - "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==", - "dependencies": { - "@types/unist": "*" - } - }, - "node_modules/@types/mdx": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.13.tgz", - "integrity": "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==" - }, - "node_modules/@types/mime": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", - "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==" - }, - "node_modules/@types/ms": { - "version": "0.7.34", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz", - "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==" - }, - "node_modules/@types/node": { - "version": "20.14.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.10.tgz", - "integrity": "sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ==", - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@types/node-forge": { - "version": "1.3.11", - "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", - "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==", - "dependencies": { - "@types/node": "*" - } - }, - "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==" - }, - "node_modules/@types/prismjs": { - "version": "1.26.4", - "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.4.tgz", - "integrity": "sha512-rlAnzkW2sZOjbqZ743IHUhFcvzaGbqijwOu8QZnZCjfQzBqFE3s4lOTJEsxikImav9uzz/42I+O7YUs1mWgMlg==" - }, - "node_modules/@types/prop-types": { - "version": "15.7.12", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", - "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==" - }, - "node_modules/@types/qs": { - "version": "6.9.15", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz", - "integrity": "sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==" - }, - "node_modules/@types/range-parser": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==" - }, - "node_modules/@types/react": { - "version": "18.3.3", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz", - "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==", - "dependencies": { - "@types/prop-types": "*", - "csstype": "^3.0.2" - } - }, - "node_modules/@types/react-router": { - "version": "5.1.20", - "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz", - "integrity": "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==", - "dependencies": { - "@types/history": "^4.7.11", - "@types/react": "*" - } - }, - "node_modules/@types/react-router-config": { - "version": "5.0.11", - "resolved": "https://registry.npmjs.org/@types/react-router-config/-/react-router-config-5.0.11.tgz", - "integrity": "sha512-WmSAg7WgqW7m4x8Mt4N6ZyKz0BubSj/2tVUMsAHp+Yd2AMwcSbeFq9WympT19p5heCFmF97R9eD5uUR/t4HEqw==", - "dependencies": { - "@types/history": "^4.7.11", - "@types/react": "*", - "@types/react-router": "^5.1.0" - } - }, - "node_modules/@types/react-router-dom": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz", - "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==", - "dependencies": { - "@types/history": "^4.7.11", - "@types/react": "*", - "@types/react-router": "*" - } - }, - "node_modules/@types/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==" - }, - "node_modules/@types/sax": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz", - "integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/send": { - "version": "0.17.4", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", - "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", - "dependencies": { - "@types/mime": "^1", - "@types/node": "*" - } - }, - "node_modules/@types/serve-index": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", - "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", - "dependencies": { - "@types/express": "*" - } - }, - "node_modules/@types/serve-static": { - "version": "1.15.7", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", - "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", - "dependencies": { - "@types/http-errors": "*", - "@types/node": "*", - "@types/send": "*" - } - }, - "node_modules/@types/sockjs": { - "version": "0.3.36", - "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", - "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/unist": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", - "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==" - }, - "node_modules/@types/ws": { - "version": "8.5.10", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", - "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "21.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==" - }, - "node_modules/@ungap/structured-clone": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==" - }, - "node_modules/@webassemblyjs/ast": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", - "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", - "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6" - } - }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", - "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==" - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", - "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==" - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", - "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==" - }, - "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", - "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", - "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", - "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==" - }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", - "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", - "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.12.1" - } - }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", - "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", - "dependencies": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", - "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", - "dependencies": { - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", - "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==" - }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", - "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", - "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/helper-wasm-section": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-opt": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1", - "@webassemblyjs/wast-printer": "1.12.1" - } - }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", - "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", - "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" - } - }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", - "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", - "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1" - } - }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", - "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", - "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-api-error": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" - } - }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", - "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", - "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==" - }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/accepts/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/accepts/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.3.3", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.3.tgz", - "integrity": "sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==", - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/address": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/address/-/address-1.2.2.tgz", - "integrity": "sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ajv": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.16.0.tgz", - "integrity": "sha512-F0twR8U1ZU67JIEtekUcLkXkoO5mMMmgGD8sK/xUFzJ805jxHQl92hImFAqqXMyMYjSPOyUPAwHYhB72g5sTXw==", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.4.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, - "node_modules/algoliasearch": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.24.0.tgz", - "integrity": "sha512-bf0QV/9jVejssFBmz2HQLxUadxk574t4iwjCKp5E7NBzwKkrDEhKPISIIjAU/p6K5qDx3qoeh4+26zWN1jmw3g==", - "dependencies": { - "@algolia/cache-browser-local-storage": "4.24.0", - "@algolia/cache-common": "4.24.0", - "@algolia/cache-in-memory": "4.24.0", - "@algolia/client-account": "4.24.0", - "@algolia/client-analytics": "4.24.0", - "@algolia/client-common": "4.24.0", - "@algolia/client-personalization": "4.24.0", - "@algolia/client-search": "4.24.0", - "@algolia/logger-common": "4.24.0", - "@algolia/logger-console": "4.24.0", - "@algolia/recommend": "4.24.0", - "@algolia/requester-browser-xhr": "4.24.0", - "@algolia/requester-common": "4.24.0", - "@algolia/requester-node-http": "4.24.0", - "@algolia/transporter": "4.24.0" - } - }, - "node_modules/algoliasearch-helper": { - "version": "3.22.5", - "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.22.5.tgz", - "integrity": "sha512-lWvhdnc+aKOKx8jyA3bsdEgHzm/sglC4cYdMG4xSQyRiPLJVJtH/IVYZG3Hp6PkTEhQqhyVYkeP9z2IlcHJsWw==", - "dependencies": { - "@algolia/events": "^4.0.1" - }, - "peerDependencies": { - "algoliasearch": ">= 3.1 < 6" - } - }, - "node_modules/algoliasearch/node_modules/@algolia/client-common": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", - "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", - "dependencies": { - "@algolia/requester-common": "4.24.0", - "@algolia/transporter": "4.24.0" - } - }, - "node_modules/algoliasearch/node_modules/@algolia/client-search": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.24.0.tgz", - "integrity": "sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==", - "dependencies": { - "@algolia/client-common": "4.24.0", - "@algolia/requester-common": "4.24.0", - "@algolia/transporter": "4.24.0" - } - }, - "node_modules/algoliasearch/node_modules/@algolia/requester-browser-xhr": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.24.0.tgz", - "integrity": "sha512-Z2NxZMb6+nVXSjF13YpjYTdvV3032YTBSGm2vnYvYPA6mMxzM3v5rsCiSspndn9rzIW4Qp1lPHBvuoKJV6jnAA==", - "dependencies": { - "@algolia/requester-common": "4.24.0" - } - }, - "node_modules/algoliasearch/node_modules/@algolia/requester-node-http": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.24.0.tgz", - "integrity": "sha512-JF18yTjNOVYvU/L3UosRcvbPMGT9B+/GQWNWnenIImglzNVGpyzChkXLnrSf6uxwVNO6ESGu6oN8MqcGQcjQJw==", - "dependencies": { - "@algolia/requester-common": "4.24.0" - } - }, - "node_modules/ansi-align": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", - "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", - "dependencies": { - "string-width": "^4.1.0" - } - }, - "node_modules/ansi-align/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "node_modules/ansi-align/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-escapes/node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-html-community": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", - "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", - "engines": [ - "node >= 0.8.0" - ], - "bin": { - "ansi-html": "bin/ansi-html" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/arg": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==" - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "engines": { - "node": ">=8" - } - }, - "node_modules/astring": { - "version": "1.8.6", - "resolved": "https://registry.npmjs.org/astring/-/astring-1.8.6.tgz", - "integrity": "sha512-ISvCdHdlTDlH5IpxQJIex7BWBywFWgjJSVdwst+/iQCoEYnyOaQ95+X1JGshuBjGp6nxKUy1jMgE3zPqN7fQdg==", - "bin": { - "astring": "bin/astring" - } - }, - "node_modules/at-least-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/autoprefixer": { - "version": "10.4.20", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", - "integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "browserslist": "^4.23.3", - "caniuse-lite": "^1.0.30001646", - "fraction.js": "^4.3.7", - "normalize-range": "^0.1.2", - "picocolors": "^1.0.1", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/babel-loader": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.2.1.tgz", - "integrity": "sha512-fqe8naHt46e0yIdkjUZYqddSXfej3AHajX+CSO5X7oy0EmPc6o5Xh+RClNoHjnieWz9AW4kZxW9yyFMhVB1QLA==", - "dependencies": { - "find-cache-dir": "^4.0.0", - "schema-utils": "^4.0.0" - }, - "engines": { - "node": ">= 14.15.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0", - "webpack": ">=5" - } - }, - "node_modules/babel-plugin-dynamic-import-node": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", - "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", - "dependencies": { - "object.assign": "^4.1.0" - } - }, - "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.11", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz", - "integrity": "sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==", - "dependencies": { - "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.6.2", - "semver": "^6.3.1" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.10.6", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz", - "integrity": "sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.2", - "core-js-compat": "^3.38.0" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.2.tgz", - "integrity": "sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.2" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/bail": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", - "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "node_modules/batch": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", - "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==" - }, - "node_modules/big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "engines": { - "node": "*" - } - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/body-parser/node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/bonjour-service": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.2.1.tgz", - "integrity": "sha512-oSzCS2zV14bh2kji6vNe7vrpJYCHGvcZnlffFQ1MEoX/WOeQ/teD8SYWKR942OI3INjq8OMNJlbPK5LLLUxFDw==", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "multicast-dns": "^7.2.5" - } - }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" - }, - "node_modules/boxen": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-6.2.1.tgz", - "integrity": "sha512-H4PEsJXfFI/Pt8sjDWbHlQPx4zL/bvSQjcilJmaulGt5mLDorHOHpmdXAJcBcmru7PhYSp/cDMWRko4ZUMFkSw==", - "dependencies": { - "ansi-align": "^3.0.1", - "camelcase": "^6.2.0", - "chalk": "^4.1.2", - "cli-boxes": "^3.0.0", - "string-width": "^5.0.1", - "type-fest": "^2.5.0", - "widest-line": "^4.0.1", - "wrap-ansi": "^8.0.1" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "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==", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.24.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", - "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", - "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" - } - ], - "dependencies": { - "caniuse-lite": "^1.0.30001669", - "electron-to-chromium": "^1.5.41", - "node-releases": "^2.0.18", - "update-browserslist-db": "^1.1.1" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" - }, - "node_modules/bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/cacheable-lookup": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", - "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", - "engines": { - "node": ">=14.16" - } - }, - "node_modules/cacheable-request": { - "version": "10.2.14", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz", - "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==", - "dependencies": { - "@types/http-cache-semantics": "^4.0.2", - "get-stream": "^6.0.1", - "http-cache-semantics": "^4.1.1", - "keyv": "^4.5.3", - "mimic-response": "^4.0.0", - "normalize-url": "^8.0.0", - "responselike": "^3.0.0" - }, - "engines": { - "node": ">=14.16" - } - }, - "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/camel-case": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", - "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", - "dependencies": { - "pascal-case": "^3.1.2", - "tslib": "^2.0.3" - } - }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/caniuse-api": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", - "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", - "dependencies": { - "browserslist": "^4.0.0", - "caniuse-lite": "^1.0.0", - "lodash.memoize": "^4.1.2", - "lodash.uniq": "^4.5.0" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001684", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001684.tgz", - "integrity": "sha512-G1LRwLIQjBQoyq0ZJGqGIJUXzJ8irpbjHLpVRXDvBEScFJ9b17sgK6vlx0GAJFE21okD7zXl08rRRUfq6HdoEQ==", - "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" - } - ] - }, - "node_modules/ccount": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", - "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "engines": { - "node": ">=10" - } - }, - "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==", - "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==", - "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==", - "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==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/cheerio": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", - "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", - "dependencies": { - "cheerio-select": "^2.1.0", - "dom-serializer": "^2.0.0", - "domhandler": "^5.0.3", - "domutils": "^3.0.1", - "htmlparser2": "^8.0.1", - "parse5": "^7.0.0", - "parse5-htmlparser2-tree-adapter": "^7.0.0" - }, - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/cheeriojs/cheerio?sponsor=1" - } - }, - "node_modules/cheerio-select": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", - "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", - "dependencies": { - "boolbase": "^1.0.0", - "css-select": "^5.1.0", - "css-what": "^6.1.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chrome-trace-event": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", - "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", - "engines": { - "node": ">=6.0" - } - }, - "node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "engines": { - "node": ">=8" - } - }, - "node_modules/clean-css": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", - "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", - "dependencies": { - "source-map": "~0.6.0" - }, - "engines": { - "node": ">= 10.0" - } - }, - "node_modules/clean-css/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "engines": { - "node": ">=6" - } - }, - "node_modules/cli-boxes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", - "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-table3": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", - "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", - "dependencies": { - "string-width": "^4.2.0" - }, - "engines": { - "node": "10.* || >= 12.*" - }, - "optionalDependencies": { - "@colors/colors": "1.5.0" - } - }, - "node_modules/cli-table3/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "node_modules/cli-table3/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "engines": { - "node": ">=6" - } - }, - "node_modules/collapse-white-space": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz", - "integrity": "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/colord": { - "version": "2.9.3", - "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", - "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==" - }, - "node_modules/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==" - }, - "node_modules/combine-promises": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/combine-promises/-/combine-promises-1.2.0.tgz", - "integrity": "sha512-VcQB1ziGD0NXrhKxiwyNbCDmRzs/OShMs2GqW2DlU2A/Sd0nQxE1oWDAE5O0ygSx5mgQOn9eIFh7yKPgFRVkPQ==", - "engines": { - "node": ">=10" - } - }, - "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==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/commander": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", - "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", - "engines": { - "node": ">= 6" - } - }, - "node_modules/common-path-prefix": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", - "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==" - }, - "node_modules/compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "dependencies": { - "mime-db": ">= 1.43.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/compressible/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/compression": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", - "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", - "dependencies": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", - "debug": "2.6.9", - "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/compression/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/compression/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/compression/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" - }, - "node_modules/config-chain": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", - "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", - "dependencies": { - "ini": "^1.3.4", - "proto-list": "~1.2.1" - } - }, - "node_modules/configstore": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-6.0.0.tgz", - "integrity": "sha512-cD31W1v3GqUlQvbBCGcXmd2Nj9SvLDOP1oQ0YFuLETufzSPaKp11rYBsSOm7rCsW3OnIRAFM3OxRhceaXNYHkA==", - "dependencies": { - "dot-prop": "^6.0.1", - "graceful-fs": "^4.2.6", - "unique-string": "^3.0.0", - "write-file-atomic": "^3.0.3", - "xdg-basedir": "^5.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/yeoman/configstore?sponsor=1" - } - }, - "node_modules/connect-history-api-fallback": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", - "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/consola": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/consola/-/consola-3.2.3.tgz", - "integrity": "sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==", - "engines": { - "node": "^14.18.0 || >=16.10.0" - } - }, - "node_modules/content-disposition": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", - "integrity": "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "engines": { - "node": ">= 0.6" - } - }, - "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==" - }, - "node_modules/cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" - }, - "node_modules/copy-text-to-clipboard": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/copy-text-to-clipboard/-/copy-text-to-clipboard-3.2.0.tgz", - "integrity": "sha512-RnJFp1XR/LOBDckxTib5Qjr/PMfkatD0MUCQgdpqS8MdKiNUzBjAQBEN6oUy+jW7LI93BBG3DtMB2KOOKpGs2Q==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/copy-webpack-plugin": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", - "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==", - "dependencies": { - "fast-glob": "^3.2.11", - "glob-parent": "^6.0.1", - "globby": "^13.1.1", - "normalize-path": "^3.0.0", - "schema-utils": "^4.0.0", - "serialize-javascript": "^6.0.0" - }, - "engines": { - "node": ">= 14.15.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - } - }, - "node_modules/copy-webpack-plugin/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/copy-webpack-plugin/node_modules/globby": { - "version": "13.2.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz", - "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==", - "dependencies": { - "dir-glob": "^3.0.1", - "fast-glob": "^3.3.0", - "ignore": "^5.2.4", - "merge2": "^1.4.1", - "slash": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/copy-webpack-plugin/node_modules/slash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", - "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/core-js": { - "version": "3.37.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.37.1.tgz", - "integrity": "sha512-Xn6qmxrQZyB0FFY8E3bgRXei3lWDJHhvI+u0q9TKIYM49G8pAr0FgnnrFRAmsbptZL1yxRADVXn+x5AGsbBfyw==", - "hasInstallScript": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-js-compat": { - "version": "3.39.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.39.0.tgz", - "integrity": "sha512-VgEUx3VwlExr5no0tXlBt+silBvhTryPwCXRI2Id1PN8WTKu7MreethvddqOubrYxkFdv/RnYrqlv1sFNAUelw==", - "dependencies": { - "browserslist": "^4.24.2" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-js-pure": { - "version": "3.39.0", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.39.0.tgz", - "integrity": "sha512-7fEcWwKI4rJinnK+wLTezeg2smbFFdSBP6E2kQZNbnzM2s1rpKQ6aaRteZSSg7FLU3P0HGGVo/gbpfanU36urg==", - "hasInstallScript": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" - }, - "node_modules/cosmiconfig": { - "version": "8.3.6", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", - "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", - "dependencies": { - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0", - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/crypto-random-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-4.0.0.tgz", - "integrity": "sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==", - "dependencies": { - "type-fest": "^1.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/crypto-random-string/node_modules/type-fest": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", - "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/css-blank-pseudo": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-7.0.1.tgz", - "integrity": "sha512-jf+twWGDf6LDoXDUode+nc7ZlrqfaNphrBIBrcmeP3D8yw1uPaix1gCC8LUQUGQ6CycuK2opkbFFWFuq/a94ag==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/css-blank-pseudo/node_modules/postcss-selector-parser": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", - "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/css-declaration-sorter": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.2.0.tgz", - "integrity": "sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==", - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.0.9" - } - }, - "node_modules/css-has-pseudo": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-7.0.1.tgz", - "integrity": "sha512-EOcoyJt+OsuKfCADgLT7gADZI5jMzIe/AeI6MeAYKiFBDmNmM7kk46DtSfMj5AohUJisqVzopBpnQTlvbyaBWg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/selector-specificity": "^5.0.0", - "postcss-selector-parser": "^7.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/css-has-pseudo/node_modules/@csstools/selector-specificity": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", - "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss-selector-parser": "^7.0.0" - } - }, - "node_modules/css-has-pseudo/node_modules/postcss-selector-parser": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", - "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/css-loader": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz", - "integrity": "sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==", - "dependencies": { - "icss-utils": "^5.1.0", - "postcss": "^8.4.33", - "postcss-modules-extract-imports": "^3.1.0", - "postcss-modules-local-by-default": "^4.0.5", - "postcss-modules-scope": "^3.2.0", - "postcss-modules-values": "^4.0.0", - "postcss-value-parser": "^4.2.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "@rspack/core": "0.x || 1.x", - "webpack": "^5.0.0" - }, - "peerDependenciesMeta": { - "@rspack/core": { - "optional": true - }, - "webpack": { - "optional": true - } - } - }, - "node_modules/css-minimizer-webpack-plugin": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-5.0.1.tgz", - "integrity": "sha512-3caImjKFQkS+ws1TGcFn0V1HyDJFq1Euy589JlD6/3rV2kj+w7r5G9WDMgSHvpvXHNZ2calVypZWuEDQd9wfLg==", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.18", - "cssnano": "^6.0.1", - "jest-worker": "^29.4.3", - "postcss": "^8.4.24", - "schema-utils": "^4.0.1", - "serialize-javascript": "^6.0.1" - }, - "engines": { - "node": ">= 14.15.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.0.0" - }, - "peerDependenciesMeta": { - "@parcel/css": { - "optional": true - }, - "@swc/css": { - "optional": true - }, - "clean-css": { - "optional": true - }, - "csso": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "lightningcss": { - "optional": true - } - } - }, - "node_modules/css-prefers-color-scheme": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-10.0.0.tgz", - "integrity": "sha512-VCtXZAWivRglTZditUfB4StnsWr6YVZ2PRtuxQLKTNRdtAf8tpzaVPE9zXIF3VaSc7O70iK/j1+NXxyQCqdPjQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/css-select": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", - "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.1.0", - "domhandler": "^5.0.2", - "domutils": "^3.0.1", - "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/css-tree": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", - "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", - "dependencies": { - "mdn-data": "2.0.30", - "source-map-js": "^1.0.1" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" - } - }, - "node_modules/css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/cssdb": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-8.2.1.tgz", - "integrity": "sha512-KwEPys7lNsC8OjASI8RrmwOYYDcm0JOW9zQhcV83ejYcQkirTEyeAGui8aO2F5PiS6SLpxuTzl6qlMElIdsgIg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - } - ] - }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/cssnano": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.1.2.tgz", - "integrity": "sha512-rYk5UeX7VAM/u0lNqewCdasdtPK81CgX8wJFLEIXHbV2oldWRgJAsZrdhRXkV1NJzA2g850KiFm9mMU2HxNxMA==", - "dependencies": { - "cssnano-preset-default": "^6.1.2", - "lilconfig": "^3.1.1" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/cssnano" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/cssnano-preset-advanced": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/cssnano-preset-advanced/-/cssnano-preset-advanced-6.1.2.tgz", - "integrity": "sha512-Nhao7eD8ph2DoHolEzQs5CfRpiEP0xa1HBdnFZ82kvqdmbwVBUr2r1QuQ4t1pi+D1ZpqpcO4T+wy/7RxzJ/WPQ==", - "dependencies": { - "autoprefixer": "^10.4.19", - "browserslist": "^4.23.0", - "cssnano-preset-default": "^6.1.2", - "postcss-discard-unused": "^6.0.5", - "postcss-merge-idents": "^6.0.3", - "postcss-reduce-idents": "^6.0.3", - "postcss-zindex": "^6.0.2" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/cssnano-preset-default": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.1.2.tgz", - "integrity": "sha512-1C0C+eNaeN8OcHQa193aRgYexyJtU8XwbdieEjClw+J9d94E41LwT6ivKH0WT+fYwYWB0Zp3I3IZ7tI/BbUbrg==", - "dependencies": { - "browserslist": "^4.23.0", - "css-declaration-sorter": "^7.2.0", - "cssnano-utils": "^4.0.2", - "postcss-calc": "^9.0.1", - "postcss-colormin": "^6.1.0", - "postcss-convert-values": "^6.1.0", - "postcss-discard-comments": "^6.0.2", - "postcss-discard-duplicates": "^6.0.3", - "postcss-discard-empty": "^6.0.3", - "postcss-discard-overridden": "^6.0.2", - "postcss-merge-longhand": "^6.0.5", - "postcss-merge-rules": "^6.1.1", - "postcss-minify-font-values": "^6.1.0", - "postcss-minify-gradients": "^6.0.3", - "postcss-minify-params": "^6.1.0", - "postcss-minify-selectors": "^6.0.4", - "postcss-normalize-charset": "^6.0.2", - "postcss-normalize-display-values": "^6.0.2", - "postcss-normalize-positions": "^6.0.2", - "postcss-normalize-repeat-style": "^6.0.2", - "postcss-normalize-string": "^6.0.2", - "postcss-normalize-timing-functions": "^6.0.2", - "postcss-normalize-unicode": "^6.1.0", - "postcss-normalize-url": "^6.0.2", - "postcss-normalize-whitespace": "^6.0.2", - "postcss-ordered-values": "^6.0.2", - "postcss-reduce-initial": "^6.1.0", - "postcss-reduce-transforms": "^6.0.2", - "postcss-svgo": "^6.0.3", - "postcss-unique-selectors": "^6.0.4" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/cssnano-utils": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-4.0.2.tgz", - "integrity": "sha512-ZR1jHg+wZ8o4c3zqf1SIUSTIvm/9mU343FMR6Obe/unskbvpGhZOo1J6d/r8D1pzkRQYuwbcH3hToOuoA2G7oQ==", - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/csso": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", - "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", - "dependencies": { - "css-tree": "~2.2.0" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/csso/node_modules/css-tree": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", - "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", - "dependencies": { - "mdn-data": "2.0.28", - "source-map-js": "^1.0.1" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/csso/node_modules/mdn-data": { - "version": "2.0.28", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", - "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==" - }, - "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" - }, - "node_modules/debounce": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", - "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==" - }, - "node_modules/debug": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", - "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", - "dependencies": { - "ms": "2.1.2" - }, - "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==", - "dependencies": { - "character-entities": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/decompress-response/node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/default-gateway": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", - "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", - "dependencies": { - "execa": "^5.0.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/defer-to-connect": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", - "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", - "engines": { - "node": ">=10" - } - }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-lazy-prop": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", - "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", - "engines": { - "node": ">=8" - } - }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/del": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/del/-/del-6.1.1.tgz", - "integrity": "sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg==", - "dependencies": { - "globby": "^11.0.1", - "graceful-fs": "^4.2.4", - "is-glob": "^4.0.1", - "is-path-cwd": "^2.2.0", - "is-path-inside": "^3.0.2", - "p-map": "^4.0.0", - "rimraf": "^3.0.2", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "engines": { - "node": ">=6" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/detect-node": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", - "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==" - }, - "node_modules/detect-port": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/detect-port/-/detect-port-1.6.1.tgz", - "integrity": "sha512-CmnVc+Hek2egPx1PeTFVta2W78xy2K/9Rkf6cC4T59S50tVnzKj+tnx5mmx5lwvCkujZ4uRrpRSuV+IVs3f90Q==", - "dependencies": { - "address": "^1.0.1", - "debug": "4" - }, - "bin": { - "detect": "bin/detect-port.js", - "detect-port": "bin/detect-port.js" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/detect-port-alt": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/detect-port-alt/-/detect-port-alt-1.1.6.tgz", - "integrity": "sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q==", - "dependencies": { - "address": "^1.0.1", - "debug": "^2.6.0" - }, - "bin": { - "detect": "bin/detect-port", - "detect-port": "bin/detect-port" - }, - "engines": { - "node": ">= 4.2.1" - } - }, - "node_modules/detect-port-alt/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/detect-port-alt/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "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==", - "dependencies": { - "dequal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/dns-packet": { - "version": "5.6.1", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", - "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", - "dependencies": { - "@leichtgewicht/ip-codec": "^2.0.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/docusaurus-theme-github-codeblock": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/docusaurus-theme-github-codeblock/-/docusaurus-theme-github-codeblock-2.0.2.tgz", - "integrity": "sha512-H2WoQPWOLjGZO6KS58Gsd+eUVjTFJemkReiSSu9chqokyLc/3Ih3+zPRYfuEZ/HsDvSMIarf7CNcp+Vt+/G+ig==", - "dependencies": { - "@docusaurus/types": "^3.0.0" - } - }, - "node_modules/dom-converter": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", - "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", - "dependencies": { - "utila": "~0.4" - } - }, - "node_modules/dom-serializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ] - }, - "node_modules/domhandler": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "dependencies": { - "domelementtype": "^2.3.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/domutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", - "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", - "dependencies": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/dot-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", - "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/dot-prop": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", - "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", - "dependencies": { - "is-obj": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/dot-prop/node_modules/is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "engines": { - "node": ">=8" - } - }, - "node_modules/duplexer": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", - "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==" - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" - }, - "node_modules/electron-to-chromium": { - "version": "1.5.67", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.67.tgz", - "integrity": "sha512-nz88NNBsD7kQSAGGJyp8hS6xSPtWwqNogA0mjtc2nUYeEf3nURK9qpV18TuBdDmEDgVWotS8Wkzf+V52dSQ/LQ==" - }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" - }, - "node_modules/emojilib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/emojilib/-/emojilib-2.4.0.tgz", - "integrity": "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==" - }, - "node_modules/emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "engines": { - "node": ">= 4" - } - }, - "node_modules/emoticon": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/emoticon/-/emoticon-4.0.1.tgz", - "integrity": "sha512-dqx7eA9YaqyvYtUhJwT4rC1HIp82j5ybS1/vQ42ur+jBe17dJMwZE4+gvL1XadSFfxaPFFGt3Xsw+Y8akThDlw==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/enhanced-resolve": { - "version": "5.17.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", - "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "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==", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "dependencies": { - "get-intrinsic": "^1.2.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-module-lexer": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", - "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==" - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-goat": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-4.0.0.tgz", - "integrity": "sha512-2Sd4ShcWxbx6OY1IHyla/CVNwvg7XwZVoXZHcSu9w9SReNP1EzzD5T8NWKIR38fIqEns9kDWKUQTXXAmlDrdPg==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" - }, - "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==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estree-util-attach-comments": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/estree-util-attach-comments/-/estree-util-attach-comments-3.0.0.tgz", - "integrity": "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==", - "dependencies": { - "@types/estree": "^1.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/estree-util-build-jsx": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/estree-util-build-jsx/-/estree-util-build-jsx-3.0.1.tgz", - "integrity": "sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "devlop": "^1.0.0", - "estree-util-is-identifier-name": "^3.0.0", - "estree-walker": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "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==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/estree-util-to-js": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/estree-util-to-js/-/estree-util-to-js-2.0.0.tgz", - "integrity": "sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "astring": "^1.8.0", - "source-map": "^0.7.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/estree-util-value-to-estree": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/estree-util-value-to-estree/-/estree-util-value-to-estree-3.1.2.tgz", - "integrity": "sha512-S0gW2+XZkmsx00tU2uJ4L9hUT7IFabbml9pHh2WQqFmAbxit++YGZne0sKJbNwkj9Wvg9E4uqWl4nCIFQMmfag==", - "dependencies": { - "@types/estree": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/remcohaszing" - } - }, - "node_modules/estree-util-visit": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/estree-util-visit/-/estree-util-visit-2.0.0.tgz", - "integrity": "sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eta": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/eta/-/eta-2.2.0.tgz", - "integrity": "sha512-UVQ72Rqjy/ZKQalzV5dCCJP80GrmPrMxh6NlNf+erV6ObL0ZFkhCstWRawS85z3smdr3d2wXPsZEY7rDPfGd2g==", - "engines": { - "node": ">=6.0.0" - }, - "funding": { - "url": "https://github.com/eta-dev/eta?sponsor=1" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/eval": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/eval/-/eval-0.1.8.tgz", - "integrity": "sha512-EzV94NYKoO09GLXGjXj9JIlXijVck4ONSr5wiCWDvhsvj5jxSrzTmRU/9C1DyB6uToszLs8aifA6NQ7lEQdvFw==", - "dependencies": { - "@types/node": "*", - "require-like": ">= 0.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", - "license": "MIT", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", - "proxy-addr": "~2.0.7", - "qs": "6.13.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express/node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/express/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/express/node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", - "license": "MIT" - }, - "node_modules/express/node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" - }, - "node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" - }, - "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" - }, - "node_modules/fastq": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", - "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fault": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fault/-/fault-2.0.1.tgz", - "integrity": "sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==", - "dependencies": { - "format": "^0.2.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/faye-websocket": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", - "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", - "dependencies": { - "websocket-driver": ">=0.5.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/feed": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/feed/-/feed-4.2.2.tgz", - "integrity": "sha512-u5/sxGfiMfZNtJ3OvQpXcvotFpYkL0n9u9mM2vkui2nGo8b4wvDkJ8gAkYqbA8QpGyFCv3RK0Z+Iv+9veCS9bQ==", - "dependencies": { - "xml-js": "^1.6.11" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/figures/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/file-loader": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz", - "integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==", - "dependencies": { - "loader-utils": "^2.0.0", - "schema-utils": "^3.0.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" - } - }, - "node_modules/file-loader/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/file-loader/node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/file-loader/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" - }, - "node_modules/file-loader/node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/filesize": { - "version": "8.0.7", - "resolved": "https://registry.npmjs.org/filesize/-/filesize-8.0.7.tgz", - "integrity": "sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ==", - "engines": { - "node": ">= 0.4.0" - } - }, - "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==", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/find-cache-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-4.0.0.tgz", - "integrity": "sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==", - "dependencies": { - "common-path-prefix": "^3.0.0", - "pkg-dir": "^7.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/find-up": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", - "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", - "dependencies": { - "locate-path": "^7.1.0", - "path-exists": "^5.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "bin": { - "flat": "cli.js" - } - }, - "node_modules/follow-redirects": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/fork-ts-checker-webpack-plugin": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.3.tgz", - "integrity": "sha512-SbH/l9ikmMWycd5puHJKTkZJKddF4iRLyW3DeZ08HTI7NGyLS38MXd/KGgeWumQO7YNQbW2u/NtPT2YowbPaGQ==", - "dependencies": { - "@babel/code-frame": "^7.8.3", - "@types/json-schema": "^7.0.5", - "chalk": "^4.1.0", - "chokidar": "^3.4.2", - "cosmiconfig": "^6.0.0", - "deepmerge": "^4.2.2", - "fs-extra": "^9.0.0", - "glob": "^7.1.6", - "memfs": "^3.1.2", - "minimatch": "^3.0.4", - "schema-utils": "2.7.0", - "semver": "^7.3.2", - "tapable": "^1.0.0" - }, - "engines": { - "node": ">=10", - "yarn": ">=1.0.0" - }, - "peerDependencies": { - "eslint": ">= 6", - "typescript": ">= 2.7", - "vue-template-compiler": "*", - "webpack": ">= 4" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - }, - "vue-template-compiler": { - "optional": true - } - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/cosmiconfig": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", - "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", - "dependencies": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.1.0", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.7.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dependencies": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/schema-utils": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", - "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", - "dependencies": { - "@types/json-schema": "^7.0.4", - "ajv": "^6.12.2", - "ajv-keywords": "^3.4.1" - }, - "engines": { - "node": ">= 8.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/tapable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", - "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", - "engines": { - "node": ">=6" - } - }, - "node_modules/form-data-encoder": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz", - "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==", - "engines": { - "node": ">= 14.17" - } - }, - "node_modules/format": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", - "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==", - "engines": { - "node": ">=0.4.x" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fraction.js": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", - "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", - "engines": { - "node": "*" - }, - "funding": { - "type": "patreon", - "url": "https://github.com/sponsors/rawify" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fs-extra": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", - "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/fs-monkey": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.6.tgz", - "integrity": "sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg==" - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "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==", - "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==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-own-enumerable-property-symbols": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", - "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==" - }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/github-slugger": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.5.0.tgz", - "integrity": "sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw==" - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" - }, - "node_modules/global-dirs": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", - "integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==", - "dependencies": { - "ini": "2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/global-dirs/node_modules/ini": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", - "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", - "engines": { - "node": ">=10" - } - }, - "node_modules/global-modules": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", - "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", - "dependencies": { - "global-prefix": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/global-prefix": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", - "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", - "dependencies": { - "ini": "^1.3.5", - "kind-of": "^6.0.2", - "which": "^1.3.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/global-prefix/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "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==", - "engines": { - "node": ">=4" - } - }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dependencies": { - "get-intrinsic": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/got": { - "version": "12.6.1", - "resolved": "https://registry.npmjs.org/got/-/got-12.6.1.tgz", - "integrity": "sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==", - "dependencies": { - "@sindresorhus/is": "^5.2.0", - "@szmarczak/http-timer": "^5.0.1", - "cacheable-lookup": "^7.0.0", - "cacheable-request": "^10.2.8", - "decompress-response": "^6.0.0", - "form-data-encoder": "^2.1.2", - "get-stream": "^6.0.1", - "http2-wrapper": "^2.1.10", - "lowercase-keys": "^3.0.0", - "p-cancelable": "^3.0.0", - "responselike": "^3.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sindresorhus/got?sponsor=1" - } - }, - "node_modules/got/node_modules/@sindresorhus/is": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", - "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" - }, - "node_modules/gray-matter": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", - "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==", - "dependencies": { - "js-yaml": "^3.13.1", - "kind-of": "^6.0.2", - "section-matter": "^1.0.0", - "strip-bom-string": "^1.0.0" - }, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/gray-matter/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/gray-matter/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/gzip-size": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", - "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", - "dependencies": { - "duplexer": "^0.1.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/handle-thing": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", - "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==" - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-yarn": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-3.0.0.tgz", - "integrity": "sha512-IrsVwUHhEULx3R8f/aA8AHuEzAorplsab/v8HBzEiIukwq5i/EC+xmOW+HfP1OaDP+2JkgT1yILHN2O3UFIbcA==", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/hast-util-from-parse5": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.1.tgz", - "integrity": "sha512-Er/Iixbc7IEa7r/XLtuG52zoqn/b3Xng/w6aZQ0xGVxzhw5xUFxcRqdPzP6yFi/4HBYRaifaI5fQ1RH8n0ZeOQ==", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "devlop": "^1.0.0", - "hastscript": "^8.0.0", - "property-information": "^6.0.0", - "vfile": "^6.0.0", - "vfile-location": "^5.0.0", - "web-namespaces": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-parse-selector": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", - "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", - "dependencies": { - "@types/hast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-raw": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.0.4.tgz", - "integrity": "sha512-LHE65TD2YiNsHD3YuXcKPHXPLuYh/gjp12mOfU8jxSrm1f/yJpsb0F/KKljS6U9LJoP0Ux+tCe8iJ2AsPzTdgA==", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "@ungap/structured-clone": "^1.0.0", - "hast-util-from-parse5": "^8.0.0", - "hast-util-to-parse5": "^8.0.0", - "html-void-elements": "^3.0.0", - "mdast-util-to-hast": "^13.0.0", - "parse5": "^7.0.0", - "unist-util-position": "^5.0.0", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.0", - "web-namespaces": "^2.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-to-estree": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-3.1.0.tgz", - "integrity": "sha512-lfX5g6hqVh9kjS/B9E2gSkvHH4SZNiQFiqWS0x9fENzEl+8W12RqdRxX6d/Cwxi30tPQs3bIO+aolQJNp1bIyw==", - "dependencies": { - "@types/estree": "^1.0.0", - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "comma-separated-tokens": "^2.0.0", - "devlop": "^1.0.0", - "estree-util-attach-comments": "^3.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": "^6.0.0", - "space-separated-tokens": "^2.0.0", - "style-to-object": "^0.4.0", - "unist-util-position": "^5.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-to-jsx-runtime": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.0.tgz", - "integrity": "sha512-H/y0+IWPdsLLS738P8tDnrQ8Z+dj12zQQ6WC11TIM21C8WFVoIxcqWXf2H3hiTVZjF1AWqoimGwrTWecWrnmRQ==", - "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": "^6.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-to-jsx-runtime/node_modules/inline-style-parser": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.3.tgz", - "integrity": "sha512-qlD8YNDqyTKTyuITrDOffsl6Tdhv+UC4hcdAVuQsK4IMQ99nSgd1MIA/Q+jQYoh9r3hVUXhYh7urSRmXPkW04g==" - }, - "node_modules/hast-util-to-jsx-runtime/node_modules/style-to-object": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.6.tgz", - "integrity": "sha512-khxq+Qm3xEyZfKd/y9L3oIWQimxuc4STrQKtQn8aSDRHb8mFgpukgX1hdzfrMEW6JCjyJ8p89x+IUMVnCBI1PA==", - "dependencies": { - "inline-style-parser": "0.2.3" - } - }, - "node_modules/hast-util-to-parse5": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz", - "integrity": "sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==", - "dependencies": { - "@types/hast": "^3.0.0", - "comma-separated-tokens": "^2.0.0", - "devlop": "^1.0.0", - "property-information": "^6.0.0", - "space-separated-tokens": "^2.0.0", - "web-namespaces": "^2.0.0", - "zwitch": "^2.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==", - "dependencies": { - "@types/hast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hastscript": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-8.0.0.tgz", - "integrity": "sha512-dMOtzCEd3ABUeSIISmrETiKuyydk1w0pa+gE/uormcTpSYuaNJPbX1NU3JLyscSLjwAQM8bWMhhIlnCqnRvDTw==", - "dependencies": { - "@types/hast": "^3.0.0", - "comma-separated-tokens": "^2.0.0", - "hast-util-parse-selector": "^4.0.0", - "property-information": "^6.0.0", - "space-separated-tokens": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "bin": { - "he": "bin/he" - } - }, - "node_modules/history": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", - "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", - "dependencies": { - "@babel/runtime": "^7.1.2", - "loose-envify": "^1.2.0", - "resolve-pathname": "^3.0.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0", - "value-equal": "^1.0.1" - } - }, - "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==", - "dependencies": { - "react-is": "^16.7.0" - } - }, - "node_modules/hpack.js": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", - "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", - "dependencies": { - "inherits": "^2.0.1", - "obuf": "^1.0.0", - "readable-stream": "^2.0.1", - "wbuf": "^1.1.0" - } - }, - "node_modules/hpack.js/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" - }, - "node_modules/hpack.js/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/hpack.js/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/hpack.js/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/html-entities": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz", - "integrity": "sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/mdevils" - }, - { - "type": "patreon", - "url": "https://patreon.com/mdevils" - } - ] - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==" - }, - "node_modules/html-minifier-terser": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-7.2.0.tgz", - "integrity": "sha512-tXgn3QfqPIpGl9o+K5tpcj3/MN4SfLtsx2GWwBC3SSd0tXQGyF3gsSqad8loJgKZGM3ZxbYDd5yhiBIdWpmvLA==", - "dependencies": { - "camel-case": "^4.1.2", - "clean-css": "~5.3.2", - "commander": "^10.0.0", - "entities": "^4.4.0", - "param-case": "^3.0.4", - "relateurl": "^0.2.7", - "terser": "^5.15.1" - }, - "bin": { - "html-minifier-terser": "cli.js" - }, - "engines": { - "node": "^14.13.1 || >=16.0.0" - } - }, - "node_modules/html-minifier-terser/node_modules/commander": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", - "engines": { - "node": ">=14" - } - }, - "node_modules/html-tags": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", - "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/html-void-elements": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", - "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/html-webpack-plugin": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.0.tgz", - "integrity": "sha512-iwaY4wzbe48AfKLZ/Cc8k0L+FKG6oSNRaZ8x5A/T/IVDGyXcbHncM9TdDa93wn0FsSm82FhTKW7f3vS61thXAw==", - "dependencies": { - "@types/html-minifier-terser": "^6.0.0", - "html-minifier-terser": "^6.0.2", - "lodash": "^4.17.21", - "pretty-error": "^4.0.0", - "tapable": "^2.0.0" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/html-webpack-plugin" - }, - "peerDependencies": { - "@rspack/core": "0.x || 1.x", - "webpack": "^5.20.0" - }, - "peerDependenciesMeta": { - "@rspack/core": { - "optional": true - }, - "webpack": { - "optional": true - } - } - }, - "node_modules/html-webpack-plugin/node_modules/commander": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", - "engines": { - "node": ">= 12" - } - }, - "node_modules/html-webpack-plugin/node_modules/html-minifier-terser": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", - "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", - "dependencies": { - "camel-case": "^4.1.2", - "clean-css": "^5.2.2", - "commander": "^8.3.0", - "he": "^1.2.0", - "param-case": "^3.0.4", - "relateurl": "^0.2.7", - "terser": "^5.10.0" - }, - "bin": { - "html-minifier-terser": "cli.js" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/htmlparser2": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", - "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.0.1", - "entities": "^4.4.0" - } - }, - "node_modules/http-cache-semantics": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==" - }, - "node_modules/http-deceiver": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", - "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==" - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/http-parser-js": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", - "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==" - }, - "node_modules/http-proxy": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", - "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", - "dependencies": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/http-proxy-middleware": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz", - "integrity": "sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA==", - "dependencies": { - "@types/http-proxy": "^1.17.8", - "http-proxy": "^1.18.1", - "is-glob": "^4.0.1", - "is-plain-obj": "^3.0.0", - "micromatch": "^4.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "@types/express": "^4.17.13" - }, - "peerDependenciesMeta": { - "@types/express": { - "optional": true - } - } - }, - "node_modules/http-proxy-middleware/node_modules/is-plain-obj": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", - "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/http2-wrapper": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", - "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", - "dependencies": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.2.0" - }, - "engines": { - "node": ">=10.19.0" - } - }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/icss-utils": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", - "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/ignore": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", - "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", - "engines": { - "node": ">= 4" - } - }, - "node_modules/image-size": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.1.1.tgz", - "integrity": "sha512-541xKlUw6jr/6gGuk92F+mYM5zaFAc5ahphvkqvNe2bQ6gVBkd6bfrmVJ2t4KDAfikAYZyIqTnktX3i6/aQDrQ==", - "dependencies": { - "queue": "6.0.2" - }, - "bin": { - "image-size": "bin/image-size.js" - }, - "engines": { - "node": ">=16.x" - } - }, - "node_modules/immer": { - "version": "9.0.21", - "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz", - "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/immer" - } - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/import-lazy": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", - "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", - "engines": { - "node": ">=8" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/infima": { - "version": "0.2.0-alpha.45", - "resolved": "https://registry.npmjs.org/infima/-/infima-0.2.0-alpha.45.tgz", - "integrity": "sha512-uyH0zfr1erU1OohLk0fT4Rrb94AOhguWNOcD9uGrSpRvNB+6gZXUoJX5J0NtvzBO10YZ9PgvA4NFgt+fYg8ojw==", - "engines": { - "node": ">=12" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" - }, - "node_modules/inline-style-parser": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz", - "integrity": "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==" - }, - "node_modules/interpret": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", - "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dependencies": { - "loose-envify": "^1.0.0" - } - }, - "node_modules/ipaddr.js": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", - "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", - "engines": { - "node": ">= 10" - } - }, - "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==", - "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==", - "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==" - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-ci": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", - "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", - "dependencies": { - "ci-info": "^3.2.0" - }, - "bin": { - "is-ci": "bin.js" - } - }, - "node_modules/is-core-module": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.14.0.tgz", - "integrity": "sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A==", - "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==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "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==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/is-installed-globally": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", - "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", - "dependencies": { - "global-dirs": "^3.0.0", - "is-path-inside": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-npm": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-6.0.0.tgz", - "integrity": "sha512-JEjxbSmtPSt1c8XTkVrlujcXdKV1/tvuQ7GwKcAlyiVLeYFQ2VHat8xfrDJsIkhCdF/tZ7CiIR3sy141c6+gPQ==", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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==", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-path-cwd": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", - "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "engines": { - "node": ">=8" - } - }, - "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==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-reference": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz", - "integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==", - "dependencies": { - "@types/estree": "*" - } - }, - "node_modules/is-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", - "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-root": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-root/-/is-root-2.1.0.tgz", - "integrity": "sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg==", - "engines": { - "node": ">=6" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" - }, - "node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-yarn-global": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.4.1.tgz", - "integrity": "sha512-/kppl+R+LO5VmhYSEWARUFjodS25D68gvj8W7z0I7OWhUla5xWu8KL6CtB2V0R6yqhnRgbcaREMr4EEM6htLPQ==", - "engines": { - "node": ">=12" - } - }, - "node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jest-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", - "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-worker": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", - "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", - "dependencies": { - "@types/node": "*", - "jest-util": "^29.7.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/jiti": { - "version": "1.21.6", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", - "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", - "bin": { - "jiti": "bin/jiti.js" - } - }, - "node_modules/joi": { - "version": "17.13.3", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", - "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", - "dependencies": { - "@hapi/hoek": "^9.3.0", - "@hapi/topo": "^5.1.0", - "@sideway/address": "^4.1.5", - "@sideway/formula": "^3.0.1", - "@sideway/pinpoint": "^2.0.0" - } - }, - "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==" - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsesc": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", - "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" - }, - "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==" - }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "engines": { - "node": ">=6" - } - }, - "node_modules/latest-version": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-7.0.0.tgz", - "integrity": "sha512-KvNT4XqAMzdcL6ka6Tl3i2lYeFDgXNCuIX+xNx6ZMVR1dFq+idXd9FLKNMOIx0t9mJ9/HudyX4oZWXZQ0UJHeg==", - "dependencies": { - "package-json": "^8.1.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/launch-editor": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.8.0.tgz", - "integrity": "sha512-vJranOAJrI/llyWGRQqiDM+adrw+k83fvmmx3+nV47g3+36xM15jE+zyZ6Ffel02+xSvuM0b2GDRosXZkbb6wA==", - "dependencies": { - "picocolors": "^1.0.0", - "shell-quote": "^1.8.1" - } - }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "engines": { - "node": ">=6" - } - }, - "node_modules/lilconfig": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", - "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antonk52" - } - }, - "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==" - }, - "node_modules/loader-runner": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", - "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", - "engines": { - "node": ">=6.11.5" - } - }, - "node_modules/loader-utils": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", - "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - }, - "engines": { - "node": ">=8.9.0" - } - }, - "node_modules/locate-path": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", - "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", - "dependencies": { - "p-locate": "^6.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "node_modules/lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" - }, - "node_modules/lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==" - }, - "node_modules/lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==" - }, - "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==", - "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==", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/lower-case": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", - "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", - "dependencies": { - "tslib": "^2.0.3" - } - }, - "node_modules/lowercase-keys": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", - "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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==", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/markdown-extensions": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-2.0.0.tgz", - "integrity": "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/markdown-table": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.3.tgz", - "integrity": "sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/mdast-util-directive": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-directive/-/mdast-util-directive-3.0.0.tgz", - "integrity": "sha512-JUpYOqKI4mM3sZcNxmF/ox04XYFFkNwr0CFlrQIkCwbvH0xzMCqkMqAde9wRd80VAhaUrwFwKm2nxretdT1h7Q==", - "dependencies": { - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "devlop": "^1.0.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-visit-parents": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-find-and-replace": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.1.tgz", - "integrity": "sha512-SG21kZHGC3XRTSUhtofZkBzZTJNM5ecCi0SK2IMKmSXR8vO3peL+kb1O0z7Zl83jKtutG4k5Wv/W7V3/YHvzPA==", - "dependencies": { - "@types/mdast": "^4.0.0", - "escape-string-regexp": "^5.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/mdast-util-find-and-replace/node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mdast-util-from-markdown": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.1.tgz", - "integrity": "sha512-aJEUyzZ6TzlsX2s5B4Of7lN7EQtAxvtradMMglCQDyaTFgse6CmtmdJ15ElnVRlCg1vpNyVtbem0PWzlNieZsA==", - "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-from-markdown/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/mdast-util-frontmatter": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-frontmatter/-/mdast-util-frontmatter-2.0.1.tgz", - "integrity": "sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "escape-string-regexp": "^5.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0", - "micromark-extension-frontmatter": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-frontmatter/node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mdast-util-gfm": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.0.0.tgz", - "integrity": "sha512-dgQEX5Amaq+DuUqf26jJqSK9qgixgd6rYDHAv4aTBuA92cTknZlKpPfa86Z/s8Dj8xsAQpFfBmPUHWJBWqS4Bw==", - "dependencies": { - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-gfm-autolink-literal": "^2.0.0", - "mdast-util-gfm-footnote": "^2.0.0", - "mdast-util-gfm-strikethrough": "^2.0.0", - "mdast-util-gfm-table": "^2.0.0", - "mdast-util-gfm-task-list-item": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-autolink-literal": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.0.tgz", - "integrity": "sha512-FyzMsduZZHSc3i0Px3PQcBT4WJY/X/RCtEJKuybiC6sjPqLv7h1yqAkmILZtuxMSsUyaLUWNp71+vQH2zqp5cg==", - "dependencies": { - "@types/mdast": "^4.0.0", - "ccount": "^2.0.0", - "devlop": "^1.0.0", - "mdast-util-find-and-replace": "^3.0.0", - "micromark-util-character": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-autolink-literal/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/mdast-util-gfm-autolink-literal/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/mdast-util-gfm-footnote": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.0.0.tgz", - "integrity": "sha512-5jOT2boTSVkMnQ7LTrd6n/18kqwjmuYqo7JUPe+tRCY6O7dAuTFMtTPauYYrMPpox9hlN0uOx/FL8XvEfG9/mQ==", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.1.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-strikethrough": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", - "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", - "dependencies": { - "@types/mdast": "^4.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-gfm-table": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", - "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "markdown-table": "^3.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-gfm-task-list-item": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", - "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", - "dependencies": { - "@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": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz", - "integrity": "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==", - "dependencies": { - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-mdx-expression": "^2.0.0", - "mdast-util-mdx-jsx": "^3.0.0", - "mdast-util-mdxjs-esm": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-mdx-expression": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.0.tgz", - "integrity": "sha512-fGCu8eWdKUKNu5mohVGkhBXCXGnOTLuFqOvGMvdikr+J1w7lDJgxThOKpwRWzzbyXAU2hhSwsmssOY4yTokluw==", - "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.1.2", - "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.1.2.tgz", - "integrity": "sha512-eKMQDeywY2wlHc97k5eD8VC+9ASMjN8ItEZQNGwJ6E0XWKiW/Z0V5/H8pvoXUf+y+Mj0VIgeRRbujBmFn4FTyA==", - "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-remove-position": "^5.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==", - "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==", - "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==", - "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.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.0.tgz", - "integrity": "sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ==", - "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-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==", - "dependencies": { - "@types/mdast": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdn-data": { - "version": "2.0.30", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", - "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==" - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/memfs": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", - "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", - "dependencies": { - "fs-monkey": "^1.0.4" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "engines": { - "node": ">= 8" - } - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/micromark": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.0.tgz", - "integrity": "sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "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.1", - "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.1.tgz", - "integrity": "sha512-CUQyKr1e///ZODyD1U3xit6zXwy1a8q2a1S1HKtIlmgvurrEpaw/Y9y6KSIbF8P59cn/NjzHyO+Q2fAyYLQrAA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "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-core-commonmark/node_modules/micromark-factory-space": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", - "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-core-commonmark/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-core-commonmark/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-extension-directive": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.0.tgz", - "integrity": "sha512-61OI07qpQrERc+0wEysLHMvoiO3s2R56x5u7glHq2Yqq6EHbH4dW25G9GfDdGCDYqA21KE6DWgNSzxSwHc2hSg==", - "dependencies": { - "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-factory-whitespace": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "parse-entities": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-directive/node_modules/micromark-factory-space": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", - "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-directive/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-directive/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-extension-frontmatter": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-frontmatter/-/micromark-extension-frontmatter-2.0.0.tgz", - "integrity": "sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==", - "dependencies": { - "fault": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-frontmatter/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-frontmatter/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-extension-gfm": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", - "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", - "dependencies": { - "micromark-extension-gfm-autolink-literal": "^2.0.0", - "micromark-extension-gfm-footnote": "^2.0.0", - "micromark-extension-gfm-strikethrough": "^2.0.0", - "micromark-extension-gfm-table": "^2.0.0", - "micromark-extension-gfm-tagfilter": "^2.0.0", - "micromark-extension-gfm-task-list-item": "^2.0.0", - "micromark-util-combine-extensions": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-autolink-literal": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", - "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-autolink-literal/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-gfm-autolink-literal/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-extension-gfm-footnote": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", - "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", - "dependencies": { - "devlop": "^1.0.0", - "micromark-core-commonmark": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-factory-space": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", - "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-extension-gfm-strikethrough": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", - "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", - "dependencies": { - "devlop": "^1.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-strikethrough/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-extension-gfm-table": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.0.tgz", - "integrity": "sha512-Ub2ncQv+fwD70/l4ou27b4YzfNaCJOvyX4HxXU15m7mpYY+rjuWzsLIPZHJL253Z643RpbcP1oeIJlQ/SKW67g==", - "dependencies": { - "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-table/node_modules/micromark-factory-space": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", - "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-gfm-table/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-gfm-table/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-extension-gfm-tagfilter": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", - "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", - "dependencies": { - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-task-list-item": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", - "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", - "dependencies": { - "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-factory-space": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", - "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-extension-mdx-expression": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.0.tgz", - "integrity": "sha512-sI0nwhUDz97xyzqJAbHQhp5TfaxEvZZZ2JDqUo+7NvyIYG6BZ5CPPqj2ogUoPJlmXHBnyZUzISg9+oUmU6tUjQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "@types/estree": "^1.0.0", - "devlop": "^1.0.0", - "micromark-factory-mdx-expression": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-events-to-acorn": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-mdx-expression/node_modules/micromark-factory-space": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", - "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-mdx-expression/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-mdx-expression/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-extension-mdx-jsx": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.0.tgz", - "integrity": "sha512-uvhhss8OGuzR4/N17L1JwvmJIpPhAd8oByMawEKx6NVdBCbesjH4t+vjEp3ZXft9DwvlKSD07fCeI44/N0Vf2w==", - "dependencies": { - "@types/acorn": "^4.0.0", - "@types/estree": "^1.0.0", - "devlop": "^1.0.0", - "estree-util-is-identifier-name": "^3.0.0", - "micromark-factory-mdx-expression": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-mdx-jsx/node_modules/micromark-factory-space": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", - "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-mdx-jsx/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-mdx-jsx/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-extension-mdx-md": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-mdx-md/-/micromark-extension-mdx-md-2.0.0.tgz", - "integrity": "sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==", - "dependencies": { - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-mdxjs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs/-/micromark-extension-mdxjs-3.0.0.tgz", - "integrity": "sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==", - "dependencies": { - "acorn": "^8.0.0", - "acorn-jsx": "^5.0.0", - "micromark-extension-mdx-expression": "^3.0.0", - "micromark-extension-mdx-jsx": "^3.0.0", - "micromark-extension-mdx-md": "^2.0.0", - "micromark-extension-mdxjs-esm": "^3.0.0", - "micromark-util-combine-extensions": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-mdxjs-esm": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-3.0.0.tgz", - "integrity": "sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==", - "dependencies": { - "@types/estree": "^1.0.0", - "devlop": "^1.0.0", - "micromark-core-commonmark": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-events-to-acorn": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unist-util-position-from-estree": "^2.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-mdxjs-esm/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-mdxjs-esm/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-factory-destination": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.0.tgz", - "integrity": "sha512-j9DGrQLm/Uhl2tCzcbLhy5kXsgkHUrjJHg4fFAeoMRwJmJerT9aw4FEhIbZStWN8A3qMwOp1uzHr4UL8AInxtA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-destination/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-destination/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-factory-label": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.0.tgz", - "integrity": "sha512-RR3i96ohZGde//4WSe/dJsxOX6vxIg9TimLAS3i4EhBAFx8Sm5SmqVfR8E87DPSR31nEAjZfbt91OMZWcNgdZw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "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-label/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-label/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-factory-mdx-expression": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.1.tgz", - "integrity": "sha512-F0ccWIUHRLRrYp5TC9ZYXmZo+p2AM13ggbsW4T0b5CRKP8KHVRB8t4pwtBgTxtjRmwrK0Irwm7vs2JOZabHZfg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "@types/estree": "^1.0.0", - "devlop": "^1.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-events-to-acorn": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unist-util-position-from-estree": "^2.0.0", - "vfile-message": "^4.0.0" - } - }, - "node_modules/micromark-factory-mdx-expression/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-mdx-expression/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-factory-space": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz", - "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-character": "^1.0.0", - "micromark-util-types": "^1.0.0" - } - }, - "node_modules/micromark-factory-space/node_modules/micromark-util-types": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", - "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-factory-title": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.0.tgz", - "integrity": "sha512-jY8CSxmpWLOxS+t8W+FG3Xigc0RDQA9bKMY/EwILvsesiRniiVMejYTE4wumNc2f4UbAa4WsHqe3J1QS1sli+A==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "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-title/node_modules/micromark-factory-space": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", - "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-title/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-title/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-factory-whitespace": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.0.tgz", - "integrity": "sha512-28kbwaBjc5yAI1XadbdPYHX/eDnqaUFVikLwrO7FDnKG7lpgxnvk/XGRhX/PN0mOZ+dBSZ+LgunHS+6tYQAzhA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "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/node_modules/micromark-factory-space": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", - "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-whitespace/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-whitespace/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-util-character": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", - "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0" - } - }, - "node_modules/micromark-util-character/node_modules/micromark-util-types": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", - "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-util-chunked": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.0.tgz", - "integrity": "sha512-anK8SWmNphkXdaKgz5hJvGa7l00qmcaUQoMYsBwDlSKFKjc6gjGXPDw3FNL3Nbwq5L8gE+RCbGqTw49FK5Qyvg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-chunked/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-util-classify-character": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.0.tgz", - "integrity": "sha512-S0ze2R9GH+fu41FA7pbSqNWObo/kzwf8rN/+IGlW/4tC6oACOs8B++bh+i9bVyNnwCcuksbFwsBme5OCKXCwIw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-classify-character/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-classify-character/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-util-combine-extensions": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.0.tgz", - "integrity": "sha512-vZZio48k7ON0fVS3CUgFatWHoKbbLTK/rT7pzpJ4Bjp5JjkZeasRfrS9wsBdDJK2cJLHMckXZdzPSSr1B8a4oQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-chunked": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-decode-numeric-character-reference": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.1.tgz", - "integrity": "sha512-bmkNc7z8Wn6kgjZmVHOX3SowGmVdhYS7yBpMnuMnPzDq/6xwVA604DuOXMZTO1lvq01g+Adfa0pE2UKGlxL1XQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-decode-numeric-character-reference/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-util-decode-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.0.tgz", - "integrity": "sha512-r4Sc6leeUTn3P6gk20aFMj2ntPwn6qpDZqWvYmAG6NgvFTIlj4WtrAudLi65qYoaGdXYViXYw2pkmn7QnIFasA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "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-decode-string/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-decode-string/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-util-encode": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.0.tgz", - "integrity": "sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-util-events-to-acorn": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.2.tgz", - "integrity": "sha512-Fk+xmBrOv9QZnEDguL9OI9/NQQp6Hz4FuQ4YmCb/5V7+9eAh1s6AYSvL20kHkD67YIg7EpE54TiSlcsf3vyZgA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "@types/acorn": "^4.0.0", - "@types/estree": "^1.0.0", - "@types/unist": "^3.0.0", - "devlop": "^1.0.0", - "estree-util-visit": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "vfile-message": "^4.0.0" - } - }, - "node_modules/micromark-util-events-to-acorn/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-util-html-tag-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.0.tgz", - "integrity": "sha512-xNn4Pqkj2puRhKdKTm8t1YHC/BAjx6CEwRFXntTaRf/x16aqka6ouVoutm+QdkISTlT7e2zU7U4ZdlDLJd2Mcw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-util-normalize-identifier": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.0.tgz", - "integrity": "sha512-2xhYT0sfo85FMrUPtHcPo2rrp1lwbDEEzpx7jiH2xXJLqBuy4H0GgXk5ToU8IEwoROtXuL8ND0ttVa4rNqYK3w==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-normalize-identifier/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-util-resolve-all": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.0.tgz", - "integrity": "sha512-6KU6qO7DZ7GJkaCgwBNtplXCvGkJToU86ybBAUdavvgsCiG8lSSvYxr9MhwmQ+udpzywHsl4RpGJsYWG1pDOcA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-sanitize-uri": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.0.tgz", - "integrity": "sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-encode": "^2.0.0", - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-sanitize-uri/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-sanitize-uri/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-util-subtokenize": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.1.tgz", - "integrity": "sha512-jZNtiFl/1aY73yS3UGQkutD0UbhTt68qnRpw2Pifmz5wV9h8gOVsN70v+Lq/f1rKaU/W8pxRe8y8Q9FX1AOe1Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "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-subtokenize/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-util-symbol": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", - "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-util-types": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.0.tgz", - "integrity": "sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark/node_modules/micromark-factory-space": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", - "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark/node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark/node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", - "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.18", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", - "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", - "dependencies": { - "mime-db": "~1.33.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "engines": { - "node": ">=6" - } - }, - "node_modules/mimic-response": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", - "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mini-css-extract-plugin": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.2.tgz", - "integrity": "sha512-GJuACcS//jtq4kCtd5ii/M0SZf7OZRH+BxdqXZHaJfb8TJiVl+NgQRPwiYt2EuqeSkNydn/7vP+bcE27C5mb9w==", - "dependencies": { - "schema-utils": "^4.0.0", - "tapable": "^2.2.1" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.0.0" - } - }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/mrmime": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", - "integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==", - "engines": { - "node": ">=10" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/multicast-dns": { - "version": "7.2.5", - "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", - "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", - "dependencies": { - "dns-packet": "^5.2.2", - "thunky": "^1.0.2" - }, - "bin": { - "multicast-dns": "cli.js" - } - }, - "node_modules/nanoid": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", - "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" - }, - "node_modules/no-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", - "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", - "dependencies": { - "lower-case": "^2.0.2", - "tslib": "^2.0.3" - } - }, - "node_modules/node-emoji": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.1.3.tgz", - "integrity": "sha512-E2WEOVsgs7O16zsURJ/eH8BqhF029wGpEOnv7Urwdo2wmQanOACwJQh0devF9D9RhoZru0+9JXIS0dBXIAz+lA==", - "dependencies": { - "@sindresorhus/is": "^4.6.0", - "char-regex": "^1.0.2", - "emojilib": "^2.4.0", - "skin-tone": "^2.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/node-forge": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", - "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", - "engines": { - "node": ">= 6.13.0" - } - }, - "node_modules/node-releases": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", - "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==" - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/normalize-url": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.1.tgz", - "integrity": "sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nprogress": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/nprogress/-/nprogress-0.2.0.tgz", - "integrity": "sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==" - }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, - "node_modules/null-loader": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/null-loader/-/null-loader-4.0.1.tgz", - "integrity": "sha512-pxqVbi4U6N26lq+LmgIbB5XATP0VdZKOG25DhHi8btMmJJefGArFyDg1yc4U3hWCJbMqSrw0qyrz1UQX+qYXqg==", - "dependencies": { - "loader-utils": "^2.0.0", - "schema-utils": "^3.0.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" - } - }, - "node_modules/null-loader/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/null-loader/node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/null-loader/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" - }, - "node_modules/null-loader/node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "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==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", - "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", - "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", - "dependencies": { - "call-bind": "^1.0.5", - "define-properties": "^1.2.1", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/obuf": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", - "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==" - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/open": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", - "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", - "dependencies": { - "define-lazy-prop": "^2.0.0", - "is-docker": "^2.1.1", - "is-wsl": "^2.2.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/opener": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", - "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", - "bin": { - "opener": "bin/opener-bin.js" - } - }, - "node_modules/p-cancelable": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", - "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", - "engines": { - "node": ">=12.20" - } - }, - "node_modules/p-limit": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", - "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", - "dependencies": { - "yocto-queue": "^1.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", - "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", - "dependencies": { - "p-limit": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-retry": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", - "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", - "dependencies": { - "@types/retry": "0.12.0", - "retry": "^0.13.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/package-json": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-8.1.1.tgz", - "integrity": "sha512-cbH9IAIJHNj9uXi196JVsRlt7cHKak6u/e6AkL/bkRelZ7rlL3X1YKxsZwa36xipOEKAsdtmaG6aAJoM1fx2zA==", - "dependencies": { - "got": "^12.1.0", - "registry-auth-token": "^5.0.1", - "registry-url": "^6.0.0", - "semver": "^7.3.7" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/param-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", - "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", - "dependencies": { - "dot-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "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==", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-entities": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.1.tgz", - "integrity": "sha512-SWzvYcSJh4d/SGLIOQfZ/CoNv6BTlI6YEQ7Nj82oDVnRpwe/Z/F1EMx42x3JAOwGBlCjeCH0BRJQbQ/opHL17w==", - "dependencies": { - "@types/unist": "^2.0.0", - "character-entities": "^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.10", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", - "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" - }, - "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==", - "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/parse-numeric-range": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz", - "integrity": "sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==" - }, - "node_modules/parse5": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", - "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", - "dependencies": { - "entities": "^4.4.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/parse5-htmlparser2-tree-adapter": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", - "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", - "dependencies": { - "domhandler": "^5.0.3", - "parse5": "^7.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/pascal-case": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", - "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/path-exists": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", - "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==" - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "engines": { - "node": ">=8" - } - }, - "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==" - }, - "node_modules/path-to-regexp": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.9.0.tgz", - "integrity": "sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==", - "dependencies": { - "isarray": "0.0.1" - } - }, - "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==", - "engines": { - "node": ">=8" - } - }, - "node_modules/periscopic": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz", - "integrity": "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^3.0.0", - "is-reference": "^3.0.0" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pkg-dir": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", - "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==", - "dependencies": { - "find-up": "^6.3.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pkg-up": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", - "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", - "dependencies": { - "find-up": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-up/node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-up/node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-up/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pkg-up/node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-up/node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss": { - "version": "8.4.39", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.39.tgz", - "integrity": "sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==", - "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" - } - ], - "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.0.1", - "source-map-js": "^1.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-attribute-case-insensitive": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-7.0.1.tgz", - "integrity": "sha512-Uai+SupNSqzlschRyNx3kbCTWgY/2hcwtHEI/ej2LJWc9JJ77qKgGptd8DHwY1mXtZ7Aoh4z4yxfwMBue9eNgw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-attribute-case-insensitive/node_modules/postcss-selector-parser": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", - "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-calc": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-9.0.1.tgz", - "integrity": "sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==", - "dependencies": { - "postcss-selector-parser": "^6.0.11", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.2.2" - } - }, - "node_modules/postcss-clamp": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-clamp/-/postcss-clamp-4.1.0.tgz", - "integrity": "sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=7.6.0" - }, - "peerDependencies": { - "postcss": "^8.4.6" - } - }, - "node_modules/postcss-color-functional-notation": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-7.0.6.tgz", - "integrity": "sha512-wLXvm8RmLs14Z2nVpB4CWlnvaWPRcOZFltJSlcbYwSJ1EDZKsKDhPKIMecCnuU054KSmlmubkqczmm6qBPCBhA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/css-color-parser": "^3.0.6", - "@csstools/css-parser-algorithms": "^3.0.4", - "@csstools/css-tokenizer": "^3.0.3", - "@csstools/postcss-progressive-custom-properties": "^4.0.0", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-color-hex-alpha": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-10.0.0.tgz", - "integrity": "sha512-1kervM2cnlgPs2a8Vt/Qbe5cQ++N7rkYo/2rz2BkqJZIHQwaVuJgQH38REHrAi4uM0b1fqxMkWYmese94iMp3w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/utilities": "^2.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-color-rebeccapurple": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-10.0.0.tgz", - "integrity": "sha512-JFta737jSP+hdAIEhk1Vs0q0YF5P8fFcj+09pweS8ktuGuZ8pPlykHsk6mPxZ8awDl4TrcxUqJo9l1IhVr/OjQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/utilities": "^2.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-colormin": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.1.0.tgz", - "integrity": "sha512-x9yX7DOxeMAR+BgGVnNSAxmAj98NX/YxEMNFP+SDCEeNLb2r3i6Hh1ksMsnW8Ub5SLCpbescQqn9YEbE9554Sw==", - "dependencies": { - "browserslist": "^4.23.0", - "caniuse-api": "^3.0.0", - "colord": "^2.9.3", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-convert-values": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.1.0.tgz", - "integrity": "sha512-zx8IwP/ts9WvUM6NkVSkiU902QZL1bwPhaVaLynPtCsOTqp+ZKbNi+s6XJg3rfqpKGA/oc7Oxk5t8pOQJcwl/w==", - "dependencies": { - "browserslist": "^4.23.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-custom-media": { - "version": "11.0.5", - "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-11.0.5.tgz", - "integrity": "sha512-SQHhayVNgDvSAdX9NQ/ygcDQGEY+aSF4b/96z7QUX6mqL5yl/JgG/DywcF6fW9XbnCRE+aVYk+9/nqGuzOPWeQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/cascade-layer-name-parser": "^2.0.4", - "@csstools/css-parser-algorithms": "^3.0.4", - "@csstools/css-tokenizer": "^3.0.3", - "@csstools/media-query-list-parser": "^4.0.2" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-custom-properties": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-14.0.4.tgz", - "integrity": "sha512-QnW8FCCK6q+4ierwjnmXF9Y9KF8q0JkbgVfvQEMa93x1GT8FvOiUevWCN2YLaOWyByeDX8S6VFbZEeWoAoXs2A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/cascade-layer-name-parser": "^2.0.4", - "@csstools/css-parser-algorithms": "^3.0.4", - "@csstools/css-tokenizer": "^3.0.3", - "@csstools/utilities": "^2.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-custom-selectors": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-8.0.4.tgz", - "integrity": "sha512-ASOXqNvDCE0dAJ/5qixxPeL1aOVGHGW2JwSy7HyjWNbnWTQCl+fDc968HY1jCmZI0+BaYT5CxsOiUhavpG/7eg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/cascade-layer-name-parser": "^2.0.4", - "@csstools/css-parser-algorithms": "^3.0.4", - "@csstools/css-tokenizer": "^3.0.3", - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-custom-selectors/node_modules/postcss-selector-parser": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", - "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-dir-pseudo-class": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-9.0.1.tgz", - "integrity": "sha512-tRBEK0MHYvcMUrAuYMEOa0zg9APqirBcgzi6P21OhxtJyJADo/SWBwY1CAwEohQ/6HDaa9jCjLRG7K3PVQYHEA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-dir-pseudo-class/node_modules/postcss-selector-parser": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", - "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-discard-comments": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.2.tgz", - "integrity": "sha512-65w/uIqhSBBfQmYnG92FO1mWZjJ4GL5b8atm5Yw2UgrwD7HiNiSSNwJor1eCFGzUgYnN/iIknhNRVqjrrpuglw==", - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-discard-duplicates": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.3.tgz", - "integrity": "sha512-+JA0DCvc5XvFAxwx6f/e68gQu/7Z9ud584VLmcgto28eB8FqSFZwtrLwB5Kcp70eIoWP/HXqz4wpo8rD8gpsTw==", - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-discard-empty": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.3.tgz", - "integrity": "sha512-znyno9cHKQsK6PtxL5D19Fj9uwSzC2mB74cpT66fhgOadEUPyXFkbgwm5tvc3bt3NAy8ltE5MrghxovZRVnOjQ==", - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-discard-overridden": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.2.tgz", - "integrity": "sha512-j87xzI4LUggC5zND7KdjsI25APtyMuynXZSujByMaav2roV6OZX+8AaCUcZSWqckZpjAjRyFDdpqybgjFO0HJQ==", - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-discard-unused": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-6.0.5.tgz", - "integrity": "sha512-wHalBlRHkaNnNwfC8z+ppX57VhvS+HWgjW508esjdaEYr3Mx7Gnn2xA4R/CKf5+Z9S5qsqC+Uzh4ueENWwCVUA==", - "dependencies": { - "postcss-selector-parser": "^6.0.16" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-double-position-gradients": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-6.0.0.tgz", - "integrity": "sha512-JkIGah3RVbdSEIrcobqj4Gzq0h53GG4uqDPsho88SgY84WnpkTpI0k50MFK/sX7XqVisZ6OqUfFnoUO6m1WWdg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/postcss-progressive-custom-properties": "^4.0.0", - "@csstools/utilities": "^2.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-focus-visible": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-10.0.1.tgz", - "integrity": "sha512-U58wyjS/I1GZgjRok33aE8juW9qQgQUNwTSdxQGuShHzwuYdcklnvK/+qOWX1Q9kr7ysbraQ6ht6r+udansalA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-focus-visible/node_modules/postcss-selector-parser": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", - "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-focus-within": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-9.0.1.tgz", - "integrity": "sha512-fzNUyS1yOYa7mOjpci/bR+u+ESvdar6hk8XNK/TRR0fiGTp2QT5N+ducP0n3rfH/m9I7H/EQU6lsa2BrgxkEjw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-focus-within/node_modules/postcss-selector-parser": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", - "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-font-variant": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz", - "integrity": "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==", - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-gap-properties": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-6.0.0.tgz", - "integrity": "sha512-Om0WPjEwiM9Ru+VhfEDPZJAKWUd0mV1HmNXqp2C29z80aQ2uP9UVhLc7e3aYMIor/S5cVhoPgYQ7RtfeZpYTRw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-image-set-function": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-7.0.0.tgz", - "integrity": "sha512-QL7W7QNlZuzOwBTeXEmbVckNt1FSmhQtbMRvGGqqU4Nf4xk6KUEQhAoWuMzwbSv5jxiRiSZ5Tv7eiDB9U87znA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/utilities": "^2.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-lab-function": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-7.0.6.tgz", - "integrity": "sha512-HPwvsoK7C949vBZ+eMyvH2cQeMr3UREoHvbtra76/UhDuiViZH6pir+z71UaJQohd7VDSVUdR6TkWYKExEc9aQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/css-color-parser": "^3.0.6", - "@csstools/css-parser-algorithms": "^3.0.4", - "@csstools/css-tokenizer": "^3.0.3", - "@csstools/postcss-progressive-custom-properties": "^4.0.0", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-loader": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.3.4.tgz", - "integrity": "sha512-iW5WTTBSC5BfsBJ9daFMPVrLT36MrNiC6fqOZTTaHjBNX6Pfd5p+hSBqe/fEeNd7pc13QiAyGt7VdGMw4eRC4A==", - "dependencies": { - "cosmiconfig": "^8.3.5", - "jiti": "^1.20.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">= 14.15.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "postcss": "^7.0.0 || ^8.0.1", - "webpack": "^5.0.0" - } - }, - "node_modules/postcss-logical": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-8.0.0.tgz", - "integrity": "sha512-HpIdsdieClTjXLOyYdUPAX/XQASNIwdKt5hoZW08ZOAiI+tbV0ta1oclkpVkW5ANU+xJvk3KkA0FejkjGLXUkg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-merge-idents": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-merge-idents/-/postcss-merge-idents-6.0.3.tgz", - "integrity": "sha512-1oIoAsODUs6IHQZkLQGO15uGEbK3EAl5wi9SS8hs45VgsxQfMnxvt+L+zIr7ifZFIH14cfAeVe2uCTa+SPRa3g==", - "dependencies": { - "cssnano-utils": "^4.0.2", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-merge-longhand": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-6.0.5.tgz", - "integrity": "sha512-5LOiordeTfi64QhICp07nzzuTDjNSO8g5Ksdibt44d+uvIIAE1oZdRn8y/W5ZtYgRH/lnLDlvi9F8btZcVzu3w==", - "dependencies": { - "postcss-value-parser": "^4.2.0", - "stylehacks": "^6.1.1" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-merge-rules": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-6.1.1.tgz", - "integrity": "sha512-KOdWF0gju31AQPZiD+2Ar9Qjowz1LTChSjFFbS+e2sFgc4uHOp3ZvVX4sNeTlk0w2O31ecFGgrFzhO0RSWbWwQ==", - "dependencies": { - "browserslist": "^4.23.0", - "caniuse-api": "^3.0.0", - "cssnano-utils": "^4.0.2", - "postcss-selector-parser": "^6.0.16" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-minify-font-values": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-6.1.0.tgz", - "integrity": "sha512-gklfI/n+9rTh8nYaSJXlCo3nOKqMNkxuGpTn/Qm0gstL3ywTr9/WRKznE+oy6fvfolH6dF+QM4nCo8yPLdvGJg==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-minify-gradients": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-6.0.3.tgz", - "integrity": "sha512-4KXAHrYlzF0Rr7uc4VrfwDJ2ajrtNEpNEuLxFgwkhFZ56/7gaE4Nr49nLsQDZyUe+ds+kEhf+YAUolJiYXF8+Q==", - "dependencies": { - "colord": "^2.9.3", - "cssnano-utils": "^4.0.2", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-minify-params": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-6.1.0.tgz", - "integrity": "sha512-bmSKnDtyyE8ujHQK0RQJDIKhQ20Jq1LYiez54WiaOoBtcSuflfK3Nm596LvbtlFcpipMjgClQGyGr7GAs+H1uA==", - "dependencies": { - "browserslist": "^4.23.0", - "cssnano-utils": "^4.0.2", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-minify-selectors": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-6.0.4.tgz", - "integrity": "sha512-L8dZSwNLgK7pjTto9PzWRoMbnLq5vsZSTu8+j1P/2GB8qdtGQfn+K1uSvFgYvgh83cbyxT5m43ZZhUMTJDSClQ==", - "dependencies": { - "postcss-selector-parser": "^6.0.16" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-modules-extract-imports": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", - "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-modules-local-by-default": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.1.0.tgz", - "integrity": "sha512-rm0bdSv4jC3BDma3s9H19ZddW0aHX6EoqwDYU2IfZhRN+53QrufTRo2IdkAbRqLx4R2IYbZnbjKKxg4VN5oU9Q==", - "dependencies": { - "icss-utils": "^5.0.0", - "postcss-selector-parser": "^7.0.0", - "postcss-value-parser": "^4.1.0" - }, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-modules-local-by-default/node_modules/postcss-selector-parser": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", - "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-modules-scope": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", - "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", - "dependencies": { - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-modules-scope/node_modules/postcss-selector-parser": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", - "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-modules-values": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", - "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", - "dependencies": { - "icss-utils": "^5.0.0" - }, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-nesting": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-13.0.1.tgz", - "integrity": "sha512-VbqqHkOBOt4Uu3G8Dm8n6lU5+9cJFxiuty9+4rcoyRPO9zZS1JIs6td49VIoix3qYqELHlJIn46Oih9SAKo+yQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/selector-resolve-nested": "^3.0.0", - "@csstools/selector-specificity": "^5.0.0", - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-nesting/node_modules/@csstools/selector-resolve-nested": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-3.0.0.tgz", - "integrity": "sha512-ZoK24Yku6VJU1gS79a5PFmC8yn3wIapiKmPgun0hZgEI5AOqgH2kiPRsPz1qkGv4HL+wuDLH83yQyk6inMYrJQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss-selector-parser": "^7.0.0" - } - }, - "node_modules/postcss-nesting/node_modules/@csstools/selector-specificity": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", - "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss-selector-parser": "^7.0.0" - } - }, - "node_modules/postcss-nesting/node_modules/postcss-selector-parser": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", - "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-normalize-charset": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.2.tgz", - "integrity": "sha512-a8N9czmdnrjPHa3DeFlwqst5eaL5W8jYu3EBbTTkI5FHkfMhFZh1EGbku6jhHhIzTA6tquI2P42NtZ59M/H/kQ==", - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-normalize-display-values": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.2.tgz", - "integrity": "sha512-8H04Mxsb82ON/aAkPeq8kcBbAtI5Q2a64X/mnRRfPXBq7XeogoQvReqxEfc0B4WPq1KimjezNC8flUtC3Qz6jg==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-normalize-positions": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-6.0.2.tgz", - "integrity": "sha512-/JFzI441OAB9O7VnLA+RtSNZvQ0NCFZDOtp6QPFo1iIyawyXg0YI3CYM9HBy1WvwCRHnPep/BvI1+dGPKoXx/Q==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-normalize-repeat-style": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.2.tgz", - "integrity": "sha512-YdCgsfHkJ2jEXwR4RR3Tm/iOxSfdRt7jplS6XRh9Js9PyCR/aka/FCb6TuHT2U8gQubbm/mPmF6L7FY9d79VwQ==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-normalize-string": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-6.0.2.tgz", - "integrity": "sha512-vQZIivlxlfqqMp4L9PZsFE4YUkWniziKjQWUtsxUiVsSSPelQydwS8Wwcuw0+83ZjPWNTl02oxlIvXsmmG+CiQ==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-normalize-timing-functions": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.2.tgz", - "integrity": "sha512-a+YrtMox4TBtId/AEwbA03VcJgtyW4dGBizPl7e88cTFULYsprgHWTbfyjSLyHeBcK/Q9JhXkt2ZXiwaVHoMzA==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-normalize-unicode": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-6.1.0.tgz", - "integrity": "sha512-QVC5TQHsVj33otj8/JD869Ndr5Xcc/+fwRh4HAsFsAeygQQXm+0PySrKbr/8tkDKzW+EVT3QkqZMfFrGiossDg==", - "dependencies": { - "browserslist": "^4.23.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-normalize-url": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-6.0.2.tgz", - "integrity": "sha512-kVNcWhCeKAzZ8B4pv/DnrU1wNh458zBNp8dh4y5hhxih5RZQ12QWMuQrDgPRw3LRl8mN9vOVfHl7uhvHYMoXsQ==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-normalize-whitespace": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.2.tgz", - "integrity": "sha512-sXZ2Nj1icbJOKmdjXVT9pnyHQKiSAyuNQHSgRCUgThn2388Y9cGVDR+E9J9iAYbSbLHI+UUwLVl1Wzco/zgv0Q==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-opacity-percentage": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-opacity-percentage/-/postcss-opacity-percentage-3.0.0.tgz", - "integrity": "sha512-K6HGVzyxUxd/VgZdX04DCtdwWJ4NGLG212US4/LA1TLAbHgmAsTWVR86o+gGIbFtnTkfOpb9sCRBx8K7HO66qQ==", - "funding": [ - { - "type": "kofi", - "url": "https://ko-fi.com/mrcgrtz" - }, - { - "type": "liberapay", - "url": "https://liberapay.com/mrcgrtz" - } - ], - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-ordered-values": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-6.0.2.tgz", - "integrity": "sha512-VRZSOB+JU32RsEAQrO94QPkClGPKJEL/Z9PCBImXMhIeK5KAYo6slP/hBYlLgrCjFxyqvn5VC81tycFEDBLG1Q==", - "dependencies": { - "cssnano-utils": "^4.0.2", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-overflow-shorthand": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-6.0.0.tgz", - "integrity": "sha512-BdDl/AbVkDjoTofzDQnwDdm/Ym6oS9KgmO7Gr+LHYjNWJ6ExORe4+3pcLQsLA9gIROMkiGVjjwZNoL/mpXHd5Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-page-break": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-3.0.4.tgz", - "integrity": "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==", - "peerDependencies": { - "postcss": "^8" - } - }, - "node_modules/postcss-place": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-10.0.0.tgz", - "integrity": "sha512-5EBrMzat2pPAxQNWYavwAfoKfYcTADJ8AXGVPcUZ2UkNloUTWzJQExgrzrDkh3EKzmAx1evfTAzF9I8NGcc+qw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-preset-env": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-10.1.1.tgz", - "integrity": "sha512-wqqsnBFD6VIwcHHRbhjTOcOi4qRVlB26RwSr0ordPj7OubRRxdWebv/aLjKLRR8zkZrbxZyuus03nOIgC5elMQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "@csstools/postcss-cascade-layers": "^5.0.1", - "@csstools/postcss-color-function": "^4.0.6", - "@csstools/postcss-color-mix-function": "^3.0.6", - "@csstools/postcss-content-alt-text": "^2.0.4", - "@csstools/postcss-exponential-functions": "^2.0.5", - "@csstools/postcss-font-format-keywords": "^4.0.0", - "@csstools/postcss-gamut-mapping": "^2.0.6", - "@csstools/postcss-gradients-interpolation-method": "^5.0.6", - "@csstools/postcss-hwb-function": "^4.0.6", - "@csstools/postcss-ic-unit": "^4.0.0", - "@csstools/postcss-initial": "^2.0.0", - "@csstools/postcss-is-pseudo-class": "^5.0.1", - "@csstools/postcss-light-dark-function": "^2.0.7", - "@csstools/postcss-logical-float-and-clear": "^3.0.0", - "@csstools/postcss-logical-overflow": "^2.0.0", - "@csstools/postcss-logical-overscroll-behavior": "^2.0.0", - "@csstools/postcss-logical-resize": "^3.0.0", - "@csstools/postcss-logical-viewport-units": "^3.0.3", - "@csstools/postcss-media-minmax": "^2.0.5", - "@csstools/postcss-media-queries-aspect-ratio-number-values": "^3.0.4", - "@csstools/postcss-nested-calc": "^4.0.0", - "@csstools/postcss-normalize-display-values": "^4.0.0", - "@csstools/postcss-oklab-function": "^4.0.6", - "@csstools/postcss-progressive-custom-properties": "^4.0.0", - "@csstools/postcss-random-function": "^1.0.1", - "@csstools/postcss-relative-color-syntax": "^3.0.6", - "@csstools/postcss-scope-pseudo-class": "^4.0.1", - "@csstools/postcss-sign-functions": "^1.1.0", - "@csstools/postcss-stepped-value-functions": "^4.0.5", - "@csstools/postcss-text-decoration-shorthand": "^4.0.1", - "@csstools/postcss-trigonometric-functions": "^4.0.5", - "@csstools/postcss-unset-value": "^4.0.0", - "autoprefixer": "^10.4.19", - "browserslist": "^4.23.1", - "css-blank-pseudo": "^7.0.1", - "css-has-pseudo": "^7.0.1", - "css-prefers-color-scheme": "^10.0.0", - "cssdb": "^8.2.1", - "postcss-attribute-case-insensitive": "^7.0.1", - "postcss-clamp": "^4.1.0", - "postcss-color-functional-notation": "^7.0.6", - "postcss-color-hex-alpha": "^10.0.0", - "postcss-color-rebeccapurple": "^10.0.0", - "postcss-custom-media": "^11.0.5", - "postcss-custom-properties": "^14.0.4", - "postcss-custom-selectors": "^8.0.4", - "postcss-dir-pseudo-class": "^9.0.1", - "postcss-double-position-gradients": "^6.0.0", - "postcss-focus-visible": "^10.0.1", - "postcss-focus-within": "^9.0.1", - "postcss-font-variant": "^5.0.0", - "postcss-gap-properties": "^6.0.0", - "postcss-image-set-function": "^7.0.0", - "postcss-lab-function": "^7.0.6", - "postcss-logical": "^8.0.0", - "postcss-nesting": "^13.0.1", - "postcss-opacity-percentage": "^3.0.0", - "postcss-overflow-shorthand": "^6.0.0", - "postcss-page-break": "^3.0.4", - "postcss-place": "^10.0.0", - "postcss-pseudo-class-any-link": "^10.0.1", - "postcss-replace-overflow-wrap": "^4.0.0", - "postcss-selector-not": "^8.0.1" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-pseudo-class-any-link": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-10.0.1.tgz", - "integrity": "sha512-3el9rXlBOqTFaMFkWDOkHUTQekFIYnaQY55Rsp8As8QQkpiSgIYEcF/6Ond93oHiDsGb4kad8zjt+NPlOC1H0Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-pseudo-class-any-link/node_modules/postcss-selector-parser": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", - "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-reduce-idents": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-reduce-idents/-/postcss-reduce-idents-6.0.3.tgz", - "integrity": "sha512-G3yCqZDpsNPoQgbDUy3T0E6hqOQ5xigUtBQyrmq3tn2GxlyiL0yyl7H+T8ulQR6kOcHJ9t7/9H4/R2tv8tJbMA==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-reduce-initial": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.1.0.tgz", - "integrity": "sha512-RarLgBK/CrL1qZags04oKbVbrrVK2wcxhvta3GCxrZO4zveibqbRPmm2VI8sSgCXwoUHEliRSbOfpR0b/VIoiw==", - "dependencies": { - "browserslist": "^4.23.0", - "caniuse-api": "^3.0.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-reduce-transforms": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.2.tgz", - "integrity": "sha512-sB+Ya++3Xj1WaT9+5LOOdirAxP7dJZms3GRcYheSPi1PiTMigsxHAdkrbItHxwYHr4kt1zL7mmcHstgMYT+aiA==", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-replace-overflow-wrap": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz", - "integrity": "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==", - "peerDependencies": { - "postcss": "^8.0.3" - } - }, - "node_modules/postcss-selector-not": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-8.0.1.tgz", - "integrity": "sha512-kmVy/5PYVb2UOhy0+LqUYAhKj7DUGDpSWa5LZqlkWJaaAV+dxxsOG3+St0yNLu6vsKD7Dmqx+nWQt0iil89+WA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "dependencies": { - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-selector-not/node_modules/postcss-selector-parser": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", - "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-selector-parser": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", - "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-sort-media-queries": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/postcss-sort-media-queries/-/postcss-sort-media-queries-5.2.0.tgz", - "integrity": "sha512-AZ5fDMLD8SldlAYlvi8NIqo0+Z8xnXU2ia0jxmuhxAU+Lqt9K+AlmLNJ/zWEnE9x+Zx3qL3+1K20ATgNOr3fAA==", - "dependencies": { - "sort-css-media-queries": "2.2.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "postcss": "^8.4.23" - } - }, - "node_modules/postcss-svgo": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-6.0.3.tgz", - "integrity": "sha512-dlrahRmxP22bX6iKEjOM+c8/1p+81asjKT+V5lrgOH944ryx/OHpclnIbGsKVd3uWOXFLYJwCVf0eEkJGvO96g==", - "dependencies": { - "postcss-value-parser": "^4.2.0", - "svgo": "^3.2.0" - }, - "engines": { - "node": "^14 || ^16 || >= 18" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-unique-selectors": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-6.0.4.tgz", - "integrity": "sha512-K38OCaIrO8+PzpArzkLKB42dSARtC2tmG6PvD4b1o1Q2E9Os8jzfWFfSy/rixsHwohtsDdFtAWGjFVFUdwYaMg==", - "dependencies": { - "postcss-selector-parser": "^6.0.16" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" - }, - "node_modules/postcss-zindex": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-zindex/-/postcss-zindex-6.0.2.tgz", - "integrity": "sha512-5BxW9l1evPB/4ZIc+2GobEBoKC+h8gPGCMi+jxsYvd2x0mjq7wazk6DrP71pStqxE9Foxh5TVnonbWpFZzXaYg==", - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/pretty-error": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", - "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", - "dependencies": { - "lodash": "^4.17.20", - "renderkid": "^3.0.0" - } - }, - "node_modules/pretty-time": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pretty-time/-/pretty-time-1.1.0.tgz", - "integrity": "sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA==", - "engines": { - "node": ">=4" - } - }, - "node_modules/prism-react-renderer": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.4.1.tgz", - "integrity": "sha512-ey8Ls/+Di31eqzUxC46h8MksNuGx/n0AAC8uKpwFau4RPDYLuE3EXTp8N8G2vX2N7UC/+IXeNUnlWBGGcAG+Ig==", - "dependencies": { - "@types/prismjs": "^1.26.0", - "clsx": "^2.0.0" - }, - "peerDependencies": { - "react": ">=16.0.0" - } - }, - "node_modules/prismjs": { - "version": "1.29.0", - "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", - "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==", - "engines": { - "node": ">=6" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "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==", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, - "node_modules/property-information": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", - "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/proto-list": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", - "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==" - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/proxy-addr/node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/pupa": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/pupa/-/pupa-3.1.0.tgz", - "integrity": "sha512-FLpr4flz5xZTSJxSeaheeMKN/EDzMdK7b8PTOC6a5PYFKTucWbdqjgqaEyH0shFiSJrVB1+Qqi4Tk19ccU6Aug==", - "dependencies": { - "escape-goat": "^4.0.0" - }, - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "dependencies": { - "side-channel": "^1.0.6" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/queue": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", - "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==", - "dependencies": { - "inherits": "~2.0.3" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/range-parser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", - "integrity": "sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/raw-body/node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, - "node_modules/rc/node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "engines": { - "node": ">=0.10.0" - } - }, - "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==", - "dependencies": { - "loose-envify": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-dev-utils": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz", - "integrity": "sha512-84Ivxmr17KjUupyqzFode6xKhjwuEJDROWKJy/BthkL7Wn6NJ8h4WE6k/exAv6ImS+0oZLRRW5j/aINMHyeGeQ==", - "dependencies": { - "@babel/code-frame": "^7.16.0", - "address": "^1.1.2", - "browserslist": "^4.18.1", - "chalk": "^4.1.2", - "cross-spawn": "^7.0.3", - "detect-port-alt": "^1.1.6", - "escape-string-regexp": "^4.0.0", - "filesize": "^8.0.6", - "find-up": "^5.0.0", - "fork-ts-checker-webpack-plugin": "^6.5.0", - "global-modules": "^2.0.0", - "globby": "^11.0.4", - "gzip-size": "^6.0.0", - "immer": "^9.0.7", - "is-root": "^2.1.0", - "loader-utils": "^3.2.0", - "open": "^8.4.0", - "pkg-up": "^3.1.0", - "prompts": "^2.4.2", - "react-error-overlay": "^6.0.11", - "recursive-readdir": "^2.2.2", - "shell-quote": "^1.7.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/react-dev-utils/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/react-dev-utils/node_modules/loader-utils": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.3.1.tgz", - "integrity": "sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg==", - "engines": { - "node": ">= 12.13.0" - } - }, - "node_modules/react-dev-utils/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/react-dev-utils/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/react-dev-utils/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/react-dev-utils/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "engines": { - "node": ">=8" - } - }, - "node_modules/react-dev-utils/node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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==", - "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.2" - }, - "peerDependencies": { - "react": "^18.3.1" - } - }, - "node_modules/react-error-overlay": { - "version": "6.0.11", - "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz", - "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==" - }, - "node_modules/react-fast-compare": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", - "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==" - }, - "node_modules/react-helmet-async": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/react-helmet-async/-/react-helmet-async-1.3.0.tgz", - "integrity": "sha512-9jZ57/dAn9t3q6hneQS0wukqC2ENOBgMNVEhb/ZG9ZSxUetzVIw4iAmEU38IaVg3QGYauQPhSeUTuIUtFglWpg==", - "dependencies": { - "@babel/runtime": "^7.12.5", - "invariant": "^2.2.4", - "prop-types": "^15.7.2", - "react-fast-compare": "^3.2.0", - "shallowequal": "^1.1.0" - }, - "peerDependencies": { - "react": "^16.6.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.6.0 || ^17.0.0 || ^18.0.0" - } - }, - "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==" - }, - "node_modules/react-json-view-lite": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/react-json-view-lite/-/react-json-view-lite-1.5.0.tgz", - "integrity": "sha512-nWqA1E4jKPklL2jvHWs6s+7Na0qNgw9HCP6xehdQJeg6nPBTFZgGwyko9Q0oj+jQWKTTVRS30u0toM5wiuL3iw==", - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "react": "^16.13.1 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/react-loadable": { - "name": "@docusaurus/react-loadable", - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@docusaurus/react-loadable/-/react-loadable-6.0.0.tgz", - "integrity": "sha512-YMMxTUQV/QFSnbgrP3tjDzLHRg7vsbMn8e9HAa8o/1iXoiomo48b7sk/kkmWEuWNDPJVlKSJRB6Y2fHqdJk+SQ==", - "dependencies": { - "@types/react": "*" - }, - "peerDependencies": { - "react": "*" - } - }, - "node_modules/react-loadable-ssr-addon-v5-slorber": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/react-loadable-ssr-addon-v5-slorber/-/react-loadable-ssr-addon-v5-slorber-1.0.1.tgz", - "integrity": "sha512-lq3Lyw1lGku8zUEJPDxsNm1AfYHBrO9Y1+olAYwpUJ2IGFBskM0DMKok97A6LWUpHm+o7IvQBOWu9MLenp9Z+A==", - "dependencies": { - "@babel/runtime": "^7.10.3" - }, - "engines": { - "node": ">=10.13.0" - }, - "peerDependencies": { - "react-loadable": "*", - "webpack": ">=4.41.1 || 5.x" - } - }, - "node_modules/react-router": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.4.tgz", - "integrity": "sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA==", - "dependencies": { - "@babel/runtime": "^7.12.13", - "history": "^4.9.0", - "hoist-non-react-statics": "^3.1.0", - "loose-envify": "^1.3.1", - "path-to-regexp": "^1.7.0", - "prop-types": "^15.6.2", - "react-is": "^16.6.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0" - }, - "peerDependencies": { - "react": ">=15" - } - }, - "node_modules/react-router-config": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/react-router-config/-/react-router-config-5.1.1.tgz", - "integrity": "sha512-DuanZjaD8mQp1ppHjgnnUnyOlqYXZVjnov/JzFhjLEwd3Z4dYjMSnqrEzzGThH47vpCOqPPwJM2FtthLeJ8Pbg==", - "dependencies": { - "@babel/runtime": "^7.1.2" - }, - "peerDependencies": { - "react": ">=15", - "react-router": ">=5" - } - }, - "node_modules/react-router-dom": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.3.4.tgz", - "integrity": "sha512-m4EqFMHv/Ih4kpcBCONHbkT68KoAeHN4p3lAGoNryfHi0dMy0kCzEZakiKRsvg5wHZ/JLrLW8o8KomWiz/qbYQ==", - "dependencies": { - "@babel/runtime": "^7.12.13", - "history": "^4.9.0", - "loose-envify": "^1.3.1", - "prop-types": "^15.6.2", - "react-router": "5.3.4", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0" - }, - "peerDependencies": { - "react": ">=15" - } - }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/reading-time": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/reading-time/-/reading-time-1.5.0.tgz", - "integrity": "sha512-onYyVhBNr4CmAxFsKS7bz+uTLRakypIe4R+5A824vBSkQy/hB3fZepoVEf8OVAxzLvK+H/jm9TzpI3ETSm64Kg==" - }, - "node_modules/rechoir": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", - "dependencies": { - "resolve": "^1.1.6" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/recursive-readdir": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz", - "integrity": "sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA==", - "dependencies": { - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==" - }, - "node_modules/regenerate-unicode-properties": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", - "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", - "dependencies": { - "regenerate": "^1.4.2" - }, - "engines": { - "node": ">=4" - } - }, - "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==" - }, - "node_modules/regenerator-transform": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", - "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", - "dependencies": { - "@babel/runtime": "^7.8.4" - } - }, - "node_modules/regexpu-core": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz", - "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==", - "dependencies": { - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.2.0", - "regjsgen": "^0.8.0", - "regjsparser": "^0.12.0", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/registry-auth-token": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.0.2.tgz", - "integrity": "sha512-o/3ikDxtXaA59BmZuZrJZDJv8NMDGSj+6j6XaeBmHw8eY1i1qd9+6H+LjVvQXx3HN6aRCGa1cUdJ9RaJZUugnQ==", - "dependencies": { - "@pnpm/npm-conf": "^2.1.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/registry-url": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-6.0.1.tgz", - "integrity": "sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==", - "dependencies": { - "rc": "1.2.8" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/regjsgen": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", - "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==" - }, - "node_modules/regjsparser": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz", - "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==", - "dependencies": { - "jsesc": "~3.0.2" - }, - "bin": { - "regjsparser": "bin/parser" - } - }, - "node_modules/rehype-raw": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", - "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==", - "dependencies": { - "@types/hast": "^3.0.0", - "hast-util-raw": "^9.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/relateurl": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", - "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/remark-directive": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/remark-directive/-/remark-directive-3.0.0.tgz", - "integrity": "sha512-l1UyWJ6Eg1VPU7Hm/9tt0zKtReJQNOA4+iDMAxTyZNWnJnFlbS/7zhiel/rogTLQ2vMYwDzSJa4BiVNqGlqIMA==", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-directive": "^3.0.0", - "micromark-extension-directive": "^3.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-emoji": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/remark-emoji/-/remark-emoji-4.0.1.tgz", - "integrity": "sha512-fHdvsTR1dHkWKev9eNyhTo4EFwbUvJ8ka9SgeWkMPYFX4WoI7ViVBms3PjlQYgw5TLvNQso3GUB/b/8t3yo+dg==", - "dependencies": { - "@types/mdast": "^4.0.2", - "emoticon": "^4.0.1", - "mdast-util-find-and-replace": "^3.0.1", - "node-emoji": "^2.1.0", - "unified": "^11.0.4" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, - "node_modules/remark-frontmatter": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/remark-frontmatter/-/remark-frontmatter-5.0.0.tgz", - "integrity": "sha512-XTFYvNASMe5iPN0719nPrdItC9aU0ssC4v14mH1BCi1u0n1gAocqcujWUrByftZTbLhRtiKRyjYTSIOcr69UVQ==", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-frontmatter": "^2.0.0", - "micromark-extension-frontmatter": "^2.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-gfm": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.0.tgz", - "integrity": "sha512-U92vJgBPkbw4Zfu/IiW2oTZLSL3Zpv+uI7My2eq8JxKgqraFdU8YUGicEJCEgSbeaG+QDFqIcwwfMTOEelPxuA==", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-gfm": "^3.0.0", - "micromark-extension-gfm": "^3.0.0", - "remark-parse": "^11.0.0", - "remark-stringify": "^11.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-mdx": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.0.1.tgz", - "integrity": "sha512-3Pz3yPQ5Rht2pM5R+0J2MrGoBSrzf+tJG94N+t/ilfdh8YLyyKYtidAYwTveB20BoHAcwIopOUqhcmh2F7hGYA==", - "dependencies": { - "mdast-util-mdx": "^3.0.0", - "micromark-extension-mdxjs": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "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==", - "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.0", - "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.0.tgz", - "integrity": "sha512-z3tJrAs2kIs1AqIIy6pzHmAHlF1hWQ+OdY4/hv+Wxe35EhyLKcajL33iUEn3ScxtFox9nUvRufR/Zre8Q08H/g==", - "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/remark-stringify": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", - "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-to-markdown": "^2.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/renderkid": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", - "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", - "dependencies": { - "css-select": "^4.1.3", - "dom-converter": "^0.2.0", - "htmlparser2": "^6.1.0", - "lodash": "^4.17.21", - "strip-ansi": "^6.0.1" - } - }, - "node_modules/renderkid/node_modules/css-select": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", - "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.0.1", - "domhandler": "^4.3.1", - "domutils": "^2.8.0", - "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/renderkid/node_modules/dom-serializer": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", - "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/renderkid/node_modules/domhandler": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", - "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", - "dependencies": { - "domelementtype": "^2.2.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/renderkid/node_modules/domutils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", - "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", - "dependencies": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/renderkid/node_modules/entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/renderkid/node_modules/htmlparser2": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", - "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.0.0", - "domutils": "^2.5.2", - "entities": "^2.0.0" - } - }, - "node_modules/repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-like": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/require-like/-/require-like-0.1.2.tgz", - "integrity": "sha512-oyrU88skkMtDdauHDuKVrgR+zuItqr6/c//FXzvmxRGMexSDc6hNvJInGW3LL46n+8b50RykrvwSUIIQH2LQ5A==", - "engines": { - "node": "*" - } - }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" - }, - "node_modules/resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", - "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-alpn": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", - "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==" - }, - "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==", - "engines": { - "node": ">=4" - } - }, - "node_modules/resolve-pathname": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", - "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==" - }, - "node_modules/responselike": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", - "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", - "dependencies": { - "lowercase-keys": "^3.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", - "engines": { - "node": ">= 4" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rtl-detect": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/rtl-detect/-/rtl-detect-1.1.2.tgz", - "integrity": "sha512-PGMBq03+TTG/p/cRB7HCLKJ1MgDIi07+QU1faSjiYRfmY5UsAttV9Hs08jDAHVwcOwmVLcSJkpwyfXszVjWfIQ==" - }, - "node_modules/rtlcss": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.3.0.tgz", - "integrity": "sha512-FI+pHEn7Wc4NqKXMXFM+VAYKEj/mRIcW4h24YVwVtyjI+EqGrLc2Hx/Ny0lrZ21cBWU2goLy36eqMcNj3AQJig==", - "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0", - "postcss": "^8.4.21", - "strip-json-comments": "^3.1.1" - }, - "bin": { - "rtlcss": "bin/rtlcss.js" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "node_modules/sax": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", - "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==" - }, - "node_modules/scheduler": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", - "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", - "dependencies": { - "loose-envify": "^1.1.0" - } - }, - "node_modules/schema-utils": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", - "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/search-insights": { - "version": "2.17.3", - "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.3.tgz", - "integrity": "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==", - "peer": true - }, - "node_modules/section-matter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", - "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", - "dependencies": { - "extend-shallow": "^2.0.1", - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/select-hose": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", - "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==" - }, - "node_modules/selfsigned": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", - "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", - "dependencies": { - "@types/node-forge": "^1.3.0", - "node-forge": "^1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/semver": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", - "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/semver-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-4.0.0.tgz", - "integrity": "sha512-0Ju4+6A8iOnpL/Thra7dZsSlOHYAHIeMxfhWQRI1/VLcT3WDBZKKtQt/QkBOsiIN9ZpuvHE6cGZ0x4glCMmfiA==", - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/send/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==" - }, - "node_modules/send/node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/serve-handler": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.6.tgz", - "integrity": "sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==", - "dependencies": { - "bytes": "3.0.0", - "content-disposition": "0.5.2", - "mime-types": "2.1.18", - "minimatch": "3.1.2", - "path-is-inside": "1.0.2", - "path-to-regexp": "3.3.0", - "range-parser": "1.2.0" - } - }, - "node_modules/serve-handler/node_modules/path-to-regexp": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz", - "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==" - }, - "node_modules/serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", - "dependencies": { - "accepts": "~1.3.4", - "batch": "0.6.1", - "debug": "2.6.9", - "escape-html": "~1.0.3", - "http-errors": "~1.6.2", - "mime-types": "~2.1.17", - "parseurl": "~1.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/serve-index/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/serve-index/node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-index/node_modules/http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-index/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==" - }, - "node_modules/serve-index/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/serve-index/node_modules/setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" - }, - "node_modules/serve-index/node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", - "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.19.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, - "node_modules/shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shallowequal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", - "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "engines": { - "node": ">=8" - } - }, - "node_modules/shell-quote": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", - "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/shelljs": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", - "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", - "dependencies": { - "glob": "^7.0.0", - "interpret": "^1.0.0", - "rechoir": "^0.6.2" - }, - "bin": { - "shjs": "bin/shjs" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", - "dependencies": { - "call-bind": "^1.0.7", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" - }, - "node_modules/sirv": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", - "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==", - "dependencies": { - "@polka/url": "^1.0.0-next.24", - "mrmime": "^2.0.0", - "totalist": "^3.0.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" - }, - "node_modules/sitemap": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-7.1.2.tgz", - "integrity": "sha512-ARCqzHJ0p4gWt+j7NlU5eDlIO9+Rkr/JhPFZKKQ1l5GCus7rJH4UdrlVAh0xC/gDS/Qir2UMxqYNHtsKr2rpCw==", - "dependencies": { - "@types/node": "^17.0.5", - "@types/sax": "^1.2.1", - "arg": "^5.0.0", - "sax": "^1.2.4" - }, - "bin": { - "sitemap": "dist/cli.js" - }, - "engines": { - "node": ">=12.0.0", - "npm": ">=5.6.0" - } - }, - "node_modules/sitemap/node_modules/@types/node": { - "version": "17.0.45", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", - "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==" - }, - "node_modules/skin-tone": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/skin-tone/-/skin-tone-2.0.0.tgz", - "integrity": "sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==", - "dependencies": { - "unicode-emoji-modifier-base": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "engines": { - "node": ">=8" - } - }, - "node_modules/snake-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", - "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", - "dependencies": { - "dot-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/sockjs": { - "version": "0.3.24", - "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", - "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", - "dependencies": { - "faye-websocket": "^0.11.3", - "uuid": "^8.3.2", - "websocket-driver": "^0.7.4" - } - }, - "node_modules/sort-css-media-queries": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/sort-css-media-queries/-/sort-css-media-queries-2.2.0.tgz", - "integrity": "sha512-0xtkGhWCC9MGt/EzgnvbbbKhqWjl1+/rncmhTh5qCpbYguXh6S/qwePfv/JQ8jePXXmqingylxoC49pCkSPIbA==", - "engines": { - "node": ">= 6.3.0" - } - }, - "node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "engines": { - "node": ">= 8" - } - }, - "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/source-map-support/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "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==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/spdy": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", - "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", - "dependencies": { - "debug": "^4.1.0", - "handle-thing": "^2.0.0", - "http-deceiver": "^1.2.7", - "select-hose": "^2.0.0", - "spdy-transport": "^3.0.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/spdy-transport": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", - "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", - "dependencies": { - "debug": "^4.1.0", - "detect-node": "^2.0.4", - "hpack.js": "^2.1.6", - "obuf": "^1.1.2", - "readable-stream": "^3.0.6", - "wbuf": "^1.7.3" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" - }, - "node_modules/srcset": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/srcset/-/srcset-4.0.0.tgz", - "integrity": "sha512-wvLeHgcVHKO8Sc/H/5lkGreJQVeYMm9rlmt8PuR1xE31rIuXhuzznUUqAt8MqLhB3MqJdFzlNAfpcWnxiFUcPw==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/std-env": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.8.0.tgz", - "integrity": "sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==" - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string-width/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/string-width/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "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==", - "dependencies": { - "character-entities-html4": "^2.0.0", - "character-entities-legacy": "^3.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/stringify-object": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", - "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", - "dependencies": { - "get-own-enumerable-property-symbols": "^3.0.0", - "is-obj": "^1.0.1", - "is-regexp": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom-string": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", - "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/style-to-object": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.4.4.tgz", - "integrity": "sha512-HYNoHZa2GorYNyqiCaBgsxvcJIn7OHq6inEga+E6Ke3m5JkoqpQbnFssk4jwe+K7AhGa2fcha4wSOf1Kn01dMg==", - "dependencies": { - "inline-style-parser": "0.1.1" - } - }, - "node_modules/stylehacks": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-6.1.1.tgz", - "integrity": "sha512-gSTTEQ670cJNoaeIp9KX6lZmm8LJ3jPB5yJmX8Zq/wQxOsAFXV3qjWzHas3YYk1qesuVIyYWWUpZ0vSE/dTSGg==", - "dependencies": { - "browserslist": "^4.23.0", - "postcss-selector-parser": "^6.0.16" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "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==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/svg-parser": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", - "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==" - }, - "node_modules/svgo": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz", - "integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==", - "dependencies": { - "@trysound/sax": "0.2.0", - "commander": "^7.2.0", - "css-select": "^5.1.0", - "css-tree": "^2.3.1", - "css-what": "^6.1.0", - "csso": "^5.0.5", - "picocolors": "^1.0.0" - }, - "bin": { - "svgo": "bin/svgo" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/svgo" - } - }, - "node_modules/svgo/node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "engines": { - "node": ">= 10" - } - }, - "node_modules/tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/terser": { - "version": "5.31.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.1.tgz", - "integrity": "sha512-37upzU1+viGvuFtBo9NPufCb9dwM0+l9hMxYyWfBA+fbwrPqNJAhbZ6W47bBFnZHKHTUBnMvi87434qq+qnxOg==", - "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser-webpack-plugin": { - "version": "5.3.10", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", - "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.20", - "jest-worker": "^27.4.5", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.1", - "terser": "^5.26.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "uglify-js": { - "optional": true - } - } - }, - "node_modules/terser-webpack-plugin/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/terser-webpack-plugin/node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/terser-webpack-plugin/node_modules/jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" - }, - "node_modules/terser-webpack-plugin/node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/terser-webpack-plugin/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" - }, - "node_modules/thunky": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", - "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==" - }, - "node_modules/tiny-invariant": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", - "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==" - }, - "node_modules/tiny-warning": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", - "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" - }, - "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==", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/totalist": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", - "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", - "engines": { - "node": ">=6" - } - }, - "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==", - "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==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/tslib": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", - "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==" - }, - "node_modules/type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/type-is/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/type-is/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dependencies": { - "is-typedarray": "^1.0.0" - } - }, - "node_modules/typescript": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz", - "integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==", - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" - }, - "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", - "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-emoji-modifier-base": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz", - "integrity": "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==", - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "dependencies": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", - "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", - "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", - "engines": { - "node": ">=4" - } - }, - "node_modules/unified": { - "version": "11.0.5", - "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", - "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", - "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/unique-string": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-3.0.0.tgz", - "integrity": "sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==", - "dependencies": { - "crypto-random-string": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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==", - "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==", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-position-from-estree": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz", - "integrity": "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-remove-position": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz", - "integrity": "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-visit": "^5.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==", - "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==", - "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==", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", - "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", - "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" - } - ], - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.0" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/update-notifier": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-6.0.2.tgz", - "integrity": "sha512-EDxhTEVPZZRLWYcJ4ZXjGFN0oP7qYvbXWzEgRm/Yql4dHX5wDbvh89YHP6PK1lzZJYrMtXUuZZz8XGK+U6U1og==", - "dependencies": { - "boxen": "^7.0.0", - "chalk": "^5.0.1", - "configstore": "^6.0.0", - "has-yarn": "^3.0.0", - "import-lazy": "^4.0.0", - "is-ci": "^3.0.1", - "is-installed-globally": "^0.4.0", - "is-npm": "^6.0.0", - "is-yarn-global": "^0.4.0", - "latest-version": "^7.0.0", - "pupa": "^3.1.0", - "semver": "^7.3.7", - "semver-diff": "^4.0.0", - "xdg-basedir": "^5.1.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/yeoman/update-notifier?sponsor=1" - } - }, - "node_modules/update-notifier/node_modules/boxen": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.1.1.tgz", - "integrity": "sha512-2hCgjEmP8YLWQ130n2FerGv7rYpfBmnmp9Uy2Le1vge6X3gZIfSmEzP5QTDElFxcvVcXlEn8Aq6MU/PZygIOog==", - "dependencies": { - "ansi-align": "^3.0.1", - "camelcase": "^7.0.1", - "chalk": "^5.2.0", - "cli-boxes": "^3.0.0", - "string-width": "^5.1.2", - "type-fest": "^2.13.0", - "widest-line": "^4.0.1", - "wrap-ansi": "^8.1.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/update-notifier/node_modules/camelcase": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz", - "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/update-notifier/node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/uri-js/node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "engines": { - "node": ">=6" - } - }, - "node_modules/url-loader": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-4.1.1.tgz", - "integrity": "sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA==", - "dependencies": { - "loader-utils": "^2.0.0", - "mime-types": "^2.1.27", - "schema-utils": "^3.0.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "file-loader": "*", - "webpack": "^4.0.0 || ^5.0.0" - }, - "peerDependenciesMeta": { - "file-loader": { - "optional": true - } - } - }, - "node_modules/url-loader/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/url-loader/node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/url-loader/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" - }, - "node_modules/url-loader/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/url-loader/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/url-loader/node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" - }, - "node_modules/utila": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", - "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==" - }, - "node_modules/utility-types": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.11.0.tgz", - "integrity": "sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==", - "engines": { - "node": ">= 4" - } - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/value-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", - "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==" - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/vfile": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.1.tgz", - "integrity": "sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-stringify-position": "^4.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vfile-location": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.2.tgz", - "integrity": "sha512-NXPYyxyBSH7zB5U6+3uDdd6Nybz6o6/od9rk8bp9H8GR3L+cm/fC0uUTbqBmUTnMCUDslAGBOIKNfvvb+gGlDg==", - "dependencies": { - "@types/unist": "^3.0.0", - "vfile": "^6.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==", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-stringify-position": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/watchpack": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.1.tgz", - "integrity": "sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg==", - "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/wbuf": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", - "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", - "dependencies": { - "minimalistic-assert": "^1.0.0" - } - }, - "node_modules/web-namespaces": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", - "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/webpack": { - "version": "5.96.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.96.1.tgz", - "integrity": "sha512-l2LlBSvVZGhL4ZrPwyr8+37AunkcYj5qh8o6u2/2rzoPc8gxFJkLj1WxNgooi9pnoc06jh0BjuXnamM4qlujZA==", - "dependencies": { - "@types/eslint-scope": "^3.7.7", - "@types/estree": "^1.0.6", - "@webassemblyjs/ast": "^1.12.1", - "@webassemblyjs/wasm-edit": "^1.12.1", - "@webassemblyjs/wasm-parser": "^1.12.1", - "acorn": "^8.14.0", - "browserslist": "^4.24.0", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.1", - "es-module-lexer": "^1.2.1", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.11", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^3.2.0", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.10", - "watchpack": "^2.4.1", - "webpack-sources": "^3.2.3" - }, - "bin": { - "webpack": "bin/webpack.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/webpack-bundle-analyzer": { - "version": "4.10.2", - "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.2.tgz", - "integrity": "sha512-vJptkMm9pk5si4Bv922ZbKLV8UTT4zib4FPgXMhgzUny0bfDDkLXAVQs3ly3fS4/TN9ROFtb0NFrm04UXFE/Vw==", - "dependencies": { - "@discoveryjs/json-ext": "0.5.7", - "acorn": "^8.0.4", - "acorn-walk": "^8.0.0", - "commander": "^7.2.0", - "debounce": "^1.2.1", - "escape-string-regexp": "^4.0.0", - "gzip-size": "^6.0.0", - "html-escaper": "^2.0.2", - "opener": "^1.5.2", - "picocolors": "^1.0.0", - "sirv": "^2.0.3", - "ws": "^7.3.1" - }, - "bin": { - "webpack-bundle-analyzer": "lib/bin/analyzer.js" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/webpack-bundle-analyzer/node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "engines": { - "node": ">= 10" - } - }, - "node_modules/webpack-dev-middleware": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz", - "integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==", - "dependencies": { - "colorette": "^2.0.10", - "memfs": "^3.4.3", - "mime-types": "^2.1.31", - "range-parser": "^1.2.1", - "schema-utils": "^4.0.0" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" - } - }, - "node_modules/webpack-dev-middleware/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/webpack-dev-middleware/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/webpack-dev-middleware/node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/webpack-dev-server": { - "version": "4.15.2", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.2.tgz", - "integrity": "sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g==", - "dependencies": { - "@types/bonjour": "^3.5.9", - "@types/connect-history-api-fallback": "^1.3.5", - "@types/express": "^4.17.13", - "@types/serve-index": "^1.9.1", - "@types/serve-static": "^1.13.10", - "@types/sockjs": "^0.3.33", - "@types/ws": "^8.5.5", - "ansi-html-community": "^0.0.8", - "bonjour-service": "^1.0.11", - "chokidar": "^3.5.3", - "colorette": "^2.0.10", - "compression": "^1.7.4", - "connect-history-api-fallback": "^2.0.0", - "default-gateway": "^6.0.3", - "express": "^4.17.3", - "graceful-fs": "^4.2.6", - "html-entities": "^2.3.2", - "http-proxy-middleware": "^2.0.3", - "ipaddr.js": "^2.0.1", - "launch-editor": "^2.6.0", - "open": "^8.0.9", - "p-retry": "^4.5.0", - "rimraf": "^3.0.2", - "schema-utils": "^4.0.0", - "selfsigned": "^2.1.1", - "serve-index": "^1.9.1", - "sockjs": "^0.3.24", - "spdy": "^4.0.2", - "webpack-dev-middleware": "^5.3.4", - "ws": "^8.13.0" - }, - "bin": { - "webpack-dev-server": "bin/webpack-dev-server.js" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^4.37.0 || ^5.0.0" - }, - "peerDependenciesMeta": { - "webpack": { - "optional": true - }, - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/webpack-dev-server/node_modules/ws": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", - "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/webpack-merge": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", - "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", - "dependencies": { - "clone-deep": "^4.0.1", - "flat": "^5.0.2", - "wildcard": "^2.0.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/webpack/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/webpack/node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/webpack/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" - }, - "node_modules/webpack/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/webpack/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/webpack/node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/webpackbar": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/webpackbar/-/webpackbar-6.0.1.tgz", - "integrity": "sha512-TnErZpmuKdwWBdMoexjio3KKX6ZtoKHRVvLIU0A47R0VVBDtx3ZyOJDktgYixhoJokZTYTt1Z37OkO9pnGJa9Q==", - "dependencies": { - "ansi-escapes": "^4.3.2", - "chalk": "^4.1.2", - "consola": "^3.2.3", - "figures": "^3.2.0", - "markdown-table": "^2.0.0", - "pretty-time": "^1.1.0", - "std-env": "^3.7.0", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=14.21.3" - }, - "peerDependencies": { - "webpack": "3 || 4 || 5" - } - }, - "node_modules/webpackbar/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "node_modules/webpackbar/node_modules/markdown-table": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-2.0.0.tgz", - "integrity": "sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==", - "dependencies": { - "repeat-string": "^1.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/webpackbar/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/webpackbar/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/websocket-driver": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", - "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", - "dependencies": { - "http-parser-js": ">=0.5.1", - "safe-buffer": ">=5.1.0", - "websocket-extensions": ">=0.1.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/websocket-extensions": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", - "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/widest-line": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", - "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==", - "dependencies": { - "string-width": "^5.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/wildcard": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", - "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==" - }, - "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" - }, - "node_modules/write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dependencies": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, - "node_modules/ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xdg-basedir": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-5.1.0.tgz", - "integrity": "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/xml-js": { - "version": "1.6.11", - "resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz", - "integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==", - "dependencies": { - "sax": "^1.2.4" - }, - "bin": { - "xml-js": "bin/cli.js" - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" - }, - "node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "engines": { - "node": ">= 6" - } - }, - "node_modules/yocto-queue": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.1.1.tgz", - "integrity": "sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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==", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - } - } -} diff --git a/docs/package.json b/docs/package.json deleted file mode 100644 index 922f94fbc..000000000 --- a/docs/package.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "name": "website", - "version": "2024.08.01", - "private": true, - "scripts": { - "docusaurus": "docusaurus", - "start": "docusaurus start", - "build": "docusaurus build", - "swizzle": "docusaurus swizzle", - "deploy": "docusaurus deploy", - "clear": "docusaurus clear", - "serve": "docusaurus serve", - "write-translations": "docusaurus write-translations", - "write-heading-ids": "docusaurus write-heading-ids" - }, - "dependencies": { - "@docusaurus/core": "3.6.3", - "@docusaurus/plugin-client-redirects": "^3.6.3", - "@docusaurus/preset-classic": "3.6.3", - "@mdx-js/react": "^3.1.0", - "clsx": "^2.0.0", - "docusaurus-theme-github-codeblock": "^2.0.2", - "prism-react-renderer": "^2.4.1", - "react": "^18.0.0", - "react-dom": "^18.0.0" - }, - "devDependencies": { - "@docusaurus/module-type-aliases": "3.6.3", - "@docusaurus/types": "3.6.3" - }, - "browserslist": { - "production": [ - ">0.5%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 3 chrome version", - "last 3 firefox version", - "last 5 safari version" - ] - }, - "engines": { - "node": ">=20.0" - } -} diff --git a/docs/static/api-docs/slack_bolt/adapter/aiohttp/index.html b/docs/reference/adapter/aiohttp/index.html similarity index 98% rename from docs/static/api-docs/slack_bolt/adapter/aiohttp/index.html rename to docs/reference/adapter/aiohttp/index.html index 879b7a023..7d7ceedbe 100644 --- a/docs/static/api-docs/slack_bolt/adapter/aiohttp/index.html +++ b/docs/reference/adapter/aiohttp/index.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.aiohttp API documentation @@ -122,7 +122,7 @@

Functions

diff --git a/docs/static/api-docs/slack_bolt/adapter/asgi/aiohttp/index.html b/docs/reference/adapter/asgi/aiohttp/index.html similarity index 99% rename from docs/static/api-docs/slack_bolt/adapter/asgi/aiohttp/index.html rename to docs/reference/adapter/asgi/aiohttp/index.html index d598dc6cb..a6aa7c92d 100644 --- a/docs/static/api-docs/slack_bolt/adapter/asgi/aiohttp/index.html +++ b/docs/reference/adapter/asgi/aiohttp/index.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.asgi.aiohttp API documentation @@ -158,7 +158,7 @@

diff --git a/docs/static/api-docs/slack_bolt/adapter/asgi/async_handler.html b/docs/reference/adapter/asgi/async_handler.html similarity index 99% rename from docs/static/api-docs/slack_bolt/adapter/asgi/async_handler.html rename to docs/reference/adapter/asgi/async_handler.html index 9ecdb6fd3..23433ffce 100644 --- a/docs/static/api-docs/slack_bolt/adapter/asgi/async_handler.html +++ b/docs/reference/adapter/asgi/async_handler.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.asgi.async_handler API documentation @@ -158,7 +158,7 @@

-

Generated by pdoc 0.11.5.

+

Generated by pdoc 0.11.6.

diff --git a/docs/static/api-docs/slack_bolt/adapter/asgi/base_handler.html b/docs/reference/adapter/asgi/base_handler.html similarity index 95% rename from docs/static/api-docs/slack_bolt/adapter/asgi/base_handler.html rename to docs/reference/adapter/asgi/base_handler.html index 2e194b12b..74358683e 100644 --- a/docs/static/api-docs/slack_bolt/adapter/asgi/base_handler.html +++ b/docs/reference/adapter/asgi/base_handler.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.asgi.base_handler API documentation @@ -88,15 +88,13 @@

Classes

return AsgiHttpResponse(status=bolt_response.status, headers=bolt_response.headers, body=bolt_response.body) return AsgiHttpResponse(status=404, headers={"content-type": ["text/plain;charset=utf-8"]}, body="Not Found") - async def _handle_lifespan(self, receive: Callable) -> Dict[str, str]: - while True: - lifespan = await receive() - if lifespan["type"] == "lifespan.startup": - """Do something before startup""" - return {"type": "lifespan.startup.complete"} - if lifespan["type"] == "lifespan.shutdown": - """Do something before shutdown""" - return {"type": "lifespan.shutdown.complete"} + async def _handle_lifespan(self, receive: Callable, send: Callable) -> None: + message = await receive() + if message["type"] == "lifespan.startup": + await send({"type": "lifespan.startup.complete"}) + message = await receive() + if message["type"] == "lifespan.shutdown": + await send({"type": "lifespan.shutdown.complete"}) async def __call__(self, scope: scope_type, receive: Callable, send: Callable) -> None: if scope["type"] == "http": @@ -107,7 +105,7 @@

Classes

await send(response.get_response_body()) return if scope["type"] == "lifespan": - await send(await self._handle_lifespan(receive)) + await self._handle_lifespan(receive, send) return raise TypeError(f"Unsupported scope type: {scope['type']!r}")
@@ -204,7 +202,7 @@

-

Generated by pdoc 0.11.5.

+

Generated by pdoc 0.11.6.

diff --git a/docs/static/api-docs/slack_bolt/adapter/asgi/builtin/index.html b/docs/reference/adapter/asgi/builtin/index.html similarity index 99% rename from docs/static/api-docs/slack_bolt/adapter/asgi/builtin/index.html rename to docs/reference/adapter/asgi/builtin/index.html index 4a0d0c777..9147380c5 100644 --- a/docs/static/api-docs/slack_bolt/adapter/asgi/builtin/index.html +++ b/docs/reference/adapter/asgi/builtin/index.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.asgi.builtin API documentation @@ -159,7 +159,7 @@

-

Generated by pdoc 0.11.5.

+

Generated by pdoc 0.11.6.

diff --git a/docs/static/api-docs/slack_bolt/adapter/asgi/http_request.html b/docs/reference/adapter/asgi/http_request.html similarity index 99% rename from docs/static/api-docs/slack_bolt/adapter/asgi/http_request.html rename to docs/reference/adapter/asgi/http_request.html index 38ab574da..062ac7ca2 100644 --- a/docs/static/api-docs/slack_bolt/adapter/asgi/http_request.html +++ b/docs/reference/adapter/asgi/http_request.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.asgi.http_request API documentation @@ -250,7 +250,7 @@

-

Generated by pdoc 0.11.5.

+

Generated by pdoc 0.11.6.

diff --git a/docs/static/api-docs/slack_bolt/adapter/asgi/http_response.html b/docs/reference/adapter/asgi/http_response.html similarity index 90% rename from docs/static/api-docs/slack_bolt/adapter/asgi/http_response.html rename to docs/reference/adapter/asgi/http_response.html index 93c929224..0c42d9a9f 100644 --- a/docs/static/api-docs/slack_bolt/adapter/asgi/http_response.html +++ b/docs/reference/adapter/asgi/http_response.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.asgi.http_response API documentation @@ -60,11 +60,14 @@

Classes

def __init__(self, status: int, headers: Dict[str, Sequence[str]] = {}, body: str = ""): self.status: int = status - self.raw_headers: List[Tuple[bytes, bytes]] = [ - (bytes(key, ENCODING), bytes(value[0], ENCODING)) for key, value in headers.items() - ] - self.raw_headers.append((b"content-length", bytes(str(len(body)), ENCODING))) self.body: bytes = bytes(body, ENCODING) + self.raw_headers: List[Tuple[bytes, bytes]] = [] + for key, values in headers.items(): + if key.lower() == "content-length": + continue + for v in values: + self.raw_headers.append((bytes(key, ENCODING), bytes(v, ENCODING))) + self.raw_headers.append((b"content-length", bytes(str(len(self.body)), ENCODING))) def get_response_start(self) -> Dict[str, Union[str, int, Iterable[Tuple[bytes, bytes]]]]: return { @@ -94,11 +97,14 @@

Instance variables

def __init__(self, status: int, headers: Dict[str, Sequence[str]] = {}, body: str = ""): self.status: int = status - self.raw_headers: List[Tuple[bytes, bytes]] = [ - (bytes(key, ENCODING), bytes(value[0], ENCODING)) for key, value in headers.items() - ] - self.raw_headers.append((b"content-length", bytes(str(len(body)), ENCODING))) self.body: bytes = bytes(body, ENCODING) + self.raw_headers: List[Tuple[bytes, bytes]] = [] + for key, values in headers.items(): + if key.lower() == "content-length": + continue + for v in values: + self.raw_headers.append((bytes(key, ENCODING), bytes(v, ENCODING))) + self.raw_headers.append((b"content-length", bytes(str(len(self.body)), ENCODING))) def get_response_start(self) -> Dict[str, Union[str, int, Iterable[Tuple[bytes, bytes]]]]: return { @@ -127,11 +133,14 @@

Instance variables

def __init__(self, status: int, headers: Dict[str, Sequence[str]] = {}, body: str = ""): self.status: int = status - self.raw_headers: List[Tuple[bytes, bytes]] = [ - (bytes(key, ENCODING), bytes(value[0], ENCODING)) for key, value in headers.items() - ] - self.raw_headers.append((b"content-length", bytes(str(len(body)), ENCODING))) self.body: bytes = bytes(body, ENCODING) + self.raw_headers: List[Tuple[bytes, bytes]] = [] + for key, values in headers.items(): + if key.lower() == "content-length": + continue + for v in values: + self.raw_headers.append((bytes(key, ENCODING), bytes(v, ENCODING))) + self.raw_headers.append((b"content-length", bytes(str(len(self.body)), ENCODING))) def get_response_start(self) -> Dict[str, Union[str, int, Iterable[Tuple[bytes, bytes]]]]: return { @@ -160,11 +169,14 @@

Instance variables

def __init__(self, status: int, headers: Dict[str, Sequence[str]] = {}, body: str = ""): self.status: int = status - self.raw_headers: List[Tuple[bytes, bytes]] = [ - (bytes(key, ENCODING), bytes(value[0], ENCODING)) for key, value in headers.items() - ] - self.raw_headers.append((b"content-length", bytes(str(len(body)), ENCODING))) self.body: bytes = bytes(body, ENCODING) + self.raw_headers: List[Tuple[bytes, bytes]] = [] + for key, values in headers.items(): + if key.lower() == "content-length": + continue + for v in values: + self.raw_headers.append((bytes(key, ENCODING), bytes(v, ENCODING))) + self.raw_headers.append((b"content-length", bytes(str(len(self.body)), ENCODING))) def get_response_start(self) -> Dict[str, Union[str, int, Iterable[Tuple[bytes, bytes]]]]: return { @@ -252,7 +264,7 @@

diff --git a/docs/static/api-docs/slack_bolt/adapter/asgi/index.html b/docs/reference/adapter/asgi/index.html similarity index 99% rename from docs/static/api-docs/slack_bolt/adapter/asgi/index.html rename to docs/reference/adapter/asgi/index.html index 295ead704..0f2abec74 100644 --- a/docs/static/api-docs/slack_bolt/adapter/asgi/index.html +++ b/docs/reference/adapter/asgi/index.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.asgi API documentation @@ -201,7 +201,7 @@

-

Generated by pdoc 0.11.5.

+

Generated by pdoc 0.11.6.

diff --git a/docs/static/api-docs/slack_bolt/adapter/asgi/utils.html b/docs/reference/adapter/asgi/utils.html similarity index 98% rename from docs/static/api-docs/slack_bolt/adapter/asgi/utils.html rename to docs/reference/adapter/asgi/utils.html index 031deda6c..8eb2a24f1 100644 --- a/docs/static/api-docs/slack_bolt/adapter/asgi/utils.html +++ b/docs/reference/adapter/asgi/utils.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.asgi.utils API documentation @@ -60,7 +60,7 @@

Module slack_bolt.adapter.asgi.utils

diff --git a/docs/static/api-docs/slack_bolt/adapter/aws_lambda/chalice_handler.html b/docs/reference/adapter/aws_lambda/chalice_handler.html similarity index 99% rename from docs/static/api-docs/slack_bolt/adapter/aws_lambda/chalice_handler.html rename to docs/reference/adapter/aws_lambda/chalice_handler.html index e8bd162ec..28c75ea6a 100644 --- a/docs/static/api-docs/slack_bolt/adapter/aws_lambda/chalice_handler.html +++ b/docs/reference/adapter/aws_lambda/chalice_handler.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.aws_lambda.chalice_handler API documentation @@ -278,7 +278,7 @@

-

Generated by pdoc 0.11.5.

+

Generated by pdoc 0.11.6.

diff --git a/docs/static/api-docs/slack_bolt/adapter/aws_lambda/chalice_lazy_listener_runner.html b/docs/reference/adapter/aws_lambda/chalice_lazy_listener_runner.html similarity index 98% rename from docs/static/api-docs/slack_bolt/adapter/aws_lambda/chalice_lazy_listener_runner.html rename to docs/reference/adapter/aws_lambda/chalice_lazy_listener_runner.html index 8bd6428c1..f27e09c93 100644 --- a/docs/static/api-docs/slack_bolt/adapter/aws_lambda/chalice_lazy_listener_runner.html +++ b/docs/reference/adapter/aws_lambda/chalice_lazy_listener_runner.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.aws_lambda.chalice_lazy_listener_runner API documentation @@ -124,7 +124,7 @@

-

Generated by pdoc 0.11.5.

+

Generated by pdoc 0.11.6.

diff --git a/docs/static/api-docs/slack_bolt/adapter/aws_lambda/handler.html b/docs/reference/adapter/aws_lambda/handler.html similarity index 99% rename from docs/static/api-docs/slack_bolt/adapter/aws_lambda/handler.html rename to docs/reference/adapter/aws_lambda/handler.html index 48fd279c2..08e4ac9b7 100644 --- a/docs/static/api-docs/slack_bolt/adapter/aws_lambda/handler.html +++ b/docs/reference/adapter/aws_lambda/handler.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.aws_lambda.handler API documentation @@ -281,7 +281,7 @@

diff --git a/docs/static/api-docs/slack_bolt/adapter/aws_lambda/index.html b/docs/reference/adapter/aws_lambda/index.html similarity index 99% rename from docs/static/api-docs/slack_bolt/adapter/aws_lambda/index.html rename to docs/reference/adapter/aws_lambda/index.html index 023c9b6eb..0aae2c31a 100644 --- a/docs/static/api-docs/slack_bolt/adapter/aws_lambda/index.html +++ b/docs/reference/adapter/aws_lambda/index.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.aws_lambda API documentation @@ -249,7 +249,7 @@

-

Generated by pdoc 0.11.5.

+

Generated by pdoc 0.11.6.

diff --git a/docs/static/api-docs/slack_bolt/adapter/aws_lambda/internals.html b/docs/reference/adapter/aws_lambda/internals.html similarity index 98% rename from docs/static/api-docs/slack_bolt/adapter/aws_lambda/internals.html rename to docs/reference/adapter/aws_lambda/internals.html index 94fa2be46..bbbe281b0 100644 --- a/docs/static/api-docs/slack_bolt/adapter/aws_lambda/internals.html +++ b/docs/reference/adapter/aws_lambda/internals.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.aws_lambda.internals API documentation @@ -60,7 +60,7 @@

Module slack_bolt.adapter.aws_lambda.internals diff --git a/docs/static/api-docs/slack_bolt/adapter/aws_lambda/lambda_s3_oauth_flow.html b/docs/reference/adapter/aws_lambda/lambda_s3_oauth_flow.html similarity index 99% rename from docs/static/api-docs/slack_bolt/adapter/aws_lambda/lambda_s3_oauth_flow.html rename to docs/reference/adapter/aws_lambda/lambda_s3_oauth_flow.html index 067ad846d..11845c902 100644 --- a/docs/static/api-docs/slack_bolt/adapter/aws_lambda/lambda_s3_oauth_flow.html +++ b/docs/reference/adapter/aws_lambda/lambda_s3_oauth_flow.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.aws_lambda.lambda_s3_oauth_flow API documentation @@ -204,7 +204,7 @@

-

Generated by pdoc 0.11.5.

+

Generated by pdoc 0.11.6.

diff --git a/docs/static/api-docs/slack_bolt/adapter/aws_lambda/lazy_listener_runner.html b/docs/reference/adapter/aws_lambda/lazy_listener_runner.html similarity index 98% rename from docs/static/api-docs/slack_bolt/adapter/aws_lambda/lazy_listener_runner.html rename to docs/reference/adapter/aws_lambda/lazy_listener_runner.html index 4fbebcd4e..df53f5f22 100644 --- a/docs/static/api-docs/slack_bolt/adapter/aws_lambda/lazy_listener_runner.html +++ b/docs/reference/adapter/aws_lambda/lazy_listener_runner.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.aws_lambda.lazy_listener_runner API documentation @@ -116,7 +116,7 @@

-

Generated by pdoc 0.11.5.

+

Generated by pdoc 0.11.6.

diff --git a/docs/static/api-docs/slack_bolt/adapter/aws_lambda/local_lambda_client.html b/docs/reference/adapter/aws_lambda/local_lambda_client.html similarity index 98% rename from docs/static/api-docs/slack_bolt/adapter/aws_lambda/local_lambda_client.html rename to docs/reference/adapter/aws_lambda/local_lambda_client.html index 1441fb2b7..45ee0510b 100644 --- a/docs/static/api-docs/slack_bolt/adapter/aws_lambda/local_lambda_client.html +++ b/docs/reference/adapter/aws_lambda/local_lambda_client.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.aws_lambda.local_lambda_client API documentation @@ -134,7 +134,7 @@

-

Generated by pdoc 0.11.5.

+

Generated by pdoc 0.11.6.

diff --git a/docs/static/api-docs/slack_bolt/adapter/bottle/handler.html b/docs/reference/adapter/bottle/handler.html similarity index 99% rename from docs/static/api-docs/slack_bolt/adapter/bottle/handler.html rename to docs/reference/adapter/bottle/handler.html index e14b2da44..fe6f8ae1a 100644 --- a/docs/static/api-docs/slack_bolt/adapter/bottle/handler.html +++ b/docs/reference/adapter/bottle/handler.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.bottle.handler API documentation @@ -186,7 +186,7 @@

diff --git a/docs/static/api-docs/slack_bolt/adapter/bottle/index.html b/docs/reference/adapter/bottle/index.html similarity index 98% rename from docs/static/api-docs/slack_bolt/adapter/bottle/index.html rename to docs/reference/adapter/bottle/index.html index 0ceecb7f7..f240d52bc 100644 --- a/docs/static/api-docs/slack_bolt/adapter/bottle/index.html +++ b/docs/reference/adapter/bottle/index.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.bottle API documentation @@ -153,7 +153,7 @@

-

Generated by pdoc 0.11.5.

+

Generated by pdoc 0.11.6.

diff --git a/docs/static/api-docs/slack_bolt/adapter/cherrypy/handler.html b/docs/reference/adapter/cherrypy/handler.html similarity index 99% rename from docs/static/api-docs/slack_bolt/adapter/cherrypy/handler.html rename to docs/reference/adapter/cherrypy/handler.html index 735786296..d41f00148 100644 --- a/docs/static/api-docs/slack_bolt/adapter/cherrypy/handler.html +++ b/docs/reference/adapter/cherrypy/handler.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.cherrypy.handler API documentation @@ -228,7 +228,7 @@

diff --git a/docs/static/api-docs/slack_bolt/adapter/cherrypy/index.html b/docs/reference/adapter/cherrypy/index.html similarity index 98% rename from docs/static/api-docs/slack_bolt/adapter/cherrypy/index.html rename to docs/reference/adapter/cherrypy/index.html index a1c121e05..5a322fd7a 100644 --- a/docs/static/api-docs/slack_bolt/adapter/cherrypy/index.html +++ b/docs/reference/adapter/cherrypy/index.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.cherrypy API documentation @@ -157,7 +157,7 @@

-

Generated by pdoc 0.11.5.

+

Generated by pdoc 0.11.6.

diff --git a/docs/static/api-docs/slack_bolt/adapter/django/handler.html b/docs/reference/adapter/django/handler.html similarity index 99% rename from docs/static/api-docs/slack_bolt/adapter/django/handler.html rename to docs/reference/adapter/django/handler.html index a2de8c99d..4fe9e359a 100644 --- a/docs/static/api-docs/slack_bolt/adapter/django/handler.html +++ b/docs/reference/adapter/django/handler.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.django.handler API documentation @@ -382,7 +382,7 @@

diff --git a/docs/static/api-docs/slack_bolt/adapter/django/index.html b/docs/reference/adapter/django/index.html similarity index 99% rename from docs/static/api-docs/slack_bolt/adapter/django/index.html rename to docs/reference/adapter/django/index.html index ed9f43658..dfb6af63f 100644 --- a/docs/static/api-docs/slack_bolt/adapter/django/index.html +++ b/docs/reference/adapter/django/index.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.django API documentation @@ -194,7 +194,7 @@

-

Generated by pdoc 0.11.5.

+

Generated by pdoc 0.11.6.

diff --git a/docs/static/api-docs/slack_bolt/adapter/falcon/async_resource.html b/docs/reference/adapter/falcon/async_resource.html similarity index 96% rename from docs/static/api-docs/slack_bolt/adapter/falcon/async_resource.html rename to docs/reference/adapter/falcon/async_resource.html index 07d432390..0dbba1ad4 100644 --- a/docs/static/api-docs/slack_bolt/adapter/falcon/async_resource.html +++ b/docs/reference/adapter/falcon/async_resource.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.falcon.async_resource API documentation @@ -85,9 +85,9 @@

Classes

await self._write_response(bolt_resp, resp) return - resp.status = "404" - # Falcon 4.x w/ mypy fails to correctly infer the str type here - resp.body = "The page is not found..." # type: ignore[assignment] + resp.status = HTTPStatus.NOT_FOUND + resp.content_type = MEDIA_TEXT + resp.text = "The page is not found..." async def on_post(self, req: Request, resp: Response): bolt_req = await self._to_bolt_request(req) @@ -149,9 +149,9 @@

Methods

await self._write_response(bolt_resp, resp) return - resp.status = "404" - # Falcon 4.x w/ mypy fails to correctly infer the str type here - resp.body = "The page is not found..." # type: ignore[assignment]
+ resp.status = HTTPStatus.NOT_FOUND + resp.content_type = MEDIA_TEXT + resp.text = "The page is not found..."
@@ -200,7 +200,7 @@

-

Generated by pdoc 0.11.5.

+

Generated by pdoc 0.11.6.

diff --git a/docs/static/api-docs/slack_bolt/adapter/falcon/index.html b/docs/reference/adapter/falcon/index.html similarity index 95% rename from docs/static/api-docs/slack_bolt/adapter/falcon/index.html rename to docs/reference/adapter/falcon/index.html index 50874f7b8..bfc21828f 100644 --- a/docs/static/api-docs/slack_bolt/adapter/falcon/index.html +++ b/docs/reference/adapter/falcon/index.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.falcon API documentation @@ -91,9 +91,9 @@

Classes

self._write_response(bolt_resp, resp) return - resp.status = "404" - # Falcon 4.x w/ mypy fails to correctly infer the str type here - resp.body = "The page is not found..." # type: ignore[assignment] + resp.status = HTTPStatus.NOT_FOUND + resp.content_type = MEDIA_TEXT + resp.text = "The page is not found..." def on_post(self, req: Request, resp: Response): bolt_req = self._to_bolt_request(req) @@ -110,7 +110,7 @@

Classes

def _write_response(self, bolt_resp: BoltResponse, resp: Response): if falcon_version.__version__.startswith("2."): # Falcon 4.x w/ mypy fails to correctly infer the str type here - resp.body = bolt_resp.body # type: ignore[assignment] + resp.body = bolt_resp.body else: resp.text = bolt_resp.body @@ -159,9 +159,9 @@

Methods

self._write_response(bolt_resp, resp) return - resp.status = "404" - # Falcon 4.x w/ mypy fails to correctly infer the str type here - resp.body = "The page is not found..." # type: ignore[assignment]
+ resp.status = HTTPStatus.NOT_FOUND + resp.content_type = MEDIA_TEXT + resp.text = "The page is not found..."
@@ -216,7 +216,7 @@

-

Generated by pdoc 0.11.5.

+

Generated by pdoc 0.11.6.

diff --git a/docs/static/api-docs/slack_bolt/adapter/falcon/resource.html b/docs/reference/adapter/falcon/resource.html similarity index 95% rename from docs/static/api-docs/slack_bolt/adapter/falcon/resource.html rename to docs/reference/adapter/falcon/resource.html index b999b290c..13f9a2177 100644 --- a/docs/static/api-docs/slack_bolt/adapter/falcon/resource.html +++ b/docs/reference/adapter/falcon/resource.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.falcon.resource API documentation @@ -80,9 +80,9 @@

Classes

self._write_response(bolt_resp, resp) return - resp.status = "404" - # Falcon 4.x w/ mypy fails to correctly infer the str type here - resp.body = "The page is not found..." # type: ignore[assignment] + resp.status = HTTPStatus.NOT_FOUND + resp.content_type = MEDIA_TEXT + resp.text = "The page is not found..." def on_post(self, req: Request, resp: Response): bolt_req = self._to_bolt_request(req) @@ -99,7 +99,7 @@

Classes

def _write_response(self, bolt_resp: BoltResponse, resp: Response): if falcon_version.__version__.startswith("2."): # Falcon 4.x w/ mypy fails to correctly infer the str type here - resp.body = bolt_resp.body # type: ignore[assignment] + resp.body = bolt_resp.body else: resp.text = bolt_resp.body @@ -148,9 +148,9 @@

Methods

self._write_response(bolt_resp, resp) return - resp.status = "404" - # Falcon 4.x w/ mypy fails to correctly infer the str type here - resp.body = "The page is not found..." # type: ignore[assignment]
+ resp.status = HTTPStatus.NOT_FOUND + resp.content_type = MEDIA_TEXT + resp.text = "The page is not found..."
@@ -199,7 +199,7 @@

-

Generated by pdoc 0.11.5.

+

Generated by pdoc 0.11.6.

diff --git a/docs/static/api-docs/slack_bolt/adapter/fastapi/async_handler.html b/docs/reference/adapter/fastapi/async_handler.html similarity index 98% rename from docs/static/api-docs/slack_bolt/adapter/fastapi/async_handler.html rename to docs/reference/adapter/fastapi/async_handler.html index 0056ce305..6f6205e51 100644 --- a/docs/static/api-docs/slack_bolt/adapter/fastapi/async_handler.html +++ b/docs/reference/adapter/fastapi/async_handler.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.fastapi.async_handler API documentation @@ -149,7 +149,7 @@

-

Generated by pdoc 0.11.5.

+

Generated by pdoc 0.11.6.

diff --git a/docs/static/api-docs/slack_bolt/adapter/fastapi/index.html b/docs/reference/adapter/fastapi/index.html similarity index 98% rename from docs/static/api-docs/slack_bolt/adapter/fastapi/index.html rename to docs/reference/adapter/fastapi/index.html index 49b91a814..6ffb52f35 100644 --- a/docs/static/api-docs/slack_bolt/adapter/fastapi/index.html +++ b/docs/reference/adapter/fastapi/index.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.fastapi API documentation @@ -153,7 +153,7 @@

-

Generated by pdoc 0.11.5.

+

Generated by pdoc 0.11.6.

diff --git a/docs/static/api-docs/slack_bolt/adapter/flask/handler.html b/docs/reference/adapter/flask/handler.html similarity index 99% rename from docs/static/api-docs/slack_bolt/adapter/flask/handler.html rename to docs/reference/adapter/flask/handler.html index 1b3952603..489b80a90 100644 --- a/docs/static/api-docs/slack_bolt/adapter/flask/handler.html +++ b/docs/reference/adapter/flask/handler.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.flask.handler API documentation @@ -179,7 +179,7 @@

-

Generated by pdoc 0.11.5.

+

Generated by pdoc 0.11.6.

diff --git a/docs/static/api-docs/slack_bolt/adapter/flask/index.html b/docs/reference/adapter/flask/index.html similarity index 98% rename from docs/static/api-docs/slack_bolt/adapter/flask/index.html rename to docs/reference/adapter/flask/index.html index 7d4b60292..ee765fa1e 100644 --- a/docs/static/api-docs/slack_bolt/adapter/flask/index.html +++ b/docs/reference/adapter/flask/index.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.flask API documentation @@ -145,7 +145,7 @@

-

Generated by pdoc 0.11.5.

+

Generated by pdoc 0.11.6.

diff --git a/docs/static/api-docs/slack_bolt/adapter/google_cloud_functions/handler.html b/docs/reference/adapter/google_cloud_functions/handler.html similarity index 99% rename from docs/static/api-docs/slack_bolt/adapter/google_cloud_functions/handler.html rename to docs/reference/adapter/google_cloud_functions/handler.html index 0b48a26a4..1d9b0da7f 100644 --- a/docs/static/api-docs/slack_bolt/adapter/google_cloud_functions/handler.html +++ b/docs/reference/adapter/google_cloud_functions/handler.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.google_cloud_functions.handler API documentation @@ -170,7 +170,7 @@

-

Generated by pdoc 0.11.5.

+

Generated by pdoc 0.11.6.

diff --git a/docs/static/api-docs/slack_bolt/adapter/google_cloud_functions/index.html b/docs/reference/adapter/google_cloud_functions/index.html similarity index 98% rename from docs/static/api-docs/slack_bolt/adapter/google_cloud_functions/index.html rename to docs/reference/adapter/google_cloud_functions/index.html index 3ad305b8d..790d210be 100644 --- a/docs/static/api-docs/slack_bolt/adapter/google_cloud_functions/index.html +++ b/docs/reference/adapter/google_cloud_functions/index.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.google_cloud_functions API documentation @@ -147,7 +147,7 @@

-

Generated by pdoc 0.11.5.

+

Generated by pdoc 0.11.6.

diff --git a/docs/static/api-docs/slack_bolt/adapter/index.html b/docs/reference/adapter/index.html similarity index 99% rename from docs/static/api-docs/slack_bolt/adapter/index.html rename to docs/reference/adapter/index.html index 78ef4a2f7..646c0ac81 100644 --- a/docs/static/api-docs/slack_bolt/adapter/index.html +++ b/docs/reference/adapter/index.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter API documentation @@ -148,7 +148,7 @@

Sub-modules

diff --git a/docs/static/api-docs/slack_bolt/adapter/pyramid/handler.html b/docs/reference/adapter/pyramid/handler.html similarity index 99% rename from docs/static/api-docs/slack_bolt/adapter/pyramid/handler.html rename to docs/reference/adapter/pyramid/handler.html index 2f26bbc38..4a4a68849 100644 --- a/docs/static/api-docs/slack_bolt/adapter/pyramid/handler.html +++ b/docs/reference/adapter/pyramid/handler.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.pyramid.handler API documentation @@ -195,7 +195,7 @@

diff --git a/docs/static/api-docs/slack_bolt/adapter/pyramid/index.html b/docs/reference/adapter/pyramid/index.html similarity index 98% rename from docs/static/api-docs/slack_bolt/adapter/pyramid/index.html rename to docs/reference/adapter/pyramid/index.html index 30d3685e8..7f0903cb6 100644 --- a/docs/static/api-docs/slack_bolt/adapter/pyramid/index.html +++ b/docs/reference/adapter/pyramid/index.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.pyramid API documentation @@ -151,7 +151,7 @@

-

Generated by pdoc 0.11.5.

+

Generated by pdoc 0.11.6.

diff --git a/docs/static/api-docs/slack_bolt/adapter/sanic/async_handler.html b/docs/reference/adapter/sanic/async_handler.html similarity index 99% rename from docs/static/api-docs/slack_bolt/adapter/sanic/async_handler.html rename to docs/reference/adapter/sanic/async_handler.html index 37945775c..adabe53be 100644 --- a/docs/static/api-docs/slack_bolt/adapter/sanic/async_handler.html +++ b/docs/reference/adapter/sanic/async_handler.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.sanic.async_handler API documentation @@ -210,7 +210,7 @@

-

Generated by pdoc 0.11.5.

+

Generated by pdoc 0.11.6.

diff --git a/docs/static/api-docs/slack_bolt/adapter/sanic/index.html b/docs/reference/adapter/sanic/index.html similarity index 98% rename from docs/static/api-docs/slack_bolt/adapter/sanic/index.html rename to docs/reference/adapter/sanic/index.html index 1ae23450c..558bb321c 100644 --- a/docs/static/api-docs/slack_bolt/adapter/sanic/index.html +++ b/docs/reference/adapter/sanic/index.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.sanic API documentation @@ -153,7 +153,7 @@

-

Generated by pdoc 0.11.5.

+

Generated by pdoc 0.11.6.

diff --git a/docs/static/api-docs/slack_bolt/adapter/socket_mode/aiohttp/index.html b/docs/reference/adapter/socket_mode/aiohttp/index.html similarity index 99% rename from docs/static/api-docs/slack_bolt/adapter/socket_mode/aiohttp/index.html rename to docs/reference/adapter/socket_mode/aiohttp/index.html index f9d240874..cc91a3d06 100644 --- a/docs/static/api-docs/slack_bolt/adapter/socket_mode/aiohttp/index.html +++ b/docs/reference/adapter/socket_mode/aiohttp/index.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.socket_mode.aiohttp API documentation @@ -239,7 +239,7 @@

diff --git a/docs/static/api-docs/slack_bolt/adapter/socket_mode/async_base_handler.html b/docs/reference/adapter/socket_mode/async_base_handler.html similarity index 99% rename from docs/static/api-docs/slack_bolt/adapter/socket_mode/async_base_handler.html rename to docs/reference/adapter/socket_mode/async_base_handler.html index 31b681b3c..b00420c11 100644 --- a/docs/static/api-docs/slack_bolt/adapter/socket_mode/async_base_handler.html +++ b/docs/reference/adapter/socket_mode/async_base_handler.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.socket_mode.async_base_handler API documentation @@ -240,7 +240,7 @@

-

Generated by pdoc 0.11.5.

+

Generated by pdoc 0.11.6.

diff --git a/docs/static/api-docs/slack_bolt/adapter/socket_mode/async_handler.html b/docs/reference/adapter/socket_mode/async_handler.html similarity index 99% rename from docs/static/api-docs/slack_bolt/adapter/socket_mode/async_handler.html rename to docs/reference/adapter/socket_mode/async_handler.html index 5093f1281..447ecf0ea 100644 --- a/docs/static/api-docs/slack_bolt/adapter/socket_mode/async_handler.html +++ b/docs/reference/adapter/socket_mode/async_handler.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.socket_mode.async_handler API documentation @@ -142,7 +142,7 @@

-

Generated by pdoc 0.11.5.

+

Generated by pdoc 0.11.6.

diff --git a/docs/static/api-docs/slack_bolt/adapter/socket_mode/async_internals.html b/docs/reference/adapter/socket_mode/async_internals.html similarity index 98% rename from docs/static/api-docs/slack_bolt/adapter/socket_mode/async_internals.html rename to docs/reference/adapter/socket_mode/async_internals.html index 5b7769483..c0b23b1de 100644 --- a/docs/static/api-docs/slack_bolt/adapter/socket_mode/async_internals.html +++ b/docs/reference/adapter/socket_mode/async_internals.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.socket_mode.async_internals API documentation @@ -54,7 +54,7 @@

Functions

Expand source code
async def run_async_bolt_app(app: AsyncApp, req: SocketModeRequest):
-    bolt_req: AsyncBoltRequest = AsyncBoltRequest(mode="socket_mode", body=req.payload)
+    bolt_req: AsyncBoltRequest = AsyncBoltRequest(mode="socket_mode", body=req.payload, headers=build_headers(req))
     bolt_resp: BoltResponse = await app.async_dispatch(bolt_req)
     return bolt_resp
@@ -121,7 +121,7 @@

Functions

diff --git a/docs/static/api-docs/slack_bolt/adapter/socket_mode/base_handler.html b/docs/reference/adapter/socket_mode/base_handler.html similarity index 99% rename from docs/static/api-docs/slack_bolt/adapter/socket_mode/base_handler.html rename to docs/reference/adapter/socket_mode/base_handler.html index b57156928..450f9ac0e 100644 --- a/docs/static/api-docs/slack_bolt/adapter/socket_mode/base_handler.html +++ b/docs/reference/adapter/socket_mode/base_handler.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.socket_mode.base_handler API documentation @@ -252,7 +252,7 @@

-

Generated by pdoc 0.11.5.

+

Generated by pdoc 0.11.6.

diff --git a/docs/static/api-docs/slack_bolt/adapter/socket_mode/builtin/index.html b/docs/reference/adapter/socket_mode/builtin/index.html similarity index 99% rename from docs/static/api-docs/slack_bolt/adapter/socket_mode/builtin/index.html rename to docs/reference/adapter/socket_mode/builtin/index.html index ab1837ae3..fc66eb203 100644 --- a/docs/static/api-docs/slack_bolt/adapter/socket_mode/builtin/index.html +++ b/docs/reference/adapter/socket_mode/builtin/index.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.socket_mode.builtin API documentation @@ -200,7 +200,7 @@

diff --git a/docs/static/api-docs/slack_bolt/adapter/socket_mode/index.html b/docs/reference/adapter/socket_mode/index.html similarity index 99% rename from docs/static/api-docs/slack_bolt/adapter/socket_mode/index.html rename to docs/reference/adapter/socket_mode/index.html index cb26a212a..511ef4840 100644 --- a/docs/static/api-docs/slack_bolt/adapter/socket_mode/index.html +++ b/docs/reference/adapter/socket_mode/index.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.socket_mode API documentation @@ -260,7 +260,7 @@

-

Generated by pdoc 0.11.5.

+

Generated by pdoc 0.11.6.

diff --git a/docs/static/api-docs/slack_bolt/adapter/socket_mode/internals.html b/docs/reference/adapter/socket_mode/internals.html similarity index 88% rename from docs/static/api-docs/slack_bolt/adapter/socket_mode/internals.html rename to docs/reference/adapter/socket_mode/internals.html index 7c1a7a81f..ba7d2f226 100644 --- a/docs/static/api-docs/slack_bolt/adapter/socket_mode/internals.html +++ b/docs/reference/adapter/socket_mode/internals.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.socket_mode.internals API documentation @@ -45,6 +45,25 @@

Module slack_bolt.adapter.socket_mode.internals

Functions

+
+def build_headers(req: slack_sdk.socket_mode.request.SocketModeRequest) ‑> Dict[str, str | Sequence[str]] | None +
+
+
+ +Expand source code + +
def build_headers(req: SocketModeRequest) -> Optional[Dict[str, Union[str, Sequence[str]]]]:
+    # Mirror the HTTP mode retry headers so middleware/listeners can detect Events API retries
+    headers: Dict[str, Union[str, Sequence[str]]] = {}
+    if req.retry_attempt is not None:
+        headers["x-slack-retry-num"] = str(req.retry_attempt)
+    if req.retry_reason is not None:
+        headers["x-slack-retry-reason"] = req.retry_reason
+    return headers or None
+
+
+
def run_bolt_app(app: App,
req: slack_sdk.socket_mode.request.SocketModeRequest)
@@ -54,7 +73,7 @@

Functions

Expand source code
def run_bolt_app(app: App, req: SocketModeRequest):
-    bolt_req: BoltRequest = BoltRequest(mode="socket_mode", body=req.payload)
+    bolt_req: BoltRequest = BoltRequest(mode="socket_mode", body=req.payload, headers=build_headers(req))
     bolt_resp: BoltResponse = app.dispatch(bolt_req)
     return bolt_resp
@@ -111,6 +130,7 @@

Functions

  • Functions

    @@ -119,7 +139,7 @@

    Functions

    diff --git a/docs/static/api-docs/slack_bolt/adapter/socket_mode/websocket_client/index.html b/docs/reference/adapter/socket_mode/websocket_client/index.html similarity index 99% rename from docs/static/api-docs/slack_bolt/adapter/socket_mode/websocket_client/index.html rename to docs/reference/adapter/socket_mode/websocket_client/index.html index d6a4b50b4..e837ef19b 100644 --- a/docs/static/api-docs/slack_bolt/adapter/socket_mode/websocket_client/index.html +++ b/docs/reference/adapter/socket_mode/websocket_client/index.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.socket_mode.websocket_client API documentation @@ -190,7 +190,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/adapter/socket_mode/websockets/index.html b/docs/reference/adapter/socket_mode/websockets/index.html similarity index 99% rename from docs/static/api-docs/slack_bolt/adapter/socket_mode/websockets/index.html rename to docs/reference/adapter/socket_mode/websockets/index.html index 2b7e9f493..7f96f0021 100644 --- a/docs/static/api-docs/slack_bolt/adapter/socket_mode/websockets/index.html +++ b/docs/reference/adapter/socket_mode/websockets/index.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.socket_mode.websockets API documentation @@ -239,7 +239,7 @@

    diff --git a/docs/static/api-docs/slack_bolt/adapter/starlette/async_handler.html b/docs/reference/adapter/starlette/async_handler.html similarity index 99% rename from docs/static/api-docs/slack_bolt/adapter/starlette/async_handler.html rename to docs/reference/adapter/starlette/async_handler.html index e8e596f2a..91345eba3 100644 --- a/docs/static/api-docs/slack_bolt/adapter/starlette/async_handler.html +++ b/docs/reference/adapter/starlette/async_handler.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.starlette.async_handler API documentation @@ -213,7 +213,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/adapter/starlette/handler.html b/docs/reference/adapter/starlette/handler.html similarity index 99% rename from docs/static/api-docs/slack_bolt/adapter/starlette/handler.html rename to docs/reference/adapter/starlette/handler.html index 5171240fd..5c74b71da 100644 --- a/docs/static/api-docs/slack_bolt/adapter/starlette/handler.html +++ b/docs/reference/adapter/starlette/handler.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.starlette.handler API documentation @@ -205,7 +205,7 @@

    diff --git a/docs/static/api-docs/slack_bolt/adapter/starlette/index.html b/docs/reference/adapter/starlette/index.html similarity index 99% rename from docs/static/api-docs/slack_bolt/adapter/starlette/index.html rename to docs/reference/adapter/starlette/index.html index 3af382537..bdf5bf42a 100644 --- a/docs/static/api-docs/slack_bolt/adapter/starlette/index.html +++ b/docs/reference/adapter/starlette/index.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.starlette API documentation @@ -158,7 +158,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/adapter/tornado/async_handler.html b/docs/reference/adapter/tornado/async_handler.html similarity index 99% rename from docs/static/api-docs/slack_bolt/adapter/tornado/async_handler.html rename to docs/reference/adapter/tornado/async_handler.html index b7d813420..c274429de 100644 --- a/docs/static/api-docs/slack_bolt/adapter/tornado/async_handler.html +++ b/docs/reference/adapter/tornado/async_handler.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.tornado.async_handler API documentation @@ -242,7 +242,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/adapter/tornado/handler.html b/docs/reference/adapter/tornado/handler.html similarity index 99% rename from docs/static/api-docs/slack_bolt/adapter/tornado/handler.html rename to docs/reference/adapter/tornado/handler.html index 1149311a9..a69adb987 100644 --- a/docs/static/api-docs/slack_bolt/adapter/tornado/handler.html +++ b/docs/reference/adapter/tornado/handler.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.tornado.handler API documentation @@ -273,7 +273,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/adapter/tornado/index.html b/docs/reference/adapter/tornado/index.html similarity index 99% rename from docs/static/api-docs/slack_bolt/adapter/tornado/index.html rename to docs/reference/adapter/tornado/index.html index dac1b6b78..a5bec4ffb 100644 --- a/docs/static/api-docs/slack_bolt/adapter/tornado/index.html +++ b/docs/reference/adapter/tornado/index.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.tornado API documentation @@ -234,7 +234,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/adapter/wsgi/handler.html b/docs/reference/adapter/wsgi/handler.html similarity index 96% rename from docs/static/api-docs/slack_bolt/adapter/wsgi/handler.html rename to docs/reference/adapter/wsgi/handler.html index 8369cb636..a6ea85ca4 100644 --- a/docs/static/api-docs/slack_bolt/adapter/wsgi/handler.html +++ b/docs/reference/adapter/wsgi/handler.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.wsgi.handler API documentation @@ -117,17 +117,20 @@

    Classes

    def __call__( self, - environ: Dict[str, Any], - start_response: Callable[[str, List[Tuple[str, str]]], None], + environ: "WSGIEnvironment", + start_response: "StartResponse", ) -> Iterable[bytes]: request = WsgiHttpRequest(environ) - if "HTTP" in request.protocol: + if request.protocol.startswith("HTTP"): response: WsgiHttpResponse = self._get_http_response( request=request, ) - start_response(response.status, response.get_headers()) - return response.get_body() - raise TypeError(f"Unsupported SERVER_PROTOCOL: {request.protocol}")
    + else: + response = WsgiHttpResponse( + status=400, headers={"content-type": ["text/plain;charset=utf-8"]}, body="Bad Request" + ) + start_response(response.status, response.get_headers()) + return response.get_body()

    Setup Bolt as a WSGI web framework, this will make your application compatible with WSGI web servers. This can be used for production deployments.

    @@ -230,7 +233,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/adapter/wsgi/http_request.html b/docs/reference/adapter/wsgi/http_request.html similarity index 97% rename from docs/static/api-docs/slack_bolt/adapter/wsgi/http_request.html rename to docs/reference/adapter/wsgi/http_request.html index c4495e440..72c5f28be 100644 --- a/docs/static/api-docs/slack_bolt/adapter/wsgi/http_request.html +++ b/docs/reference/adapter/wsgi/http_request.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.wsgi.http_request API documentation @@ -48,7 +48,7 @@

    Classes

    class WsgiHttpRequest -(environ: Dict[str, Any]) +(environ: WSGIEnvironment)
    @@ -64,7 +64,7 @@

    Classes

    __slots__ = ("method", "path", "query_string", "protocol", "environ") - def __init__(self, environ: Dict[str, Any]): + def __init__(self, environ: "WSGIEnvironment"): self.method: str = environ.get("REQUEST_METHOD", "GET") self.path: str = environ.get("PATH_INFO", "") self.query_string: str = environ.get("QUERY_STRING", "") @@ -85,7 +85,7 @@

    Classes

    def get_body(self) -> str: if "wsgi.input" not in self.environ: return "" - content_length = int(self.environ.get("CONTENT_LENGTH", 0)) + content_length = int(self.environ.get("CONTENT_LENGTH") or 0) return self.environ["wsgi.input"].read(content_length).decode(ENCODING)

    This Class uses the PEP 3333 standard to extract request information @@ -108,7 +108,7 @@

    Instance variables

    __slots__ = ("method", "path", "query_string", "protocol", "environ") - def __init__(self, environ: Dict[str, Any]): + def __init__(self, environ: "WSGIEnvironment"): self.method: str = environ.get("REQUEST_METHOD", "GET") self.path: str = environ.get("PATH_INFO", "") self.query_string: str = environ.get("QUERY_STRING", "") @@ -129,7 +129,7 @@

    Instance variables

    def get_body(self) -> str: if "wsgi.input" not in self.environ: return "" - content_length = int(self.environ.get("CONTENT_LENGTH", 0)) + content_length = int(self.environ.get("CONTENT_LENGTH") or 0) return self.environ["wsgi.input"].read(content_length).decode(ENCODING)
    @@ -149,7 +149,7 @@

    Instance variables

    __slots__ = ("method", "path", "query_string", "protocol", "environ") - def __init__(self, environ: Dict[str, Any]): + def __init__(self, environ: "WSGIEnvironment"): self.method: str = environ.get("REQUEST_METHOD", "GET") self.path: str = environ.get("PATH_INFO", "") self.query_string: str = environ.get("QUERY_STRING", "") @@ -170,7 +170,7 @@

    Instance variables

    def get_body(self) -> str: if "wsgi.input" not in self.environ: return "" - content_length = int(self.environ.get("CONTENT_LENGTH", 0)) + content_length = int(self.environ.get("CONTENT_LENGTH") or 0) return self.environ["wsgi.input"].read(content_length).decode(ENCODING)
    @@ -190,7 +190,7 @@

    Instance variables

    __slots__ = ("method", "path", "query_string", "protocol", "environ") - def __init__(self, environ: Dict[str, Any]): + def __init__(self, environ: "WSGIEnvironment"): self.method: str = environ.get("REQUEST_METHOD", "GET") self.path: str = environ.get("PATH_INFO", "") self.query_string: str = environ.get("QUERY_STRING", "") @@ -211,7 +211,7 @@

    Instance variables

    def get_body(self) -> str: if "wsgi.input" not in self.environ: return "" - content_length = int(self.environ.get("CONTENT_LENGTH", 0)) + content_length = int(self.environ.get("CONTENT_LENGTH") or 0) return self.environ["wsgi.input"].read(content_length).decode(ENCODING)
    @@ -231,7 +231,7 @@

    Instance variables

    __slots__ = ("method", "path", "query_string", "protocol", "environ") - def __init__(self, environ: Dict[str, Any]): + def __init__(self, environ: "WSGIEnvironment"): self.method: str = environ.get("REQUEST_METHOD", "GET") self.path: str = environ.get("PATH_INFO", "") self.query_string: str = environ.get("QUERY_STRING", "") @@ -252,7 +252,7 @@

    Instance variables

    def get_body(self) -> str: if "wsgi.input" not in self.environ: return "" - content_length = int(self.environ.get("CONTENT_LENGTH", 0)) + content_length = int(self.environ.get("CONTENT_LENGTH") or 0) return self.environ["wsgi.input"].read(content_length).decode(ENCODING)
    @@ -272,7 +272,7 @@

    Instance variables

    __slots__ = ("method", "path", "query_string", "protocol", "environ") - def __init__(self, environ: Dict[str, Any]): + def __init__(self, environ: "WSGIEnvironment"): self.method: str = environ.get("REQUEST_METHOD", "GET") self.path: str = environ.get("PATH_INFO", "") self.query_string: str = environ.get("QUERY_STRING", "") @@ -293,7 +293,7 @@

    Instance variables

    def get_body(self) -> str: if "wsgi.input" not in self.environ: return "" - content_length = int(self.environ.get("CONTENT_LENGTH", 0)) + content_length = int(self.environ.get("CONTENT_LENGTH") or 0) return self.environ["wsgi.input"].read(content_length).decode(ENCODING)
    @@ -312,7 +312,7 @@

    Methods

    def get_body(self) -> str:
         if "wsgi.input" not in self.environ:
             return ""
    -    content_length = int(self.environ.get("CONTENT_LENGTH", 0))
    +    content_length = int(self.environ.get("CONTENT_LENGTH") or 0)
         return self.environ["wsgi.input"].read(content_length).decode(ENCODING)
    @@ -373,7 +373,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/adapter/wsgi/http_response.html b/docs/reference/adapter/wsgi/http_response.html similarity index 93% rename from docs/static/api-docs/slack_bolt/adapter/wsgi/http_response.html rename to docs/reference/adapter/wsgi/http_response.html index 3ddc2350c..726332c77 100644 --- a/docs/static/api-docs/slack_bolt/adapter/wsgi/http_response.html +++ b/docs/reference/adapter/wsgi/http_response.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.wsgi.http_response API documentation @@ -48,7 +48,7 @@

    Classes

    class WsgiHttpResponse -(status: int, headers: Dict[str, Sequence[str]] = {}, body: str = '') +(status: int, headers: Dict[str, Sequence[str]] | None = None, body: str = '')
    @@ -64,18 +64,19 @@

    Classes

    __slots__ = ("status", "_headers", "_body") - def __init__(self, status: int, headers: Dict[str, Sequence[str]] = {}, body: str = ""): + def __init__(self, status: int, headers: Optional[Dict[str, Sequence[str]]] = None, body: str = ""): _status = HTTPStatus(status) self.status = f"{_status.value} {_status.phrase}" - self._headers = headers + self._headers = headers or {} self._body = bytes(body, ENCODING) def get_headers(self) -> List[Tuple[str, str]]: headers: List[Tuple[str, str]] = [] - for key, value in self._headers.items(): + for key, values in self._headers.items(): if key.lower() == "content-length": continue - headers.append((key, value[0])) + for v in values: + headers.append((key, v)) headers.append(("content-length", str(len(self._body)))) return headers @@ -103,18 +104,19 @@

    Instance variables

    __slots__ = ("status", "_headers", "_body") - def __init__(self, status: int, headers: Dict[str, Sequence[str]] = {}, body: str = ""): + def __init__(self, status: int, headers: Optional[Dict[str, Sequence[str]]] = None, body: str = ""): _status = HTTPStatus(status) self.status = f"{_status.value} {_status.phrase}" - self._headers = headers + self._headers = headers or {} self._body = bytes(body, ENCODING) def get_headers(self) -> List[Tuple[str, str]]: headers: List[Tuple[str, str]] = [] - for key, value in self._headers.items(): + for key, values in self._headers.items(): if key.lower() == "content-length": continue - headers.append((key, value[0])) + for v in values: + headers.append((key, v)) headers.append(("content-length", str(len(self._body)))) return headers @@ -150,10 +152,11 @@

    Methods

    def get_headers(self) -> List[Tuple[str, str]]:
         headers: List[Tuple[str, str]] = []
    -    for key, value in self._headers.items():
    +    for key, values in self._headers.items():
             if key.lower() == "content-length":
                 continue
    -        headers.append((key, value[0]))
    +        for v in values:
    +            headers.append((key, v))
     
         headers.append(("content-length", str(len(self._body))))
         return headers
    @@ -191,7 +194,7 @@

    diff --git a/docs/static/api-docs/slack_bolt/adapter/wsgi/index.html b/docs/reference/adapter/wsgi/index.html similarity index 96% rename from docs/static/api-docs/slack_bolt/adapter/wsgi/index.html rename to docs/reference/adapter/wsgi/index.html index 49ab0d930..186d1adf6 100644 --- a/docs/static/api-docs/slack_bolt/adapter/wsgi/index.html +++ b/docs/reference/adapter/wsgi/index.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.wsgi API documentation @@ -136,17 +136,20 @@

    Classes

    def __call__( self, - environ: Dict[str, Any], - start_response: Callable[[str, List[Tuple[str, str]]], None], + environ: "WSGIEnvironment", + start_response: "StartResponse", ) -> Iterable[bytes]: request = WsgiHttpRequest(environ) - if "HTTP" in request.protocol: + if request.protocol.startswith("HTTP"): response: WsgiHttpResponse = self._get_http_response( request=request, ) - start_response(response.status, response.get_headers()) - return response.get_body() - raise TypeError(f"Unsupported SERVER_PROTOCOL: {request.protocol}")
    + else: + response = WsgiHttpResponse( + status=400, headers={"content-type": ["text/plain;charset=utf-8"]}, body="Bad Request" + ) + start_response(response.status, response.get_headers()) + return response.get_body()

    Setup Bolt as a WSGI web framework, this will make your application compatible with WSGI web servers. This can be used for production deployments.

    @@ -257,7 +260,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/adapter/wsgi/internals.html b/docs/reference/adapter/wsgi/internals.html similarity index 98% rename from docs/static/api-docs/slack_bolt/adapter/wsgi/internals.html rename to docs/reference/adapter/wsgi/internals.html index addbae583..7fdfa267f 100644 --- a/docs/static/api-docs/slack_bolt/adapter/wsgi/internals.html +++ b/docs/reference/adapter/wsgi/internals.html @@ -3,7 +3,7 @@ - + slack_bolt.adapter.wsgi.internals API documentation @@ -60,7 +60,7 @@

    Module slack_bolt.adapter.wsgi.internals

    diff --git a/docs/static/api-docs/slack_bolt/app/app.html b/docs/reference/app/app.html similarity index 94% rename from docs/static/api-docs/slack_bolt/app/app.html rename to docs/reference/app/app.html index 4584a03c1..737597548 100644 --- a/docs/static/api-docs/slack_bolt/app/app.html +++ b/docs/reference/app/app.html @@ -3,7 +3,7 @@ - + slack_bolt.app.app API documentation @@ -48,7 +48,7 @@

    Classes

    class App -(*,
    logger: logging.Logger | None = None,
    name: str | None = None,
    process_before_response: bool = False,
    raise_error_for_unhandled_request: bool = False,
    signing_secret: str | None = None,
    token: str | None = None,
    token_verification_enabled: bool = True,
    client: slack_sdk.web.client.WebClient | None = None,
    before_authorize: Middleware | Callable[..., Any] | None = None,
    authorize: Callable[..., AuthorizeResult] | None = None,
    user_facing_authorize_error_message: str | None = None,
    installation_store: slack_sdk.oauth.installation_store.installation_store.InstallationStore | None = None,
    installation_store_bot_only: bool | None = None,
    request_verification_enabled: bool = True,
    ignoring_self_events_enabled: bool = True,
    ignoring_self_assistant_message_events_enabled: bool = True,
    ssl_check_enabled: bool = True,
    url_verification_enabled: bool = True,
    attaching_function_token_enabled: bool = True,
    oauth_settings: OAuthSettings | None = None,
    oauth_flow: OAuthFlow | None = None,
    verification_token: str | None = None,
    listener_executor: concurrent.futures._base.Executor | None = None,
    assistant_thread_context_store: AssistantThreadContextStore | None = None)
    +(*,
    logger: logging.Logger | None = None,
    name: str | None = None,
    process_before_response: bool = False,
    raise_error_for_unhandled_request: bool = False,
    signing_secret: str | None = None,
    token: str | None = None,
    token_verification_enabled: bool = True,
    client: slack_sdk.web.client.WebClient | None = None,
    before_authorize: Middleware | Callable[..., Any] | None = None,
    authorize: Callable[..., AuthorizeResult] | None = None,
    user_facing_authorize_error_message: str | None = None,
    installation_store: slack_sdk.oauth.installation_store.installation_store.InstallationStore | None = None,
    installation_store_bot_only: bool | None = None,
    request_verification_enabled: bool = True,
    ignoring_self_events_enabled: bool = True,
    ignoring_self_assistant_message_events_enabled: bool = True,
    ssl_check_enabled: bool = True,
    url_verification_enabled: bool = True,
    attaching_function_token_enabled: bool = True,
    oauth_settings: OAuthSettings | None = None,
    oauth_flow: OAuthFlow | None = None,
    verification_token: str | None = None,
    listener_executor: concurrent.futures._base.Executor | None = None,
    assistant_thread_context_store: AssistantThreadContextStore | None = None,
    attaching_conversation_kwargs_enabled: bool = True)
    @@ -95,6 +95,7 @@

    Classes

    listener_executor: Optional[Executor] = None, # for AI Agents & Assistants assistant_thread_context_store: Optional[AssistantThreadContextStore] = None, + attaching_conversation_kwargs_enabled: bool = True, ): """Bolt App that provides functionalities to register middleware/listeners. @@ -117,10 +118,10 @@

    Classes

    if __name__ == "__main__": app.start(port=int(os.environ.get("PORT", 3000))) - Refer to https://slack.dev/bolt-python/tutorial/getting-started for details. + Refer to https://docs.slack.dev/tools/bolt-python/creating-an-app for details. If you would like to build an OAuth app for enabling the app to run with multiple workspaces, - refer to https://slack.dev/bolt-python/concepts#authenticating-oauth to learn how to configure the app. + refer to https://docs.slack.dev/tools/bolt-python/concepts/authenticating-oauth to learn how to configure the app. Args: logger: The custom logger that can be used in this app. @@ -315,6 +316,7 @@

    Classes

    listener_executor = ThreadPoolExecutor(max_workers=5) self._assistant_thread_context_store = assistant_thread_context_store + self._attaching_conversation_kwargs_enabled = attaching_conversation_kwargs_enabled self._process_before_response = process_before_response self._listener_runner = ThreadListenerRunner( @@ -629,7 +631,7 @@

    Classes

    # Pass a function to this method app.middleware(middleware_func) - Refer to https://slack.dev/bolt-python/concepts#global-middleware for details. + Refer to https://docs.slack.dev/tools/bolt-python/concepts/global-middleware for details. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -675,7 +677,7 @@

    Classes

    """ Deprecated: Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://api.slack.com/automation/functions/custom-bolt + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ Registers a new step from app listener. @@ -693,7 +695,7 @@

    Classes

    # Pass Step to set up listeners app.step(ws) - Refer to https://api.slack.com/workflows/steps for details of steps from apps. + Refer to https://docs.slack.dev/legacy/legacy-steps-from-apps/ for details of steps from apps. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -710,7 +712,7 @@

    Classes

    warnings.warn( ( "Steps from apps for legacy workflows are now deprecated. " - "Use new custom steps: https://api.slack.com/automation/functions/custom-bolt" + "Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/" ), category=DeprecationWarning, ) @@ -787,7 +789,7 @@

    Classes

    # Pass a function to this method app.event("team_join")(ask_for_introduction) - Refer to https://api.slack.com/apis/connections/events-api for details of Events API. + Refer to https://docs.slack.dev/apis/events-api/ for details of Events API. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -799,10 +801,13 @@

    Classes

    middleware: A list of lister middleware functions. Only when all the middleware call `next()` method, the listener function can be invoked. """ + middleware = list(middleware) if middleware else [] def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) primary_matcher = builtin_matchers.event(event, base_logger=self._base_logger) + if self._attaching_conversation_kwargs_enabled: + middleware.insert(0, AttachingConversationKwargs(self._assistant_thread_context_store)) return self._register_listener(list(functions), primary_matcher, matchers, middleware, True) return __call__ @@ -825,7 +830,7 @@

    Classes

    # Pass a function to this method app.message(":wave:")(say_hello) - Refer to https://api.slack.com/events/message for details of `message` events. + Refer to https://docs.slack.dev/reference/events/message/ for details of `message` events. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -860,6 +865,8 @@

    Classes

    primary_matcher = builtin_matchers.message_event( keyword=keyword, constraints=constraints, base_logger=self._base_logger ) + if self._attaching_conversation_kwargs_enabled: + middleware.insert(0, AttachingConversationKwargs(self._assistant_thread_context_store)) middleware.insert(0, MessageListenerMatches(keyword)) return self._register_listener(list(functions), primary_matcher, matchers, middleware, True) @@ -871,6 +878,7 @@

    Classes

    matchers: Optional[Sequence[Callable[..., bool]]] = None, middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, auto_acknowledge: bool = True, + ack_timeout: int = 3, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new Function listener. This method can be used as either a decorator or a method. @@ -899,13 +907,17 @@

    Classes

    Only when all the middleware call `next()` method, the listener function can be invoked. """ + if auto_acknowledge is True: + if ack_timeout != 3: + self._framework_logger.warning(warning_ack_timeout_has_no_effect(callback_id, ack_timeout)) + matchers = list(matchers) if matchers else [] middleware = list(middleware) if middleware else [] def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) primary_matcher = builtin_matchers.function_executed(callback_id=callback_id, base_logger=self._base_logger) - return self._register_listener(functions, primary_matcher, matchers, middleware, auto_acknowledge) + return self._register_listener(functions, primary_matcher, matchers, middleware, auto_acknowledge, ack_timeout) return __call__ @@ -931,7 +943,7 @@

    Classes

    # Pass a function to this method app.command("/echo")(repeat_text) - Refer to https://api.slack.com/interactivity/slash-commands for details of Slash Commands. + Refer to https://docs.slack.dev/interactivity/implementing-slash-commands/ for details of Slash Commands. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -978,7 +990,7 @@

    Classes

    # Pass a function to this method app.shortcut("open_modal")(open_modal) - Refer to https://api.slack.com/interactivity/shortcuts for details about Shortcuts. + Refer to https://docs.slack.dev/interactivity/implementing-shortcuts/ for details about Shortcuts. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -1046,9 +1058,9 @@

    Classes

    # Pass a function to this method app.action("approve_button")(update_message) - * Refer to https://api.slack.com/reference/interaction-payloads/block-actions for actions in `blocks`. - * Refer to https://api.slack.com/legacy/message-buttons for actions in `attachments`. - * Refer to https://api.slack.com/dialogs for actions in dialogs. + * Refer to https://docs.slack.dev/reference/interaction-payloads/block_actions-payload/ for actions in `blocks`. + * Refer to https://docs.slack.dev/legacy/legacy-messaging/legacy-message-buttons/ for actions in `attachments`. + * Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for actions in dialogs. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -1074,7 +1086,7 @@

    Classes

    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new `block_actions` action listener. - Refer to https://api.slack.com/reference/interaction-payloads/block-actions for details. + Refer to https://docs.slack.dev/reference/interaction-payloads/block_actions-payload/ for details. """ def __call__(*args, **kwargs): @@ -1091,7 +1103,7 @@

    Classes

    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new `interactive_message` action listener. - Refer to https://api.slack.com/legacy/message-buttons for details.""" + Refer to https://docs.slack.dev/legacy/legacy-messaging/legacy-message-buttons/ for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1107,7 +1119,7 @@

    Classes

    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new `dialog_submission` listener. - Refer to https://api.slack.com/dialogs for details.""" + Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1123,7 +1135,7 @@

    Classes

    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new `dialog_cancellation` listener. - Refer to https://api.slack.com/dialogs for details.""" + Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1164,7 +1176,7 @@

    Classes

    # Pass a function to this method app.view("view_1")(handle_submission) - Refer to https://api.slack.com/reference/interaction-payloads/views for details of payloads. + Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload for details of payloads. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -1190,7 +1202,9 @@

    Classes

    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new `view_submission` listener. - Refer to https://api.slack.com/reference/interaction-payloads/views#view_submission for details.""" + Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for + details. + """ def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1206,7 +1220,7 @@

    Classes

    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new `view_closed` listener. - Refer to https://api.slack.com/reference/interaction-payloads/views#view_closed for details.""" + Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_closed for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1247,8 +1261,8 @@

    Classes

    Refer to the following documents for details: - * https://api.slack.com/reference/block-kit/block-elements#external_select - * https://api.slack.com/reference/block-kit/block-elements#external_multi_select + * https://docs.slack.dev/reference/block-kit/block-elements/select-menu-element#external_select + * https://docs.slack.dev/reference/block-kit/block-elements/multi-select-menu-element#external_multi_select To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -1288,7 +1302,7 @@

    Classes

    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new `dialog_suggestion` listener. - Refer to https://api.slack.com/dialogs for details.""" + Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1340,6 +1354,7 @@

    Classes

    proxy=self._client.proxy, headers=self._client.headers, team_id=req.context.team_id, + logger=self._client.logger, retry_handlers=self._client.retry_handlers.copy() if self._client.retry_handlers is not None else None, ) req.context["client"] = client_per_request @@ -1348,20 +1363,6 @@

    Classes

    # It is intended for apps that start lazy listeners from their custom global middleware. req.context["listener_runner"] = self.listener_runner - # For AI Agents & Assistants - if is_assistant_event(req.body): - assistant = AssistantUtilities( - payload=to_event(req.body), # type:ignore[arg-type] - context=req.context, - thread_context_store=self._assistant_thread_context_store, - ) - req.context["say"] = assistant.say - req.context["set_status"] = assistant.set_status - req.context["set_title"] = assistant.set_title - req.context["set_suggested_prompts"] = assistant.set_suggested_prompts - req.context["get_thread_context"] = assistant.get_thread_context - req.context["save_thread_context"] = assistant.save_thread_context - @staticmethod def _to_listener_functions( kwargs: dict, @@ -1380,6 +1381,7 @@

    Classes

    matchers: Optional[Sequence[Callable[..., bool]]], middleware: Optional[Sequence[Union[Callable, Middleware]]], auto_acknowledgement: bool = False, + ack_timeout: int = 3, ) -> Optional[Callable[..., Optional[BoltResponse]]]: value_to_return = None if not isinstance(functions, list): @@ -1406,10 +1408,11 @@

    Classes

    CustomListener( app_name=self.name, ack_function=functions.pop(0), - lazy_functions=functions, + lazy_functions=functions, # type: ignore[arg-type] matchers=listener_matchers, middleware=listener_middleware, auto_acknowledgement=auto_acknowledgement, + ack_timeout=ack_timeout, base_logger=self._base_logger, ) ) @@ -1435,9 +1438,9 @@

    Classes

    if __name__ == "__main__": app.start(port=int(os.environ.get("PORT", 3000)))
    -

    Refer to https://slack.dev/bolt-python/tutorial/getting-started for details.

    +

    Refer to https://docs.slack.dev/tools/bolt-python/creating-an-app for details.

    If you would like to build an OAuth app for enabling the app to run with multiple workspaces, -refer to https://slack.dev/bolt-python/concepts#authenticating-oauth to learn how to configure the app.

    +refer to https://docs.slack.dev/tools/bolt-python/concepts/authenticating-oauth to learn how to configure the app.

    Args

    logger
    @@ -1629,9 +1632,9 @@

    Methods

    # Pass a function to this method app.action("approve_button")(update_message) - * Refer to https://api.slack.com/reference/interaction-payloads/block-actions for actions in `blocks`. - * Refer to https://api.slack.com/legacy/message-buttons for actions in `attachments`. - * Refer to https://api.slack.com/dialogs for actions in dialogs. + * Refer to https://docs.slack.dev/reference/interaction-payloads/block_actions-payload/ for actions in `blocks`. + * Refer to https://docs.slack.dev/legacy/legacy-messaging/legacy-message-buttons/ for actions in `attachments`. + * Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for actions in dialogs. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -1660,9 +1663,9 @@

    Methods

    app.action("approve_button")(update_message)

    To learn available arguments for middleware/listeners, see slack_bolt.kwargs_injection.args's API document.

    Args

    @@ -1705,7 +1708,7 @@

    Args

    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new `interactive_message` action listener. - Refer to https://api.slack.com/legacy/message-buttons for details.""" + Refer to https://docs.slack.dev/legacy/legacy-messaging/legacy-message-buttons/ for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1715,7 +1718,7 @@

    Args

    return __call__

    Registers a new interactive_message action listener. -Refer to https://api.slack.com/legacy/message-buttons for details.

    +Refer to https://docs.slack.dev/legacy/legacy-messaging/legacy-message-buttons/ for details.

    def block_action(self,
    constraints: str | Pattern | Dict[str, str | Pattern],
    matchers: Sequence[Callable[..., bool]] | None = None,
    middleware: Sequence[Callable | Middleware] | None = None) ‑> Callable[..., Callable[..., BoltResponse | None] | None]
    @@ -1732,7 +1735,7 @@

    Args

    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new `block_actions` action listener. - Refer to https://api.slack.com/reference/interaction-payloads/block-actions for details. + Refer to https://docs.slack.dev/reference/interaction-payloads/block_actions-payload/ for details. """ def __call__(*args, **kwargs): @@ -1743,7 +1746,7 @@

    Args

    return __call__

    Registers a new block_actions action listener. -Refer to https://api.slack.com/reference/interaction-payloads/block-actions for details.

    +Refer to https://docs.slack.dev/reference/interaction-payloads/block_actions-payload/ for details.

    def block_suggestion(self,
    action_id: str | Pattern,
    matchers: Sequence[Callable[..., bool]] | None = None,
    middleware: Sequence[Callable | Middleware] | None = None) ‑> Callable[..., Callable[..., BoltResponse | None] | None]
    @@ -1797,7 +1800,7 @@

    Args

    # Pass a function to this method app.command("/echo")(repeat_text) - Refer to https://api.slack.com/interactivity/slash-commands for details of Slash Commands. + Refer to https://docs.slack.dev/interactivity/implementing-slash-commands/ for details of Slash Commands. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -1828,7 +1831,7 @@

    Args

    # Pass a function to this method app.command("/echo")(repeat_text)
    -

    Refer to https://api.slack.com/interactivity/slash-commands for details of Slash Commands.

    +

    Refer to https://docs.slack.dev/interactivity/implementing-slash-commands/ for details of Slash Commands.

    To learn available arguments for middleware/listeners, see slack_bolt.kwargs_injection.args's API document.

    Args

    @@ -1891,7 +1894,7 @@

    Args

    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new `dialog_cancellation` listener. - Refer to https://api.slack.com/dialogs for details.""" + Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1901,7 +1904,7 @@

    Args

    return __call__

    Registers a new dialog_cancellation listener. -Refer to https://api.slack.com/dialogs for details.

    +Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.

    def dialog_submission(self,
    callback_id: str | Pattern,
    matchers: Sequence[Callable[..., bool]] | None = None,
    middleware: Sequence[Callable | Middleware] | None = None) ‑> Callable[..., Callable[..., BoltResponse | None] | None]
    @@ -1918,7 +1921,7 @@

    Args

    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new `dialog_submission` listener. - Refer to https://api.slack.com/dialogs for details.""" + Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1928,7 +1931,7 @@

    Args

    return __call__

    Registers a new dialog_submission listener. -Refer to https://api.slack.com/dialogs for details.

    +Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.

    def dialog_suggestion(self,
    callback_id: str | Pattern,
    matchers: Sequence[Callable[..., bool]] | None = None,
    middleware: Sequence[Callable | Middleware] | None = None) ‑> Callable[..., Callable[..., BoltResponse | None] | None]
    @@ -1945,7 +1948,7 @@

    Args

    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new `dialog_suggestion` listener. - Refer to https://api.slack.com/dialogs for details.""" + Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1955,7 +1958,7 @@

    Args

    return __call__

    Registers a new dialog_suggestion listener. -Refer to https://api.slack.com/dialogs for details.

    +Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.

    def dispatch(self,
    req: BoltRequest) ‑> BoltResponse
    @@ -2181,7 +2184,7 @@

    Args

    # Pass a function to this method app.event("team_join")(ask_for_introduction) - Refer to https://api.slack.com/apis/connections/events-api for details of Events API. + Refer to https://docs.slack.dev/apis/events-api/ for details of Events API. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -2193,10 +2196,13 @@

    Args

    middleware: A list of lister middleware functions. Only when all the middleware call `next()` method, the listener function can be invoked. """ + middleware = list(middleware) if middleware else [] def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) primary_matcher = builtin_matchers.event(event, base_logger=self._base_logger) + if self._attaching_conversation_kwargs_enabled: + middleware.insert(0, AttachingConversationKwargs(self._assistant_thread_context_store)) return self._register_listener(list(functions), primary_matcher, matchers, middleware, True) return __call__
    @@ -2213,7 +2219,7 @@

    Args

    # Pass a function to this method app.event("team_join")(ask_for_introduction) -

    Refer to https://api.slack.com/apis/connections/events-api for details of Events API.

    +

    Refer to https://docs.slack.dev/apis/events-api/ for details of Events API.

    To learn available arguments for middleware/listeners, see slack_bolt.kwargs_injection.args's API document.

    Args

    @@ -2229,7 +2235,7 @@

    Args

    -def function(self,
    callback_id: str | Pattern,
    matchers: Sequence[Callable[..., bool]] | None = None,
    middleware: Sequence[Callable | Middleware] | None = None,
    auto_acknowledge: bool = True) ‑> Callable[..., Callable[..., BoltResponse | None] | None]
    +def function(self,
    callback_id: str | Pattern,
    matchers: Sequence[Callable[..., bool]] | None = None,
    middleware: Sequence[Callable | Middleware] | None = None,
    auto_acknowledge: bool = True,
    ack_timeout: int = 3) ‑> Callable[..., Callable[..., BoltResponse | None] | None]
    @@ -2242,6 +2248,7 @@

    Args

    matchers: Optional[Sequence[Callable[..., bool]]] = None, middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, auto_acknowledge: bool = True, + ack_timeout: int = 3, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new Function listener. This method can be used as either a decorator or a method. @@ -2270,13 +2277,17 @@

    Args

    Only when all the middleware call `next()` method, the listener function can be invoked. """ + if auto_acknowledge is True: + if ack_timeout != 3: + self._framework_logger.warning(warning_ack_timeout_has_no_effect(callback_id, ack_timeout)) + matchers = list(matchers) if matchers else [] middleware = list(middleware) if middleware else [] def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) primary_matcher = builtin_matchers.function_executed(callback_id=callback_id, base_logger=self._base_logger) - return self._register_listener(functions, primary_matcher, matchers, middleware, auto_acknowledge) + return self._register_listener(functions, primary_matcher, matchers, middleware, auto_acknowledge, ack_timeout) return __call__
    @@ -2360,7 +2371,7 @@

    Args

    # Pass a function to this method app.message(":wave:")(say_hello) - Refer to https://api.slack.com/events/message for details of `message` events. + Refer to https://docs.slack.dev/reference/events/message/ for details of `message` events. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -2395,6 +2406,8 @@

    Args

    primary_matcher = builtin_matchers.message_event( keyword=keyword, constraints=constraints, base_logger=self._base_logger ) + if self._attaching_conversation_kwargs_enabled: + middleware.insert(0, AttachingConversationKwargs(self._assistant_thread_context_store)) middleware.insert(0, MessageListenerMatches(keyword)) return self._register_listener(list(functions), primary_matcher, matchers, middleware, True) @@ -2411,7 +2424,7 @@

    Args

    # Pass a function to this method app.message(":wave:")(say_hello) -

    Refer to https://api.slack.com/events/message for details of message events.

    +

    Refer to https://docs.slack.dev/reference/events/message/ for details of message events.

    To learn available arguments for middleware/listeners, see slack_bolt.kwargs_injection.args's API document.

    Args

    @@ -2471,7 +2484,7 @@

    Args

    # Pass a function to this method app.middleware(middleware_func) - Refer to https://slack.dev/bolt-python/concepts#global-middleware for details. + Refer to https://docs.slack.dev/tools/bolt-python/concepts/global-middleware for details. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -2509,7 +2522,7 @@

    Args

    # Pass a function to this method app.middleware(middleware_func) -

    Refer to https://slack.dev/bolt-python/concepts#global-middleware for details.

    +

    Refer to https://docs.slack.dev/tools/bolt-python/concepts/global-middleware for details.

    To learn available arguments for middleware/listeners, see slack_bolt.kwargs_injection.args's API document.

    Args

    @@ -2554,8 +2567,8 @@

    Args

    Refer to the following documents for details: - * https://api.slack.com/reference/block-kit/block-elements#external_select - * https://api.slack.com/reference/block-kit/block-elements#external_multi_select + * https://docs.slack.dev/reference/block-kit/block-elements/select-menu-element#external_select + * https://docs.slack.dev/reference/block-kit/block-elements/multi-select-menu-element#external_multi_select To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -2595,8 +2608,8 @@

    Args

    Refer to the following documents for details:

    To learn available arguments for middleware/listeners, see slack_bolt.kwargs_injection.args's API document.

    Args

    @@ -2642,7 +2655,7 @@

    Args

    # Pass a function to this method app.shortcut("open_modal")(open_modal) - Refer to https://api.slack.com/interactivity/shortcuts for details about Shortcuts. + Refer to https://docs.slack.dev/interactivity/implementing-shortcuts/ for details about Shortcuts. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -2679,7 +2692,7 @@

    Args

    # Pass a function to this method app.shortcut("open_modal")(open_modal) -

    Refer to https://api.slack.com/interactivity/shortcuts for details about Shortcuts.

    +

    Refer to https://docs.slack.dev/interactivity/implementing-shortcuts/ for details about Shortcuts.

    To learn available arguments for middleware/listeners, see slack_bolt.kwargs_injection.args's API document.

    Args

    @@ -2765,7 +2778,7 @@

    Args

    """ Deprecated: Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://api.slack.com/automation/functions/custom-bolt + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ Registers a new step from app listener. @@ -2783,7 +2796,7 @@

    Args

    # Pass Step to set up listeners app.step(ws) - Refer to https://api.slack.com/workflows/steps for details of steps from apps. + Refer to https://docs.slack.dev/legacy/legacy-steps-from-apps/ for details of steps from apps. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -2800,7 +2813,7 @@

    Args

    warnings.warn( ( "Steps from apps for legacy workflows are now deprecated. " - "Use new custom steps: https://api.slack.com/automation/functions/custom-bolt" + "Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/" ), category=DeprecationWarning, ) @@ -2822,7 +2835,7 @@

    Args

    Deprecated

    Steps from apps for legacy workflows are now deprecated. -Use new custom steps: https://api.slack.com/automation/functions/custom-bolt

    +Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/

    Registers a new step from app listener.

    Unlike others, this method doesn't behave as a decorator. If you want to register a step from app by a decorator, use WorkflowStepBuilder's methods.

    @@ -2837,7 +2850,7 @@

    Args

    # Pass Step to set up listeners app.step(ws) -

    Refer to https://api.slack.com/workflows/steps for details of steps from apps.

    +

    Refer to https://docs.slack.dev/legacy/legacy-steps-from-apps/ for details of steps from apps.

    To learn available arguments for middleware/listeners, see slack_bolt.kwargs_injection.args's API document.

    For further information about WorkflowStep specific function arguments such as configure, update, complete, and fail, @@ -2908,7 +2921,7 @@

    Args

    # Pass a function to this method app.view("view_1")(handle_submission) - Refer to https://api.slack.com/reference/interaction-payloads/views for details of payloads. + Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload for details of payloads. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -2949,7 +2962,7 @@

    Args

    # Pass a function to this method app.view("view_1")(handle_submission) -

    Refer to https://api.slack.com/reference/interaction-payloads/views for details of payloads.

    +

    Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload for details of payloads.

    To learn available arguments for middleware/listeners, see slack_bolt.kwargs_injection.args's API document.

    Args

    @@ -2978,7 +2991,7 @@

    Args

    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new `view_closed` listener. - Refer to https://api.slack.com/reference/interaction-payloads/views#view_closed for details.""" + Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_closed for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -2988,7 +3001,7 @@

    Args

    return __call__

    Registers a new view_closed listener. -Refer to https://api.slack.com/reference/interaction-payloads/views#view_closed for details.

    +Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_closed for details.

    def view_submission(self,
    constraints: str | Pattern,
    matchers: Sequence[Callable[..., bool]] | None = None,
    middleware: Sequence[Callable | Middleware] | None = None) ‑> Callable[..., Callable[..., BoltResponse | None] | None]
    @@ -3005,7 +3018,9 @@

    Args

    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new `view_submission` listener. - Refer to https://api.slack.com/reference/interaction-payloads/views#view_submission for details.""" + Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for + details. + """ def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -3015,7 +3030,8 @@

    Args

    return __call__

    Registers a new view_submission listener. -Refer to https://api.slack.com/reference/interaction-payloads/views#view_submission for details.

    +Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for +details.

  • @@ -3266,7 +3282,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/app/async_app.html b/docs/reference/app/async_app.html similarity index 94% rename from docs/static/api-docs/slack_bolt/app/async_app.html rename to docs/reference/app/async_app.html index f1bdcf2ca..cf4c651cb 100644 --- a/docs/static/api-docs/slack_bolt/app/async_app.html +++ b/docs/reference/app/async_app.html @@ -3,7 +3,7 @@ - + slack_bolt.app.async_app API documentation @@ -48,7 +48,7 @@

    Classes

    class AsyncApp -(*,
    logger: logging.Logger | None = None,
    name: str | None = None,
    process_before_response: bool = False,
    raise_error_for_unhandled_request: bool = False,
    signing_secret: str | None = None,
    token: str | None = None,
    client: slack_sdk.web.async_client.AsyncWebClient | None = None,
    before_authorize: AsyncMiddleware | Callable[..., Awaitable[Any]] | None = None,
    authorize: Callable[..., Awaitable[AuthorizeResult]] | None = None,
    user_facing_authorize_error_message: str | None = None,
    installation_store: slack_sdk.oauth.installation_store.async_installation_store.AsyncInstallationStore | None = None,
    installation_store_bot_only: bool | None = None,
    request_verification_enabled: bool = True,
    ignoring_self_events_enabled: bool = True,
    ignoring_self_assistant_message_events_enabled: bool = True,
    ssl_check_enabled: bool = True,
    url_verification_enabled: bool = True,
    attaching_function_token_enabled: bool = True,
    oauth_settings: AsyncOAuthSettings | None = None,
    oauth_flow: AsyncOAuthFlow | None = None,
    verification_token: str | None = None,
    assistant_thread_context_store: AsyncAssistantThreadContextStore | None = None)
    +(*,
    logger: logging.Logger | None = None,
    name: str | None = None,
    process_before_response: bool = False,
    raise_error_for_unhandled_request: bool = False,
    signing_secret: str | None = None,
    token: str | None = None,
    client: slack_sdk.web.async_client.AsyncWebClient | None = None,
    before_authorize: AsyncMiddleware | Callable[..., Awaitable[Any]] | None = None,
    authorize: Callable[..., Awaitable[AuthorizeResult]] | None = None,
    user_facing_authorize_error_message: str | None = None,
    installation_store: slack_sdk.oauth.installation_store.async_installation_store.AsyncInstallationStore | None = None,
    installation_store_bot_only: bool | None = None,
    request_verification_enabled: bool = True,
    ignoring_self_events_enabled: bool = True,
    ignoring_self_assistant_message_events_enabled: bool = True,
    ssl_check_enabled: bool = True,
    url_verification_enabled: bool = True,
    attaching_function_token_enabled: bool = True,
    oauth_settings: AsyncOAuthSettings | None = None,
    oauth_flow: AsyncOAuthFlow | None = None,
    verification_token: str | None = None,
    assistant_thread_context_store: AsyncAssistantThreadContextStore | None = None,
    attaching_conversation_kwargs_enabled: bool = True)
    @@ -92,6 +92,7 @@

    Classes

    verification_token: Optional[str] = None, # for AI Agents & Assistants assistant_thread_context_store: Optional[AsyncAssistantThreadContextStore] = None, + attaching_conversation_kwargs_enabled: bool = True, ): """Bolt App that provides functionalities to register middleware/listeners. @@ -114,10 +115,10 @@

    Classes

    if __name__ == "__main__": app.start(port=int(os.environ.get("PORT", 3000))) - Refer to https://slack.dev/bolt-python/concepts#async for details. + Refer to https://docs.slack.dev/tools/bolt-python/concepts/async for details. If you would like to build an OAuth app for enabling the app to run with multiple workspaces, - refer to https://slack.dev/bolt-python/concepts#authenticating-oauth to learn how to configure the app. + refer to https://docs.slack.dev/tools/bolt-python/concepts/authenticating-oauth to learn how to configure the app. Args: logger: The custom logger that can be used in this app. @@ -312,6 +313,7 @@

    Classes

    self._async_listeners: List[AsyncListener] = [] self._assistant_thread_context_store = assistant_thread_context_store + self._attaching_conversation_kwargs_enabled = attaching_conversation_kwargs_enabled self._process_before_response = process_before_response self._async_listener_runner = AsyncioListenerRunner( @@ -565,7 +567,7 @@

    Classes

    self._framework_logger.debug(debug_checking_listener(listener_name)) if await listener.async_matches(req=req, resp=resp): # type: ignore[arg-type] # run all the middleware attached to this listener first - (middleware_resp, next_was_not_called) = await listener.run_async_middleware( + middleware_resp, next_was_not_called = await listener.run_async_middleware( req=req, resp=resp # type: ignore[arg-type] ) if next_was_not_called: @@ -687,7 +689,7 @@

    Classes

    """ Deprecated: Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://api.slack.com/automation/functions/custom-bolt + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ Registers a new step from app listener. @@ -705,7 +707,7 @@

    Classes

    # Pass Step to set up listeners app.step(ws) - Refer to https://api.slack.com/workflows/steps for details of steps from apps. + Refer to https://docs.slack.dev/legacy/legacy-steps-from-apps/ for details of steps from apps. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.async_args`'s API document. For further information about AsyncWorkflowStep specific function arguments @@ -721,7 +723,7 @@

    Classes

    warnings.warn( ( "Steps from apps for legacy workflows are now deprecated. " - "Use new custom steps: https://api.slack.com/automation/functions/custom-bolt" + "Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/" ), category=DeprecationWarning, ) @@ -803,7 +805,7 @@

    Classes

    # Pass a function to this method app.event("team_join")(ask_for_introduction) - Refer to https://api.slack.com/apis/connections/events-api for details of Events API. + Refer to https://docs.slack.dev/apis/events-api/ for details of Events API. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.async_args`'s API document. @@ -815,10 +817,13 @@

    Classes

    middleware: A list of lister middleware functions. Only when all the middleware call `next()` method, the listener function can be invoked. """ + middleware = list(middleware) if middleware else [] def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) primary_matcher = builtin_matchers.event(event, True, base_logger=self._base_logger) + if self._attaching_conversation_kwargs_enabled: + middleware.insert(0, AsyncAttachingConversationKwargs(self._assistant_thread_context_store)) return self._register_listener(list(functions), primary_matcher, matchers, middleware, True) return __call__ @@ -841,7 +846,7 @@

    Classes

    # Pass a function to this method app.message(":wave:")(say_hello) - Refer to https://api.slack.com/events/message for details of `message` events. + Refer to https://docs.slack.dev/reference/events/message/ for details of `message` events. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.async_args`'s API document. @@ -879,6 +884,8 @@

    Classes

    asyncio=True, base_logger=self._base_logger, ) + if self._attaching_conversation_kwargs_enabled: + middleware.insert(0, AsyncAttachingConversationKwargs(self._assistant_thread_context_store)) middleware.insert(0, AsyncMessageListenerMatches(keyword)) return self._register_listener(list(functions), primary_matcher, matchers, middleware, True) @@ -890,6 +897,7 @@

    Classes

    matchers: Optional[Sequence[Callable[..., Awaitable[bool]]]] = None, middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None, auto_acknowledge: bool = True, + ack_timeout: int = 3, ) -> Callable[..., Optional[Callable[..., Awaitable[BoltResponse]]]]: """Registers a new Function listener. This method can be used as either a decorator or a method. @@ -917,6 +925,9 @@

    Classes

    middleware: A list of lister middleware functions. Only when all the middleware call `next()` method, the listener function can be invoked. """ + if auto_acknowledge is True: + if ack_timeout != 3: + self._framework_logger.warning(warning_ack_timeout_has_no_effect(callback_id, ack_timeout)) matchers = list(matchers) if matchers else [] middleware = list(middleware) if middleware else [] @@ -926,7 +937,7 @@

    Classes

    primary_matcher = builtin_matchers.function_executed( callback_id=callback_id, base_logger=self._base_logger, asyncio=True ) - return self._register_listener(functions, primary_matcher, matchers, middleware, auto_acknowledge) + return self._register_listener(functions, primary_matcher, matchers, middleware, auto_acknowledge, ack_timeout) return __call__ @@ -952,7 +963,7 @@

    Classes

    # Pass a function to this method app.command("/echo")(repeat_text) - Refer to https://api.slack.com/interactivity/slash-commands for details of Slash Commands. + Refer to https://docs.slack.dev/interactivity/implementing-slash-commands/ for details of Slash Commands. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.async_args`'s API document. @@ -999,7 +1010,7 @@

    Classes

    # Pass a function to this method app.shortcut("open_modal")(open_modal) - Refer to https://api.slack.com/interactivity/shortcuts for details about Shortcuts. + Refer to https://docs.slack.dev/interactivity/implementing-shortcuts/ for details about Shortcuts. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.async_args`'s API document. @@ -1067,9 +1078,9 @@

    Classes

    # Pass a function to this method app.action("approve_button")(update_message) - * Refer to https://api.slack.com/reference/interaction-payloads/block-actions for actions in `blocks`. - * Refer to https://api.slack.com/legacy/message-buttons for actions in `attachments`. - * Refer to https://api.slack.com/dialogs for actions in dialogs. + * Refer to https://docs.slack.dev/reference/interaction-payloads/block_actions-payload/ for actions in `blocks`. + * Refer to https://docs.slack.dev/legacy/legacy-messaging/legacy-message-buttons/ for actions in `attachments`. + * Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for actions in dialogs. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.async_args`'s API document. @@ -1095,7 +1106,7 @@

    Classes

    middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None, ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]: """Registers a new `block_actions` action listener. - Refer to https://api.slack.com/reference/interaction-payloads/block-actions for details. + Refer to https://docs.slack.dev/reference/interaction-payloads/block_actions-payload/ for details. """ def __call__(*args, **kwargs): @@ -1112,7 +1123,7 @@

    Classes

    middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None, ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]: """Registers a new `interactive_message` action listener. - Refer to https://api.slack.com/legacy/message-buttons for details.""" + Refer to https://docs.slack.dev/legacy/legacy-messaging/legacy-message-buttons/ for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1128,7 +1139,7 @@

    Classes

    middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None, ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]: """Registers a new `dialog_submission` listener. - Refer to https://api.slack.com/dialogs for details.""" + Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1144,7 +1155,7 @@

    Classes

    middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None, ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]: """Registers a new `dialog_submission` listener. - Refer to https://api.slack.com/dialogs for details.""" + Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1185,7 +1196,7 @@

    Classes

    # Pass a function to this method app.view("view_1")(handle_submission) - Refer to https://api.slack.com/reference/interaction-payloads/views for details of payloads. + Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload for details of payloads. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.async_args`'s API document. @@ -1211,7 +1222,9 @@

    Classes

    middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None, ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]: """Registers a new `view_submission` listener. - Refer to https://api.slack.com/reference/interaction-payloads/views#view_submission for details.""" + Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for + details. + """ def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1227,7 +1240,7 @@

    Classes

    middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None, ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]: """Registers a new `view_closed` listener. - Refer to https://api.slack.com/reference/interaction-payloads/views#view_closed for details.""" + Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_closed for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1268,8 +1281,8 @@

    Classes

    Refer to the following documents for details: - * https://api.slack.com/reference/block-kit/block-elements#external_select - * https://api.slack.com/reference/block-kit/block-elements#external_multi_select + * https://docs.slack.dev/reference/block-kit/block-elements/select-menu-element#external_select + * https://docs.slack.dev/reference/block-kit/block-elements/multi-select-menu-element#external_multi_select To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.async_args`'s API document. @@ -1309,7 +1322,7 @@

    Classes

    middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None, ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]: """Registers a new `dialog_suggestion` listener. - Refer to https://api.slack.com/dialogs for details.""" + Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1363,6 +1376,7 @@

    Classes

    trust_env_in_session=self._async_client.trust_env_in_session, headers=self._async_client.headers, team_id=req.context.team_id, + logger=self._async_client.logger, retry_handlers=( self._async_client.retry_handlers.copy() if self._async_client.retry_handlers is not None else None ), @@ -1373,20 +1387,6 @@

    Classes

    # It is intended for apps that start lazy listeners from their custom global middleware. req.context["listener_runner"] = self.listener_runner - # For AI Agents & Assistants - if is_assistant_event(req.body): - assistant = AsyncAssistantUtilities( - payload=to_event(req.body), # type:ignore[arg-type] - context=req.context, - thread_context_store=self._assistant_thread_context_store, - ) - req.context["say"] = assistant.say - req.context["set_status"] = assistant.set_status - req.context["set_title"] = assistant.set_title - req.context["set_suggested_prompts"] = assistant.set_suggested_prompts - req.context["get_thread_context"] = assistant.get_thread_context - req.context["save_thread_context"] = assistant.save_thread_context - @staticmethod def _to_listener_functions( kwargs: dict, @@ -1405,6 +1405,7 @@

    Classes

    matchers: Optional[Sequence[Callable[..., Awaitable[bool]]]], middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]], auto_acknowledgement: bool = False, + ack_timeout: int = 3, ) -> Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]: value_to_return = None if not isinstance(functions, list): @@ -1436,10 +1437,11 @@

    Classes

    AsyncCustomListener( app_name=self.name, ack_function=functions.pop(0), - lazy_functions=functions, + lazy_functions=functions, # type: ignore[arg-type] matchers=listener_matchers, middleware=listener_middleware, auto_acknowledgement=auto_acknowledgement, + ack_timeout=ack_timeout, base_logger=self._base_logger, ) ) @@ -1466,9 +1468,9 @@

    Classes

    if __name__ == "__main__": app.start(port=int(os.environ.get("PORT", 3000)))
    -

    Refer to https://slack.dev/bolt-python/concepts#async for details.

    +

    Refer to https://docs.slack.dev/tools/bolt-python/concepts/async for details.

    If you would like to build an OAuth app for enabling the app to run with multiple workspaces, -refer to https://slack.dev/bolt-python/concepts#authenticating-oauth to learn how to configure the app.

    +refer to https://docs.slack.dev/tools/bolt-python/concepts/authenticating-oauth to learn how to configure the app.

    Args

    logger
    @@ -1662,9 +1664,9 @@

    Methods

    # Pass a function to this method app.action("approve_button")(update_message) - * Refer to https://api.slack.com/reference/interaction-payloads/block-actions for actions in `blocks`. - * Refer to https://api.slack.com/legacy/message-buttons for actions in `attachments`. - * Refer to https://api.slack.com/dialogs for actions in dialogs. + * Refer to https://docs.slack.dev/reference/interaction-payloads/block_actions-payload/ for actions in `blocks`. + * Refer to https://docs.slack.dev/legacy/legacy-messaging/legacy-message-buttons/ for actions in `attachments`. + * Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for actions in dialogs. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.async_args`'s API document. @@ -1693,9 +1695,9 @@

    Methods

    app.action("approve_button")(update_message)

    To learn available arguments for middleware/listeners, see slack_bolt.kwargs_injection.async_args's API document.

    Args

    @@ -1785,7 +1787,7 @@

    Args

    self._framework_logger.debug(debug_checking_listener(listener_name)) if await listener.async_matches(req=req, resp=resp): # type: ignore[arg-type] # run all the middleware attached to this listener first - (middleware_resp, next_was_not_called) = await listener.run_async_middleware( + middleware_resp, next_was_not_called = await listener.run_async_middleware( req=req, resp=resp # type: ignore[arg-type] ) if next_was_not_called: @@ -1866,7 +1868,7 @@

    Returns

    middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None, ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]: """Registers a new `interactive_message` action listener. - Refer to https://api.slack.com/legacy/message-buttons for details.""" + Refer to https://docs.slack.dev/legacy/legacy-messaging/legacy-message-buttons/ for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1876,7 +1878,7 @@

    Returns

    return __call__

    Registers a new interactive_message action listener. -Refer to https://api.slack.com/legacy/message-buttons for details.

    +Refer to https://docs.slack.dev/legacy/legacy-messaging/legacy-message-buttons/ for details.

    def block_action(self,
    constraints: str | Pattern | Dict[str, str | Pattern],
    matchers: Sequence[Callable[..., Awaitable[bool]]] | None = None,
    middleware: Sequence[Callable | AsyncMiddleware] | None = None) ‑> Callable[..., Callable[..., Awaitable[BoltResponse | None]] | None]
    @@ -1893,7 +1895,7 @@

    Returns

    middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None, ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]: """Registers a new `block_actions` action listener. - Refer to https://api.slack.com/reference/interaction-payloads/block-actions for details. + Refer to https://docs.slack.dev/reference/interaction-payloads/block_actions-payload/ for details. """ def __call__(*args, **kwargs): @@ -1904,7 +1906,7 @@

    Returns

    return __call__

    Registers a new block_actions action listener. -Refer to https://api.slack.com/reference/interaction-payloads/block-actions for details.

    +Refer to https://docs.slack.dev/reference/interaction-payloads/block_actions-payload/ for details.

    def block_suggestion(self,
    action_id: str | Pattern,
    matchers: Sequence[Callable[..., Awaitable[bool]]] | None = None,
    middleware: Sequence[Callable | AsyncMiddleware] | None = None) ‑> Callable[..., Callable[..., Awaitable[BoltResponse | None]] | None]
    @@ -1958,7 +1960,7 @@

    Returns

    # Pass a function to this method app.command("/echo")(repeat_text) - Refer to https://api.slack.com/interactivity/slash-commands for details of Slash Commands. + Refer to https://docs.slack.dev/interactivity/implementing-slash-commands/ for details of Slash Commands. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.async_args`'s API document. @@ -1989,7 +1991,7 @@

    Returns

    # Pass a function to this method app.command("/echo")(repeat_text)
    -

    Refer to https://api.slack.com/interactivity/slash-commands for details of Slash Commands.

    +

    Refer to https://docs.slack.dev/interactivity/implementing-slash-commands/ for details of Slash Commands.

    To learn available arguments for middleware/listeners, see slack_bolt.kwargs_injection.async_args's API document.

    Args

    @@ -2052,7 +2054,7 @@

    Args

    middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None, ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]: """Registers a new `dialog_submission` listener. - Refer to https://api.slack.com/dialogs for details.""" + Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -2062,7 +2064,7 @@

    Args

    return __call__

    Registers a new dialog_submission listener. -Refer to https://api.slack.com/dialogs for details.

    +Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.

    def dialog_submission(self,
    callback_id: str | Pattern,
    matchers: Sequence[Callable[..., Awaitable[bool]]] | None = None,
    middleware: Sequence[Callable | AsyncMiddleware] | None = None) ‑> Callable[..., Callable[..., Awaitable[BoltResponse | None]] | None]
    @@ -2079,7 +2081,7 @@

    Args

    middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None, ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]: """Registers a new `dialog_submission` listener. - Refer to https://api.slack.com/dialogs for details.""" + Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -2089,7 +2091,7 @@

    Args

    return __call__

    Registers a new dialog_submission listener. -Refer to https://api.slack.com/dialogs for details.

    +Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.

    def dialog_suggestion(self,
    callback_id: str | Pattern,
    matchers: Sequence[Callable[..., Awaitable[bool]]] | None = None,
    middleware: Sequence[Callable | AsyncMiddleware] | None = None) ‑> Callable[..., Callable[..., Awaitable[BoltResponse | None]] | None]
    @@ -2106,7 +2108,7 @@

    Args

    middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None, ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]: """Registers a new `dialog_suggestion` listener. - Refer to https://api.slack.com/dialogs for details.""" + Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -2116,7 +2118,7 @@

    Args

    return __call__

    Registers a new dialog_suggestion listener. -Refer to https://api.slack.com/dialogs for details.

    +Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.

    def enable_token_revocation_listeners(self) ‑> None @@ -2222,7 +2224,7 @@

    Args

    # Pass a function to this method app.event("team_join")(ask_for_introduction) - Refer to https://api.slack.com/apis/connections/events-api for details of Events API. + Refer to https://docs.slack.dev/apis/events-api/ for details of Events API. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.async_args`'s API document. @@ -2234,10 +2236,13 @@

    Args

    middleware: A list of lister middleware functions. Only when all the middleware call `next()` method, the listener function can be invoked. """ + middleware = list(middleware) if middleware else [] def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) primary_matcher = builtin_matchers.event(event, True, base_logger=self._base_logger) + if self._attaching_conversation_kwargs_enabled: + middleware.insert(0, AsyncAttachingConversationKwargs(self._assistant_thread_context_store)) return self._register_listener(list(functions), primary_matcher, matchers, middleware, True) return __call__
    @@ -2254,7 +2259,7 @@

    Args

    # Pass a function to this method app.event("team_join")(ask_for_introduction) -

    Refer to https://api.slack.com/apis/connections/events-api for details of Events API.

    +

    Refer to https://docs.slack.dev/apis/events-api/ for details of Events API.

    To learn available arguments for middleware/listeners, see slack_bolt.kwargs_injection.async_args's API document.

    Args

    @@ -2270,7 +2275,7 @@

    Args

    -def function(self,
    callback_id: str | Pattern,
    matchers: Sequence[Callable[..., Awaitable[bool]]] | None = None,
    middleware: Sequence[Callable | AsyncMiddleware] | None = None,
    auto_acknowledge: bool = True) ‑> Callable[..., Callable[..., Awaitable[BoltResponse]] | None]
    +def function(self,
    callback_id: str | Pattern,
    matchers: Sequence[Callable[..., Awaitable[bool]]] | None = None,
    middleware: Sequence[Callable | AsyncMiddleware] | None = None,
    auto_acknowledge: bool = True,
    ack_timeout: int = 3) ‑> Callable[..., Callable[..., Awaitable[BoltResponse]] | None]
    @@ -2283,6 +2288,7 @@

    Args

    matchers: Optional[Sequence[Callable[..., Awaitable[bool]]]] = None, middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None, auto_acknowledge: bool = True, + ack_timeout: int = 3, ) -> Callable[..., Optional[Callable[..., Awaitable[BoltResponse]]]]: """Registers a new Function listener. This method can be used as either a decorator or a method. @@ -2310,6 +2316,9 @@

    Args

    middleware: A list of lister middleware functions. Only when all the middleware call `next()` method, the listener function can be invoked. """ + if auto_acknowledge is True: + if ack_timeout != 3: + self._framework_logger.warning(warning_ack_timeout_has_no_effect(callback_id, ack_timeout)) matchers = list(matchers) if matchers else [] middleware = list(middleware) if middleware else [] @@ -2319,7 +2328,7 @@

    Args

    primary_matcher = builtin_matchers.function_executed( callback_id=callback_id, base_logger=self._base_logger, asyncio=True ) - return self._register_listener(functions, primary_matcher, matchers, middleware, auto_acknowledge) + return self._register_listener(functions, primary_matcher, matchers, middleware, auto_acknowledge, ack_timeout) return __call__
    @@ -2403,7 +2412,7 @@

    Args

    # Pass a function to this method app.message(":wave:")(say_hello) - Refer to https://api.slack.com/events/message for details of `message` events. + Refer to https://docs.slack.dev/reference/events/message/ for details of `message` events. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.async_args`'s API document. @@ -2441,6 +2450,8 @@

    Args

    asyncio=True, base_logger=self._base_logger, ) + if self._attaching_conversation_kwargs_enabled: + middleware.insert(0, AsyncAttachingConversationKwargs(self._assistant_thread_context_store)) middleware.insert(0, AsyncMessageListenerMatches(keyword)) return self._register_listener(list(functions), primary_matcher, matchers, middleware, True) @@ -2457,7 +2468,7 @@

    Args

    # Pass a function to this method app.message(":wave:")(say_hello) -

    Refer to https://api.slack.com/events/message for details of message events.

    +

    Refer to https://docs.slack.dev/reference/events/message/ for details of message events.

    To learn available arguments for middleware/listeners, see slack_bolt.kwargs_injection.async_args's API document.

    Args

    @@ -2597,8 +2608,8 @@

    Args

    Refer to the following documents for details: - * https://api.slack.com/reference/block-kit/block-elements#external_select - * https://api.slack.com/reference/block-kit/block-elements#external_multi_select + * https://docs.slack.dev/reference/block-kit/block-elements/select-menu-element#external_select + * https://docs.slack.dev/reference/block-kit/block-elements/multi-select-menu-element#external_multi_select To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.async_args`'s API document. @@ -2638,8 +2649,8 @@

    Args

    Refer to the following documents for details:

    To learn available arguments for middleware/listeners, see slack_bolt.kwargs_injection.async_args's API document.

    Args

    @@ -2728,7 +2739,7 @@

    Args

    # Pass a function to this method app.shortcut("open_modal")(open_modal) - Refer to https://api.slack.com/interactivity/shortcuts for details about Shortcuts. + Refer to https://docs.slack.dev/interactivity/implementing-shortcuts/ for details about Shortcuts. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.async_args`'s API document. @@ -2765,7 +2776,7 @@

    Args

    # Pass a function to this method app.shortcut("open_modal")(open_modal) -

    Refer to https://api.slack.com/interactivity/shortcuts for details about Shortcuts.

    +

    Refer to https://docs.slack.dev/interactivity/implementing-shortcuts/ for details about Shortcuts.

    To learn available arguments for middleware/listeners, see slack_bolt.kwargs_injection.async_args's API document.

    Args

    @@ -2828,7 +2839,7 @@

    Args

    """ Deprecated: Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://api.slack.com/automation/functions/custom-bolt + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ Registers a new step from app listener. @@ -2846,7 +2857,7 @@

    Args

    # Pass Step to set up listeners app.step(ws) - Refer to https://api.slack.com/workflows/steps for details of steps from apps. + Refer to https://docs.slack.dev/legacy/legacy-steps-from-apps/ for details of steps from apps. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.async_args`'s API document. For further information about AsyncWorkflowStep specific function arguments @@ -2862,7 +2873,7 @@

    Args

    warnings.warn( ( "Steps from apps for legacy workflows are now deprecated. " - "Use new custom steps: https://api.slack.com/automation/functions/custom-bolt" + "Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/" ), category=DeprecationWarning, ) @@ -2884,7 +2895,7 @@

    Args

    Deprecated

    Steps from apps for legacy workflows are now deprecated. -Use new custom steps: https://api.slack.com/automation/functions/custom-bolt

    +Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/

    Registers a new step from app listener.

    Unlike others, this method doesn't behave as a decorator. If you want to register a step from app by a decorator, use AsyncWorkflowStepBuilder's methods.

    @@ -2899,7 +2910,7 @@

    Args

    # Pass Step to set up listeners app.step(ws) -

    Refer to https://api.slack.com/workflows/steps for details of steps from apps.

    +

    Refer to https://docs.slack.dev/legacy/legacy-steps-from-apps/ for details of steps from apps.

    To learn available arguments for middleware/listeners, see slack_bolt.kwargs_injection.async_args's API document. For further information about AsyncWorkflowStep specific function arguments such as configure, update, complete, and fail, @@ -2967,7 +2978,7 @@

    Args

    # Pass a function to this method app.view("view_1")(handle_submission) - Refer to https://api.slack.com/reference/interaction-payloads/views for details of payloads. + Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload for details of payloads. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.async_args`'s API document. @@ -3008,7 +3019,7 @@

    Args

    # Pass a function to this method app.view("view_1")(handle_submission) -

    Refer to https://api.slack.com/reference/interaction-payloads/views for details of payloads.

    +

    Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload for details of payloads.

    To learn available arguments for middleware/listeners, see slack_bolt.kwargs_injection.async_args's API document.

    Args

    @@ -3037,7 +3048,7 @@

    Args

    middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None, ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]: """Registers a new `view_closed` listener. - Refer to https://api.slack.com/reference/interaction-payloads/views#view_closed for details.""" + Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_closed for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -3047,7 +3058,7 @@

    Args

    return __call__

    Registers a new view_closed listener. -Refer to https://api.slack.com/reference/interaction-payloads/views#view_closed for details.

    +Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_closed for details.

    def view_submission(self,
    constraints: str | Pattern,
    matchers: Sequence[Callable[..., Awaitable[bool]]] | None = None,
    middleware: Sequence[Callable | AsyncMiddleware] | None = None) ‑> Callable[..., Callable[..., Awaitable[BoltResponse | None]] | None]
    @@ -3064,7 +3075,9 @@

    Args

    middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None, ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]: """Registers a new `view_submission` listener. - Refer to https://api.slack.com/reference/interaction-payloads/views#view_submission for details.""" + Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for + details. + """ def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -3074,7 +3087,8 @@

    Args

    return __call__

    Registers a new view_submission listener. -Refer to https://api.slack.com/reference/interaction-payloads/views#view_submission for details.

    +Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for +details.

    def web_app(self, path: str = '/slack/events', port: int = 3000) ‑> aiohttp.web_app.Application @@ -3194,7 +3208,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/app/async_server.html b/docs/reference/app/async_server.html similarity index 98% rename from docs/static/api-docs/slack_bolt/app/async_server.html rename to docs/reference/app/async_server.html index 25d28fed1..5eefe90dd 100644 --- a/docs/static/api-docs/slack_bolt/app/async_server.html +++ b/docs/reference/app/async_server.html @@ -3,7 +3,7 @@ - + slack_bolt.app.async_server API documentation @@ -59,14 +59,14 @@

    Classes

    port: int path: str host: str - bolt_app: "AsyncApp" # type: ignore[name-defined] + bolt_app: "AsyncApp" web_app: web.Application def __init__( self, port: int, path: str, - app: "AsyncApp", # type: ignore[name-defined] + app: "AsyncApp", host: Optional[str] = None, ): """Standalone AIOHTTP Web Server. @@ -81,7 +81,7 @@

    Classes

    self.port = port self.path = path self.host = host if host is not None else "0.0.0.0" - self.bolt_app: "AsyncApp" = app # type: ignore[name-defined] + self.bolt_app: "AsyncApp" = app self.web_app = web.Application() self._bolt_oauth_flow = self.bolt_app.oauth_flow if self._bolt_oauth_flow: @@ -270,7 +270,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/app/index.html b/docs/reference/app/index.html similarity index 93% rename from docs/static/api-docs/slack_bolt/app/index.html rename to docs/reference/app/index.html index 8d5cbe618..5581b98e7 100644 --- a/docs/static/api-docs/slack_bolt/app/index.html +++ b/docs/reference/app/index.html @@ -3,7 +3,7 @@ - + slack_bolt.app API documentation @@ -67,7 +67,7 @@

    Classes

    class App -(*,
    logger: logging.Logger | None = None,
    name: str | None = None,
    process_before_response: bool = False,
    raise_error_for_unhandled_request: bool = False,
    signing_secret: str | None = None,
    token: str | None = None,
    token_verification_enabled: bool = True,
    client: slack_sdk.web.client.WebClient | None = None,
    before_authorize: Middleware | Callable[..., Any] | None = None,
    authorize: Callable[..., AuthorizeResult] | None = None,
    user_facing_authorize_error_message: str | None = None,
    installation_store: slack_sdk.oauth.installation_store.installation_store.InstallationStore | None = None,
    installation_store_bot_only: bool | None = None,
    request_verification_enabled: bool = True,
    ignoring_self_events_enabled: bool = True,
    ignoring_self_assistant_message_events_enabled: bool = True,
    ssl_check_enabled: bool = True,
    url_verification_enabled: bool = True,
    attaching_function_token_enabled: bool = True,
    oauth_settings: OAuthSettings | None = None,
    oauth_flow: OAuthFlow | None = None,
    verification_token: str | None = None,
    listener_executor: concurrent.futures._base.Executor | None = None,
    assistant_thread_context_store: AssistantThreadContextStore | None = None)
    +(*,
    logger: logging.Logger | None = None,
    name: str | None = None,
    process_before_response: bool = False,
    raise_error_for_unhandled_request: bool = False,
    signing_secret: str | None = None,
    token: str | None = None,
    token_verification_enabled: bool = True,
    client: slack_sdk.web.client.WebClient | None = None,
    before_authorize: Middleware | Callable[..., Any] | None = None,
    authorize: Callable[..., AuthorizeResult] | None = None,
    user_facing_authorize_error_message: str | None = None,
    installation_store: slack_sdk.oauth.installation_store.installation_store.InstallationStore | None = None,
    installation_store_bot_only: bool | None = None,
    request_verification_enabled: bool = True,
    ignoring_self_events_enabled: bool = True,
    ignoring_self_assistant_message_events_enabled: bool = True,
    ssl_check_enabled: bool = True,
    url_verification_enabled: bool = True,
    attaching_function_token_enabled: bool = True,
    oauth_settings: OAuthSettings | None = None,
    oauth_flow: OAuthFlow | None = None,
    verification_token: str | None = None,
    listener_executor: concurrent.futures._base.Executor | None = None,
    assistant_thread_context_store: AssistantThreadContextStore | None = None,
    attaching_conversation_kwargs_enabled: bool = True)
    @@ -114,6 +114,7 @@

    Classes

    listener_executor: Optional[Executor] = None, # for AI Agents & Assistants assistant_thread_context_store: Optional[AssistantThreadContextStore] = None, + attaching_conversation_kwargs_enabled: bool = True, ): """Bolt App that provides functionalities to register middleware/listeners. @@ -136,10 +137,10 @@

    Classes

    if __name__ == "__main__": app.start(port=int(os.environ.get("PORT", 3000))) - Refer to https://slack.dev/bolt-python/tutorial/getting-started for details. + Refer to https://docs.slack.dev/tools/bolt-python/creating-an-app for details. If you would like to build an OAuth app for enabling the app to run with multiple workspaces, - refer to https://slack.dev/bolt-python/concepts#authenticating-oauth to learn how to configure the app. + refer to https://docs.slack.dev/tools/bolt-python/concepts/authenticating-oauth to learn how to configure the app. Args: logger: The custom logger that can be used in this app. @@ -334,6 +335,7 @@

    Classes

    listener_executor = ThreadPoolExecutor(max_workers=5) self._assistant_thread_context_store = assistant_thread_context_store + self._attaching_conversation_kwargs_enabled = attaching_conversation_kwargs_enabled self._process_before_response = process_before_response self._listener_runner = ThreadListenerRunner( @@ -648,7 +650,7 @@

    Classes

    # Pass a function to this method app.middleware(middleware_func) - Refer to https://slack.dev/bolt-python/concepts#global-middleware for details. + Refer to https://docs.slack.dev/tools/bolt-python/concepts/global-middleware for details. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -694,7 +696,7 @@

    Classes

    """ Deprecated: Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://api.slack.com/automation/functions/custom-bolt + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ Registers a new step from app listener. @@ -712,7 +714,7 @@

    Classes

    # Pass Step to set up listeners app.step(ws) - Refer to https://api.slack.com/workflows/steps for details of steps from apps. + Refer to https://docs.slack.dev/legacy/legacy-steps-from-apps/ for details of steps from apps. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -729,7 +731,7 @@

    Classes

    warnings.warn( ( "Steps from apps for legacy workflows are now deprecated. " - "Use new custom steps: https://api.slack.com/automation/functions/custom-bolt" + "Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/" ), category=DeprecationWarning, ) @@ -806,7 +808,7 @@

    Classes

    # Pass a function to this method app.event("team_join")(ask_for_introduction) - Refer to https://api.slack.com/apis/connections/events-api for details of Events API. + Refer to https://docs.slack.dev/apis/events-api/ for details of Events API. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -818,10 +820,13 @@

    Classes

    middleware: A list of lister middleware functions. Only when all the middleware call `next()` method, the listener function can be invoked. """ + middleware = list(middleware) if middleware else [] def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) primary_matcher = builtin_matchers.event(event, base_logger=self._base_logger) + if self._attaching_conversation_kwargs_enabled: + middleware.insert(0, AttachingConversationKwargs(self._assistant_thread_context_store)) return self._register_listener(list(functions), primary_matcher, matchers, middleware, True) return __call__ @@ -844,7 +849,7 @@

    Classes

    # Pass a function to this method app.message(":wave:")(say_hello) - Refer to https://api.slack.com/events/message for details of `message` events. + Refer to https://docs.slack.dev/reference/events/message/ for details of `message` events. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -879,6 +884,8 @@

    Classes

    primary_matcher = builtin_matchers.message_event( keyword=keyword, constraints=constraints, base_logger=self._base_logger ) + if self._attaching_conversation_kwargs_enabled: + middleware.insert(0, AttachingConversationKwargs(self._assistant_thread_context_store)) middleware.insert(0, MessageListenerMatches(keyword)) return self._register_listener(list(functions), primary_matcher, matchers, middleware, True) @@ -890,6 +897,7 @@

    Classes

    matchers: Optional[Sequence[Callable[..., bool]]] = None, middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, auto_acknowledge: bool = True, + ack_timeout: int = 3, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new Function listener. This method can be used as either a decorator or a method. @@ -918,13 +926,17 @@

    Classes

    Only when all the middleware call `next()` method, the listener function can be invoked. """ + if auto_acknowledge is True: + if ack_timeout != 3: + self._framework_logger.warning(warning_ack_timeout_has_no_effect(callback_id, ack_timeout)) + matchers = list(matchers) if matchers else [] middleware = list(middleware) if middleware else [] def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) primary_matcher = builtin_matchers.function_executed(callback_id=callback_id, base_logger=self._base_logger) - return self._register_listener(functions, primary_matcher, matchers, middleware, auto_acknowledge) + return self._register_listener(functions, primary_matcher, matchers, middleware, auto_acknowledge, ack_timeout) return __call__ @@ -950,7 +962,7 @@

    Classes

    # Pass a function to this method app.command("/echo")(repeat_text) - Refer to https://api.slack.com/interactivity/slash-commands for details of Slash Commands. + Refer to https://docs.slack.dev/interactivity/implementing-slash-commands/ for details of Slash Commands. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -997,7 +1009,7 @@

    Classes

    # Pass a function to this method app.shortcut("open_modal")(open_modal) - Refer to https://api.slack.com/interactivity/shortcuts for details about Shortcuts. + Refer to https://docs.slack.dev/interactivity/implementing-shortcuts/ for details about Shortcuts. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -1065,9 +1077,9 @@

    Classes

    # Pass a function to this method app.action("approve_button")(update_message) - * Refer to https://api.slack.com/reference/interaction-payloads/block-actions for actions in `blocks`. - * Refer to https://api.slack.com/legacy/message-buttons for actions in `attachments`. - * Refer to https://api.slack.com/dialogs for actions in dialogs. + * Refer to https://docs.slack.dev/reference/interaction-payloads/block_actions-payload/ for actions in `blocks`. + * Refer to https://docs.slack.dev/legacy/legacy-messaging/legacy-message-buttons/ for actions in `attachments`. + * Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for actions in dialogs. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -1093,7 +1105,7 @@

    Classes

    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new `block_actions` action listener. - Refer to https://api.slack.com/reference/interaction-payloads/block-actions for details. + Refer to https://docs.slack.dev/reference/interaction-payloads/block_actions-payload/ for details. """ def __call__(*args, **kwargs): @@ -1110,7 +1122,7 @@

    Classes

    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new `interactive_message` action listener. - Refer to https://api.slack.com/legacy/message-buttons for details.""" + Refer to https://docs.slack.dev/legacy/legacy-messaging/legacy-message-buttons/ for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1126,7 +1138,7 @@

    Classes

    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new `dialog_submission` listener. - Refer to https://api.slack.com/dialogs for details.""" + Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1142,7 +1154,7 @@

    Classes

    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new `dialog_cancellation` listener. - Refer to https://api.slack.com/dialogs for details.""" + Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1183,7 +1195,7 @@

    Classes

    # Pass a function to this method app.view("view_1")(handle_submission) - Refer to https://api.slack.com/reference/interaction-payloads/views for details of payloads. + Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload for details of payloads. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -1209,7 +1221,9 @@

    Classes

    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new `view_submission` listener. - Refer to https://api.slack.com/reference/interaction-payloads/views#view_submission for details.""" + Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for + details. + """ def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1225,7 +1239,7 @@

    Classes

    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new `view_closed` listener. - Refer to https://api.slack.com/reference/interaction-payloads/views#view_closed for details.""" + Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_closed for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1266,8 +1280,8 @@

    Classes

    Refer to the following documents for details: - * https://api.slack.com/reference/block-kit/block-elements#external_select - * https://api.slack.com/reference/block-kit/block-elements#external_multi_select + * https://docs.slack.dev/reference/block-kit/block-elements/select-menu-element#external_select + * https://docs.slack.dev/reference/block-kit/block-elements/multi-select-menu-element#external_multi_select To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -1307,7 +1321,7 @@

    Classes

    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new `dialog_suggestion` listener. - Refer to https://api.slack.com/dialogs for details.""" + Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1359,6 +1373,7 @@

    Classes

    proxy=self._client.proxy, headers=self._client.headers, team_id=req.context.team_id, + logger=self._client.logger, retry_handlers=self._client.retry_handlers.copy() if self._client.retry_handlers is not None else None, ) req.context["client"] = client_per_request @@ -1367,20 +1382,6 @@

    Classes

    # It is intended for apps that start lazy listeners from their custom global middleware. req.context["listener_runner"] = self.listener_runner - # For AI Agents & Assistants - if is_assistant_event(req.body): - assistant = AssistantUtilities( - payload=to_event(req.body), # type:ignore[arg-type] - context=req.context, - thread_context_store=self._assistant_thread_context_store, - ) - req.context["say"] = assistant.say - req.context["set_status"] = assistant.set_status - req.context["set_title"] = assistant.set_title - req.context["set_suggested_prompts"] = assistant.set_suggested_prompts - req.context["get_thread_context"] = assistant.get_thread_context - req.context["save_thread_context"] = assistant.save_thread_context - @staticmethod def _to_listener_functions( kwargs: dict, @@ -1399,6 +1400,7 @@

    Classes

    matchers: Optional[Sequence[Callable[..., bool]]], middleware: Optional[Sequence[Union[Callable, Middleware]]], auto_acknowledgement: bool = False, + ack_timeout: int = 3, ) -> Optional[Callable[..., Optional[BoltResponse]]]: value_to_return = None if not isinstance(functions, list): @@ -1425,10 +1427,11 @@

    Classes

    CustomListener( app_name=self.name, ack_function=functions.pop(0), - lazy_functions=functions, + lazy_functions=functions, # type: ignore[arg-type] matchers=listener_matchers, middleware=listener_middleware, auto_acknowledgement=auto_acknowledgement, + ack_timeout=ack_timeout, base_logger=self._base_logger, ) ) @@ -1454,9 +1457,9 @@

    Classes

    if __name__ == "__main__": app.start(port=int(os.environ.get("PORT", 3000)))
    -

    Refer to https://slack.dev/bolt-python/tutorial/getting-started for details.

    +

    Refer to https://docs.slack.dev/tools/bolt-python/creating-an-app for details.

    If you would like to build an OAuth app for enabling the app to run with multiple workspaces, -refer to https://slack.dev/bolt-python/concepts#authenticating-oauth to learn how to configure the app.

    +refer to https://docs.slack.dev/tools/bolt-python/concepts/authenticating-oauth to learn how to configure the app.

    Args

    logger
    @@ -1648,9 +1651,9 @@

    Methods

    # Pass a function to this method app.action("approve_button")(update_message) - * Refer to https://api.slack.com/reference/interaction-payloads/block-actions for actions in `blocks`. - * Refer to https://api.slack.com/legacy/message-buttons for actions in `attachments`. - * Refer to https://api.slack.com/dialogs for actions in dialogs. + * Refer to https://docs.slack.dev/reference/interaction-payloads/block_actions-payload/ for actions in `blocks`. + * Refer to https://docs.slack.dev/legacy/legacy-messaging/legacy-message-buttons/ for actions in `attachments`. + * Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for actions in dialogs. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -1679,9 +1682,9 @@

    Methods

    app.action("approve_button")(update_message)

    To learn available arguments for middleware/listeners, see slack_bolt.kwargs_injection.args's API document.

    Args

    @@ -1724,7 +1727,7 @@

    Args

    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new `interactive_message` action listener. - Refer to https://api.slack.com/legacy/message-buttons for details.""" + Refer to https://docs.slack.dev/legacy/legacy-messaging/legacy-message-buttons/ for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1734,7 +1737,7 @@

    Args

    return __call__

    Registers a new interactive_message action listener. -Refer to https://api.slack.com/legacy/message-buttons for details.

    +Refer to https://docs.slack.dev/legacy/legacy-messaging/legacy-message-buttons/ for details.

    def block_action(self,
    constraints: str | Pattern | Dict[str, str | Pattern],
    matchers: Sequence[Callable[..., bool]] | None = None,
    middleware: Sequence[Callable | Middleware] | None = None) ‑> Callable[..., Callable[..., BoltResponse | None] | None]
    @@ -1751,7 +1754,7 @@

    Args

    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new `block_actions` action listener. - Refer to https://api.slack.com/reference/interaction-payloads/block-actions for details. + Refer to https://docs.slack.dev/reference/interaction-payloads/block_actions-payload/ for details. """ def __call__(*args, **kwargs): @@ -1762,7 +1765,7 @@

    Args

    return __call__

    Registers a new block_actions action listener. -Refer to https://api.slack.com/reference/interaction-payloads/block-actions for details.

    +Refer to https://docs.slack.dev/reference/interaction-payloads/block_actions-payload/ for details.

    def block_suggestion(self,
    action_id: str | Pattern,
    matchers: Sequence[Callable[..., bool]] | None = None,
    middleware: Sequence[Callable | Middleware] | None = None) ‑> Callable[..., Callable[..., BoltResponse | None] | None]
    @@ -1816,7 +1819,7 @@

    Args

    # Pass a function to this method app.command("/echo")(repeat_text) - Refer to https://api.slack.com/interactivity/slash-commands for details of Slash Commands. + Refer to https://docs.slack.dev/interactivity/implementing-slash-commands/ for details of Slash Commands. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -1847,7 +1850,7 @@

    Args

    # Pass a function to this method app.command("/echo")(repeat_text)
    -

    Refer to https://api.slack.com/interactivity/slash-commands for details of Slash Commands.

    +

    Refer to https://docs.slack.dev/interactivity/implementing-slash-commands/ for details of Slash Commands.

    To learn available arguments for middleware/listeners, see slack_bolt.kwargs_injection.args's API document.

    Args

    @@ -1910,7 +1913,7 @@

    Args

    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new `dialog_cancellation` listener. - Refer to https://api.slack.com/dialogs for details.""" + Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1920,7 +1923,7 @@

    Args

    return __call__

    Registers a new dialog_cancellation listener. -Refer to https://api.slack.com/dialogs for details.

    +Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.

    def dialog_submission(self,
    callback_id: str | Pattern,
    matchers: Sequence[Callable[..., bool]] | None = None,
    middleware: Sequence[Callable | Middleware] | None = None) ‑> Callable[..., Callable[..., BoltResponse | None] | None]
    @@ -1937,7 +1940,7 @@

    Args

    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new `dialog_submission` listener. - Refer to https://api.slack.com/dialogs for details.""" + Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1947,7 +1950,7 @@

    Args

    return __call__

    Registers a new dialog_submission listener. -Refer to https://api.slack.com/dialogs for details.

    +Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.

    def dialog_suggestion(self,
    callback_id: str | Pattern,
    matchers: Sequence[Callable[..., bool]] | None = None,
    middleware: Sequence[Callable | Middleware] | None = None) ‑> Callable[..., Callable[..., BoltResponse | None] | None]
    @@ -1964,7 +1967,7 @@

    Args

    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new `dialog_suggestion` listener. - Refer to https://api.slack.com/dialogs for details.""" + Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1974,7 +1977,7 @@

    Args

    return __call__

    Registers a new dialog_suggestion listener. -Refer to https://api.slack.com/dialogs for details.

    +Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.

    def dispatch(self,
    req: BoltRequest) ‑> BoltResponse
    @@ -2200,7 +2203,7 @@

    Args

    # Pass a function to this method app.event("team_join")(ask_for_introduction) - Refer to https://api.slack.com/apis/connections/events-api for details of Events API. + Refer to https://docs.slack.dev/apis/events-api/ for details of Events API. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -2212,10 +2215,13 @@

    Args

    middleware: A list of lister middleware functions. Only when all the middleware call `next()` method, the listener function can be invoked. """ + middleware = list(middleware) if middleware else [] def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) primary_matcher = builtin_matchers.event(event, base_logger=self._base_logger) + if self._attaching_conversation_kwargs_enabled: + middleware.insert(0, AttachingConversationKwargs(self._assistant_thread_context_store)) return self._register_listener(list(functions), primary_matcher, matchers, middleware, True) return __call__
    @@ -2232,7 +2238,7 @@

    Args

    # Pass a function to this method app.event("team_join")(ask_for_introduction) -

    Refer to https://api.slack.com/apis/connections/events-api for details of Events API.

    +

    Refer to https://docs.slack.dev/apis/events-api/ for details of Events API.

    To learn available arguments for middleware/listeners, see slack_bolt.kwargs_injection.args's API document.

    Args

    @@ -2248,7 +2254,7 @@

    Args

    -def function(self,
    callback_id: str | Pattern,
    matchers: Sequence[Callable[..., bool]] | None = None,
    middleware: Sequence[Callable | Middleware] | None = None,
    auto_acknowledge: bool = True) ‑> Callable[..., Callable[..., BoltResponse | None] | None]
    +def function(self,
    callback_id: str | Pattern,
    matchers: Sequence[Callable[..., bool]] | None = None,
    middleware: Sequence[Callable | Middleware] | None = None,
    auto_acknowledge: bool = True,
    ack_timeout: int = 3) ‑> Callable[..., Callable[..., BoltResponse | None] | None]
    @@ -2261,6 +2267,7 @@

    Args

    matchers: Optional[Sequence[Callable[..., bool]]] = None, middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, auto_acknowledge: bool = True, + ack_timeout: int = 3, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new Function listener. This method can be used as either a decorator or a method. @@ -2289,13 +2296,17 @@

    Args

    Only when all the middleware call `next()` method, the listener function can be invoked. """ + if auto_acknowledge is True: + if ack_timeout != 3: + self._framework_logger.warning(warning_ack_timeout_has_no_effect(callback_id, ack_timeout)) + matchers = list(matchers) if matchers else [] middleware = list(middleware) if middleware else [] def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) primary_matcher = builtin_matchers.function_executed(callback_id=callback_id, base_logger=self._base_logger) - return self._register_listener(functions, primary_matcher, matchers, middleware, auto_acknowledge) + return self._register_listener(functions, primary_matcher, matchers, middleware, auto_acknowledge, ack_timeout) return __call__
    @@ -2379,7 +2390,7 @@

    Args

    # Pass a function to this method app.message(":wave:")(say_hello) - Refer to https://api.slack.com/events/message for details of `message` events. + Refer to https://docs.slack.dev/reference/events/message/ for details of `message` events. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -2414,6 +2425,8 @@

    Args

    primary_matcher = builtin_matchers.message_event( keyword=keyword, constraints=constraints, base_logger=self._base_logger ) + if self._attaching_conversation_kwargs_enabled: + middleware.insert(0, AttachingConversationKwargs(self._assistant_thread_context_store)) middleware.insert(0, MessageListenerMatches(keyword)) return self._register_listener(list(functions), primary_matcher, matchers, middleware, True) @@ -2430,7 +2443,7 @@

    Args

    # Pass a function to this method app.message(":wave:")(say_hello) -

    Refer to https://api.slack.com/events/message for details of message events.

    +

    Refer to https://docs.slack.dev/reference/events/message/ for details of message events.

    To learn available arguments for middleware/listeners, see slack_bolt.kwargs_injection.args's API document.

    Args

    @@ -2490,7 +2503,7 @@

    Args

    # Pass a function to this method app.middleware(middleware_func) - Refer to https://slack.dev/bolt-python/concepts#global-middleware for details. + Refer to https://docs.slack.dev/tools/bolt-python/concepts/global-middleware for details. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -2528,7 +2541,7 @@

    Args

    # Pass a function to this method app.middleware(middleware_func) -

    Refer to https://slack.dev/bolt-python/concepts#global-middleware for details.

    +

    Refer to https://docs.slack.dev/tools/bolt-python/concepts/global-middleware for details.

    To learn available arguments for middleware/listeners, see slack_bolt.kwargs_injection.args's API document.

    Args

    @@ -2573,8 +2586,8 @@

    Args

    Refer to the following documents for details: - * https://api.slack.com/reference/block-kit/block-elements#external_select - * https://api.slack.com/reference/block-kit/block-elements#external_multi_select + * https://docs.slack.dev/reference/block-kit/block-elements/select-menu-element#external_select + * https://docs.slack.dev/reference/block-kit/block-elements/multi-select-menu-element#external_multi_select To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -2614,8 +2627,8 @@

    Args

    Refer to the following documents for details:

    To learn available arguments for middleware/listeners, see slack_bolt.kwargs_injection.args's API document.

    Args

    @@ -2661,7 +2674,7 @@

    Args

    # Pass a function to this method app.shortcut("open_modal")(open_modal) - Refer to https://api.slack.com/interactivity/shortcuts for details about Shortcuts. + Refer to https://docs.slack.dev/interactivity/implementing-shortcuts/ for details about Shortcuts. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -2698,7 +2711,7 @@

    Args

    # Pass a function to this method app.shortcut("open_modal")(open_modal) -

    Refer to https://api.slack.com/interactivity/shortcuts for details about Shortcuts.

    +

    Refer to https://docs.slack.dev/interactivity/implementing-shortcuts/ for details about Shortcuts.

    To learn available arguments for middleware/listeners, see slack_bolt.kwargs_injection.args's API document.

    Args

    @@ -2784,7 +2797,7 @@

    Args

    """ Deprecated: Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://api.slack.com/automation/functions/custom-bolt + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ Registers a new step from app listener. @@ -2802,7 +2815,7 @@

    Args

    # Pass Step to set up listeners app.step(ws) - Refer to https://api.slack.com/workflows/steps for details of steps from apps. + Refer to https://docs.slack.dev/legacy/legacy-steps-from-apps/ for details of steps from apps. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -2819,7 +2832,7 @@

    Args

    warnings.warn( ( "Steps from apps for legacy workflows are now deprecated. " - "Use new custom steps: https://api.slack.com/automation/functions/custom-bolt" + "Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/" ), category=DeprecationWarning, ) @@ -2841,7 +2854,7 @@

    Args

    Deprecated

    Steps from apps for legacy workflows are now deprecated. -Use new custom steps: https://api.slack.com/automation/functions/custom-bolt

    +Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/

    Registers a new step from app listener.

    Unlike others, this method doesn't behave as a decorator. If you want to register a step from app by a decorator, use WorkflowStepBuilder's methods.

    @@ -2856,7 +2869,7 @@

    Args

    # Pass Step to set up listeners app.step(ws) -

    Refer to https://api.slack.com/workflows/steps for details of steps from apps.

    +

    Refer to https://docs.slack.dev/legacy/legacy-steps-from-apps/ for details of steps from apps.

    To learn available arguments for middleware/listeners, see slack_bolt.kwargs_injection.args's API document.

    For further information about WorkflowStep specific function arguments such as configure, update, complete, and fail, @@ -2927,7 +2940,7 @@

    Args

    # Pass a function to this method app.view("view_1")(handle_submission) - Refer to https://api.slack.com/reference/interaction-payloads/views for details of payloads. + Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload for details of payloads. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -2968,7 +2981,7 @@

    Args

    # Pass a function to this method app.view("view_1")(handle_submission) -

    Refer to https://api.slack.com/reference/interaction-payloads/views for details of payloads.

    +

    Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload for details of payloads.

    To learn available arguments for middleware/listeners, see slack_bolt.kwargs_injection.args's API document.

    Args

    @@ -2997,7 +3010,7 @@

    Args

    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new `view_closed` listener. - Refer to https://api.slack.com/reference/interaction-payloads/views#view_closed for details.""" + Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_closed for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -3007,7 +3020,7 @@

    Args

    return __call__

    Registers a new view_closed listener. -Refer to https://api.slack.com/reference/interaction-payloads/views#view_closed for details.

    +Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_closed for details.

    def view_submission(self,
    constraints: str | Pattern,
    matchers: Sequence[Callable[..., bool]] | None = None,
    middleware: Sequence[Callable | Middleware] | None = None) ‑> Callable[..., Callable[..., BoltResponse | None] | None]
    @@ -3024,7 +3037,9 @@

    Args

    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new `view_submission` listener. - Refer to https://api.slack.com/reference/interaction-payloads/views#view_submission for details.""" + Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for + details. + """ def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -3034,7 +3049,8 @@

    Args

    return __call__

    Registers a new view_submission listener. -Refer to https://api.slack.com/reference/interaction-payloads/views#view_submission for details.

    +Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for +details.

    @@ -3106,7 +3122,7 @@

    App diff --git a/docs/static/api-docs/slack_bolt/async_app.html b/docs/reference/async_app.html similarity index 93% rename from docs/static/api-docs/slack_bolt/async_app.html rename to docs/reference/async_app.html index ef3118433..670ba58d0 100644 --- a/docs/static/api-docs/slack_bolt/async_app.html +++ b/docs/reference/async_app.html @@ -3,7 +3,7 @@ - + slack_bolt.async_app API documentation @@ -39,7 +39,7 @@

    Module slack_bolt.async_app

    Module for creating asyncio based apps

    Creating an async app

    If you'd prefer to build your app with asyncio, you can import the AIOHTTP library and call the AsyncApp constructor. Within async apps, you can use the async/await pattern.

    -
    # Python 3.6+ required
    +
    # Python 3.7+ required
     python -m venv .venv
     source .venv/bin/activate
     
    @@ -139,7 +139,7 @@ 

    Class variables

    class AsyncApp -(*,
    logger: logging.Logger | None = None,
    name: str | None = None,
    process_before_response: bool = False,
    raise_error_for_unhandled_request: bool = False,
    signing_secret: str | None = None,
    token: str | None = None,
    client: slack_sdk.web.async_client.AsyncWebClient | None = None,
    before_authorize: AsyncMiddleware | Callable[..., Awaitable[Any]] | None = None,
    authorize: Callable[..., Awaitable[AuthorizeResult]] | None = None,
    user_facing_authorize_error_message: str | None = None,
    installation_store: slack_sdk.oauth.installation_store.async_installation_store.AsyncInstallationStore | None = None,
    installation_store_bot_only: bool | None = None,
    request_verification_enabled: bool = True,
    ignoring_self_events_enabled: bool = True,
    ignoring_self_assistant_message_events_enabled: bool = True,
    ssl_check_enabled: bool = True,
    url_verification_enabled: bool = True,
    attaching_function_token_enabled: bool = True,
    oauth_settings: AsyncOAuthSettings | None = None,
    oauth_flow: AsyncOAuthFlow | None = None,
    verification_token: str | None = None,
    assistant_thread_context_store: AsyncAssistantThreadContextStore | None = None)
    +(*,
    logger: logging.Logger | None = None,
    name: str | None = None,
    process_before_response: bool = False,
    raise_error_for_unhandled_request: bool = False,
    signing_secret: str | None = None,
    token: str | None = None,
    client: slack_sdk.web.async_client.AsyncWebClient | None = None,
    before_authorize: AsyncMiddleware | Callable[..., Awaitable[Any]] | None = None,
    authorize: Callable[..., Awaitable[AuthorizeResult]] | None = None,
    user_facing_authorize_error_message: str | None = None,
    installation_store: slack_sdk.oauth.installation_store.async_installation_store.AsyncInstallationStore | None = None,
    installation_store_bot_only: bool | None = None,
    request_verification_enabled: bool = True,
    ignoring_self_events_enabled: bool = True,
    ignoring_self_assistant_message_events_enabled: bool = True,
    ssl_check_enabled: bool = True,
    url_verification_enabled: bool = True,
    attaching_function_token_enabled: bool = True,
    oauth_settings: AsyncOAuthSettings | None = None,
    oauth_flow: AsyncOAuthFlow | None = None,
    verification_token: str | None = None,
    assistant_thread_context_store: AsyncAssistantThreadContextStore | None = None,
    attaching_conversation_kwargs_enabled: bool = True)
    @@ -183,6 +183,7 @@

    Class variables

    verification_token: Optional[str] = None, # for AI Agents & Assistants assistant_thread_context_store: Optional[AsyncAssistantThreadContextStore] = None, + attaching_conversation_kwargs_enabled: bool = True, ): """Bolt App that provides functionalities to register middleware/listeners. @@ -205,10 +206,10 @@

    Class variables

    if __name__ == "__main__": app.start(port=int(os.environ.get("PORT", 3000))) - Refer to https://slack.dev/bolt-python/concepts#async for details. + Refer to https://docs.slack.dev/tools/bolt-python/concepts/async for details. If you would like to build an OAuth app for enabling the app to run with multiple workspaces, - refer to https://slack.dev/bolt-python/concepts#authenticating-oauth to learn how to configure the app. + refer to https://docs.slack.dev/tools/bolt-python/concepts/authenticating-oauth to learn how to configure the app. Args: logger: The custom logger that can be used in this app. @@ -403,6 +404,7 @@

    Class variables

    self._async_listeners: List[AsyncListener] = [] self._assistant_thread_context_store = assistant_thread_context_store + self._attaching_conversation_kwargs_enabled = attaching_conversation_kwargs_enabled self._process_before_response = process_before_response self._async_listener_runner = AsyncioListenerRunner( @@ -656,7 +658,7 @@

    Class variables

    self._framework_logger.debug(debug_checking_listener(listener_name)) if await listener.async_matches(req=req, resp=resp): # type: ignore[arg-type] # run all the middleware attached to this listener first - (middleware_resp, next_was_not_called) = await listener.run_async_middleware( + middleware_resp, next_was_not_called = await listener.run_async_middleware( req=req, resp=resp # type: ignore[arg-type] ) if next_was_not_called: @@ -778,7 +780,7 @@

    Class variables

    """ Deprecated: Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://api.slack.com/automation/functions/custom-bolt + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ Registers a new step from app listener. @@ -796,7 +798,7 @@

    Class variables

    # Pass Step to set up listeners app.step(ws) - Refer to https://api.slack.com/workflows/steps for details of steps from apps. + Refer to https://docs.slack.dev/legacy/legacy-steps-from-apps/ for details of steps from apps. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.async_args`'s API document. For further information about AsyncWorkflowStep specific function arguments @@ -812,7 +814,7 @@

    Class variables

    warnings.warn( ( "Steps from apps for legacy workflows are now deprecated. " - "Use new custom steps: https://api.slack.com/automation/functions/custom-bolt" + "Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/" ), category=DeprecationWarning, ) @@ -894,7 +896,7 @@

    Class variables

    # Pass a function to this method app.event("team_join")(ask_for_introduction) - Refer to https://api.slack.com/apis/connections/events-api for details of Events API. + Refer to https://docs.slack.dev/apis/events-api/ for details of Events API. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.async_args`'s API document. @@ -906,10 +908,13 @@

    Class variables

    middleware: A list of lister middleware functions. Only when all the middleware call `next()` method, the listener function can be invoked. """ + middleware = list(middleware) if middleware else [] def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) primary_matcher = builtin_matchers.event(event, True, base_logger=self._base_logger) + if self._attaching_conversation_kwargs_enabled: + middleware.insert(0, AsyncAttachingConversationKwargs(self._assistant_thread_context_store)) return self._register_listener(list(functions), primary_matcher, matchers, middleware, True) return __call__ @@ -932,7 +937,7 @@

    Class variables

    # Pass a function to this method app.message(":wave:")(say_hello) - Refer to https://api.slack.com/events/message for details of `message` events. + Refer to https://docs.slack.dev/reference/events/message/ for details of `message` events. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.async_args`'s API document. @@ -970,6 +975,8 @@

    Class variables

    asyncio=True, base_logger=self._base_logger, ) + if self._attaching_conversation_kwargs_enabled: + middleware.insert(0, AsyncAttachingConversationKwargs(self._assistant_thread_context_store)) middleware.insert(0, AsyncMessageListenerMatches(keyword)) return self._register_listener(list(functions), primary_matcher, matchers, middleware, True) @@ -981,6 +988,7 @@

    Class variables

    matchers: Optional[Sequence[Callable[..., Awaitable[bool]]]] = None, middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None, auto_acknowledge: bool = True, + ack_timeout: int = 3, ) -> Callable[..., Optional[Callable[..., Awaitable[BoltResponse]]]]: """Registers a new Function listener. This method can be used as either a decorator or a method. @@ -1008,6 +1016,9 @@

    Class variables

    middleware: A list of lister middleware functions. Only when all the middleware call `next()` method, the listener function can be invoked. """ + if auto_acknowledge is True: + if ack_timeout != 3: + self._framework_logger.warning(warning_ack_timeout_has_no_effect(callback_id, ack_timeout)) matchers = list(matchers) if matchers else [] middleware = list(middleware) if middleware else [] @@ -1017,7 +1028,7 @@

    Class variables

    primary_matcher = builtin_matchers.function_executed( callback_id=callback_id, base_logger=self._base_logger, asyncio=True ) - return self._register_listener(functions, primary_matcher, matchers, middleware, auto_acknowledge) + return self._register_listener(functions, primary_matcher, matchers, middleware, auto_acknowledge, ack_timeout) return __call__ @@ -1043,7 +1054,7 @@

    Class variables

    # Pass a function to this method app.command("/echo")(repeat_text) - Refer to https://api.slack.com/interactivity/slash-commands for details of Slash Commands. + Refer to https://docs.slack.dev/interactivity/implementing-slash-commands/ for details of Slash Commands. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.async_args`'s API document. @@ -1090,7 +1101,7 @@

    Class variables

    # Pass a function to this method app.shortcut("open_modal")(open_modal) - Refer to https://api.slack.com/interactivity/shortcuts for details about Shortcuts. + Refer to https://docs.slack.dev/interactivity/implementing-shortcuts/ for details about Shortcuts. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.async_args`'s API document. @@ -1158,9 +1169,9 @@

    Class variables

    # Pass a function to this method app.action("approve_button")(update_message) - * Refer to https://api.slack.com/reference/interaction-payloads/block-actions for actions in `blocks`. - * Refer to https://api.slack.com/legacy/message-buttons for actions in `attachments`. - * Refer to https://api.slack.com/dialogs for actions in dialogs. + * Refer to https://docs.slack.dev/reference/interaction-payloads/block_actions-payload/ for actions in `blocks`. + * Refer to https://docs.slack.dev/legacy/legacy-messaging/legacy-message-buttons/ for actions in `attachments`. + * Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for actions in dialogs. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.async_args`'s API document. @@ -1186,7 +1197,7 @@

    Class variables

    middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None, ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]: """Registers a new `block_actions` action listener. - Refer to https://api.slack.com/reference/interaction-payloads/block-actions for details. + Refer to https://docs.slack.dev/reference/interaction-payloads/block_actions-payload/ for details. """ def __call__(*args, **kwargs): @@ -1203,7 +1214,7 @@

    Class variables

    middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None, ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]: """Registers a new `interactive_message` action listener. - Refer to https://api.slack.com/legacy/message-buttons for details.""" + Refer to https://docs.slack.dev/legacy/legacy-messaging/legacy-message-buttons/ for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1219,7 +1230,7 @@

    Class variables

    middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None, ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]: """Registers a new `dialog_submission` listener. - Refer to https://api.slack.com/dialogs for details.""" + Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1235,7 +1246,7 @@

    Class variables

    middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None, ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]: """Registers a new `dialog_submission` listener. - Refer to https://api.slack.com/dialogs for details.""" + Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1276,7 +1287,7 @@

    Class variables

    # Pass a function to this method app.view("view_1")(handle_submission) - Refer to https://api.slack.com/reference/interaction-payloads/views for details of payloads. + Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload for details of payloads. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.async_args`'s API document. @@ -1302,7 +1313,9 @@

    Class variables

    middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None, ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]: """Registers a new `view_submission` listener. - Refer to https://api.slack.com/reference/interaction-payloads/views#view_submission for details.""" + Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for + details. + """ def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1318,7 +1331,7 @@

    Class variables

    middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None, ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]: """Registers a new `view_closed` listener. - Refer to https://api.slack.com/reference/interaction-payloads/views#view_closed for details.""" + Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_closed for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1359,8 +1372,8 @@

    Class variables

    Refer to the following documents for details: - * https://api.slack.com/reference/block-kit/block-elements#external_select - * https://api.slack.com/reference/block-kit/block-elements#external_multi_select + * https://docs.slack.dev/reference/block-kit/block-elements/select-menu-element#external_select + * https://docs.slack.dev/reference/block-kit/block-elements/multi-select-menu-element#external_multi_select To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.async_args`'s API document. @@ -1400,7 +1413,7 @@

    Class variables

    middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None, ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]: """Registers a new `dialog_suggestion` listener. - Refer to https://api.slack.com/dialogs for details.""" + Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1454,6 +1467,7 @@

    Class variables

    trust_env_in_session=self._async_client.trust_env_in_session, headers=self._async_client.headers, team_id=req.context.team_id, + logger=self._async_client.logger, retry_handlers=( self._async_client.retry_handlers.copy() if self._async_client.retry_handlers is not None else None ), @@ -1464,20 +1478,6 @@

    Class variables

    # It is intended for apps that start lazy listeners from their custom global middleware. req.context["listener_runner"] = self.listener_runner - # For AI Agents & Assistants - if is_assistant_event(req.body): - assistant = AsyncAssistantUtilities( - payload=to_event(req.body), # type:ignore[arg-type] - context=req.context, - thread_context_store=self._assistant_thread_context_store, - ) - req.context["say"] = assistant.say - req.context["set_status"] = assistant.set_status - req.context["set_title"] = assistant.set_title - req.context["set_suggested_prompts"] = assistant.set_suggested_prompts - req.context["get_thread_context"] = assistant.get_thread_context - req.context["save_thread_context"] = assistant.save_thread_context - @staticmethod def _to_listener_functions( kwargs: dict, @@ -1496,6 +1496,7 @@

    Class variables

    matchers: Optional[Sequence[Callable[..., Awaitable[bool]]]], middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]], auto_acknowledgement: bool = False, + ack_timeout: int = 3, ) -> Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]: value_to_return = None if not isinstance(functions, list): @@ -1527,10 +1528,11 @@

    Class variables

    AsyncCustomListener( app_name=self.name, ack_function=functions.pop(0), - lazy_functions=functions, + lazy_functions=functions, # type: ignore[arg-type] matchers=listener_matchers, middleware=listener_middleware, auto_acknowledgement=auto_acknowledgement, + ack_timeout=ack_timeout, base_logger=self._base_logger, ) ) @@ -1557,9 +1559,9 @@

    Class variables

    if __name__ == "__main__": app.start(port=int(os.environ.get("PORT", 3000)))
    -

    Refer to https://slack.dev/bolt-python/concepts#async for details.

    +

    Refer to https://docs.slack.dev/tools/bolt-python/concepts/async for details.

    If you would like to build an OAuth app for enabling the app to run with multiple workspaces, -refer to https://slack.dev/bolt-python/concepts#authenticating-oauth to learn how to configure the app.

    +refer to https://docs.slack.dev/tools/bolt-python/concepts/authenticating-oauth to learn how to configure the app.

    Args

    logger
    @@ -1753,9 +1755,9 @@

    Methods

    # Pass a function to this method app.action("approve_button")(update_message) - * Refer to https://api.slack.com/reference/interaction-payloads/block-actions for actions in `blocks`. - * Refer to https://api.slack.com/legacy/message-buttons for actions in `attachments`. - * Refer to https://api.slack.com/dialogs for actions in dialogs. + * Refer to https://docs.slack.dev/reference/interaction-payloads/block_actions-payload/ for actions in `blocks`. + * Refer to https://docs.slack.dev/legacy/legacy-messaging/legacy-message-buttons/ for actions in `attachments`. + * Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for actions in dialogs. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.async_args`'s API document. @@ -1784,9 +1786,9 @@

    Methods

    app.action("approve_button")(update_message)

    To learn available arguments for middleware/listeners, see slack_bolt.kwargs_injection.async_args's API document.

    Args

    @@ -1876,7 +1878,7 @@

    Args

    self._framework_logger.debug(debug_checking_listener(listener_name)) if await listener.async_matches(req=req, resp=resp): # type: ignore[arg-type] # run all the middleware attached to this listener first - (middleware_resp, next_was_not_called) = await listener.run_async_middleware( + middleware_resp, next_was_not_called = await listener.run_async_middleware( req=req, resp=resp # type: ignore[arg-type] ) if next_was_not_called: @@ -1957,7 +1959,7 @@

    Returns

    middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None, ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]: """Registers a new `interactive_message` action listener. - Refer to https://api.slack.com/legacy/message-buttons for details.""" + Refer to https://docs.slack.dev/legacy/legacy-messaging/legacy-message-buttons/ for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1967,7 +1969,7 @@

    Returns

    return __call__

    Registers a new interactive_message action listener. -Refer to https://api.slack.com/legacy/message-buttons for details.

    +Refer to https://docs.slack.dev/legacy/legacy-messaging/legacy-message-buttons/ for details.

    def block_action(self,
    constraints: str | Pattern | Dict[str, str | Pattern],
    matchers: Sequence[Callable[..., Awaitable[bool]]] | None = None,
    middleware: Sequence[Callable | AsyncMiddleware] | None = None) ‑> Callable[..., Callable[..., Awaitable[BoltResponse | None]] | None]
    @@ -1984,7 +1986,7 @@

    Returns

    middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None, ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]: """Registers a new `block_actions` action listener. - Refer to https://api.slack.com/reference/interaction-payloads/block-actions for details. + Refer to https://docs.slack.dev/reference/interaction-payloads/block_actions-payload/ for details. """ def __call__(*args, **kwargs): @@ -1995,7 +1997,7 @@

    Returns

    return __call__

    Registers a new block_actions action listener. -Refer to https://api.slack.com/reference/interaction-payloads/block-actions for details.

    +Refer to https://docs.slack.dev/reference/interaction-payloads/block_actions-payload/ for details.

    def block_suggestion(self,
    action_id: str | Pattern,
    matchers: Sequence[Callable[..., Awaitable[bool]]] | None = None,
    middleware: Sequence[Callable | AsyncMiddleware] | None = None) ‑> Callable[..., Callable[..., Awaitable[BoltResponse | None]] | None]
    @@ -2049,7 +2051,7 @@

    Returns

    # Pass a function to this method app.command("/echo")(repeat_text) - Refer to https://api.slack.com/interactivity/slash-commands for details of Slash Commands. + Refer to https://docs.slack.dev/interactivity/implementing-slash-commands/ for details of Slash Commands. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.async_args`'s API document. @@ -2080,7 +2082,7 @@

    Returns

    # Pass a function to this method app.command("/echo")(repeat_text)
    -

    Refer to https://api.slack.com/interactivity/slash-commands for details of Slash Commands.

    +

    Refer to https://docs.slack.dev/interactivity/implementing-slash-commands/ for details of Slash Commands.

    To learn available arguments for middleware/listeners, see slack_bolt.kwargs_injection.async_args's API document.

    Args

    @@ -2143,7 +2145,7 @@

    Args

    middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None, ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]: """Registers a new `dialog_submission` listener. - Refer to https://api.slack.com/dialogs for details.""" + Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -2153,7 +2155,7 @@

    Args

    return __call__

    Registers a new dialog_submission listener. -Refer to https://api.slack.com/dialogs for details.

    +Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.

    def dialog_submission(self,
    callback_id: str | Pattern,
    matchers: Sequence[Callable[..., Awaitable[bool]]] | None = None,
    middleware: Sequence[Callable | AsyncMiddleware] | None = None) ‑> Callable[..., Callable[..., Awaitable[BoltResponse | None]] | None]
    @@ -2170,7 +2172,7 @@

    Args

    middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None, ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]: """Registers a new `dialog_submission` listener. - Refer to https://api.slack.com/dialogs for details.""" + Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -2180,7 +2182,7 @@

    Args

    return __call__

    Registers a new dialog_submission listener. -Refer to https://api.slack.com/dialogs for details.

    +Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.

    def dialog_suggestion(self,
    callback_id: str | Pattern,
    matchers: Sequence[Callable[..., Awaitable[bool]]] | None = None,
    middleware: Sequence[Callable | AsyncMiddleware] | None = None) ‑> Callable[..., Callable[..., Awaitable[BoltResponse | None]] | None]
    @@ -2197,7 +2199,7 @@

    Args

    middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None, ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]: """Registers a new `dialog_suggestion` listener. - Refer to https://api.slack.com/dialogs for details.""" + Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -2207,7 +2209,7 @@

    Args

    return __call__

    Registers a new dialog_suggestion listener. -Refer to https://api.slack.com/dialogs for details.

    +Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.

    def enable_token_revocation_listeners(self) ‑> None @@ -2313,7 +2315,7 @@

    Args

    # Pass a function to this method app.event("team_join")(ask_for_introduction) - Refer to https://api.slack.com/apis/connections/events-api for details of Events API. + Refer to https://docs.slack.dev/apis/events-api/ for details of Events API. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.async_args`'s API document. @@ -2325,10 +2327,13 @@

    Args

    middleware: A list of lister middleware functions. Only when all the middleware call `next()` method, the listener function can be invoked. """ + middleware = list(middleware) if middleware else [] def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) primary_matcher = builtin_matchers.event(event, True, base_logger=self._base_logger) + if self._attaching_conversation_kwargs_enabled: + middleware.insert(0, AsyncAttachingConversationKwargs(self._assistant_thread_context_store)) return self._register_listener(list(functions), primary_matcher, matchers, middleware, True) return __call__
    @@ -2345,7 +2350,7 @@

    Args

    # Pass a function to this method app.event("team_join")(ask_for_introduction) -

    Refer to https://api.slack.com/apis/connections/events-api for details of Events API.

    +

    Refer to https://docs.slack.dev/apis/events-api/ for details of Events API.

    To learn available arguments for middleware/listeners, see slack_bolt.kwargs_injection.async_args's API document.

    Args

    @@ -2361,7 +2366,7 @@

    Args

    -def function(self,
    callback_id: str | Pattern,
    matchers: Sequence[Callable[..., Awaitable[bool]]] | None = None,
    middleware: Sequence[Callable | AsyncMiddleware] | None = None,
    auto_acknowledge: bool = True) ‑> Callable[..., Callable[..., Awaitable[BoltResponse]] | None]
    +def function(self,
    callback_id: str | Pattern,
    matchers: Sequence[Callable[..., Awaitable[bool]]] | None = None,
    middleware: Sequence[Callable | AsyncMiddleware] | None = None,
    auto_acknowledge: bool = True,
    ack_timeout: int = 3) ‑> Callable[..., Callable[..., Awaitable[BoltResponse]] | None]
    @@ -2374,6 +2379,7 @@

    Args

    matchers: Optional[Sequence[Callable[..., Awaitable[bool]]]] = None, middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None, auto_acknowledge: bool = True, + ack_timeout: int = 3, ) -> Callable[..., Optional[Callable[..., Awaitable[BoltResponse]]]]: """Registers a new Function listener. This method can be used as either a decorator or a method. @@ -2401,6 +2407,9 @@

    Args

    middleware: A list of lister middleware functions. Only when all the middleware call `next()` method, the listener function can be invoked. """ + if auto_acknowledge is True: + if ack_timeout != 3: + self._framework_logger.warning(warning_ack_timeout_has_no_effect(callback_id, ack_timeout)) matchers = list(matchers) if matchers else [] middleware = list(middleware) if middleware else [] @@ -2410,7 +2419,7 @@

    Args

    primary_matcher = builtin_matchers.function_executed( callback_id=callback_id, base_logger=self._base_logger, asyncio=True ) - return self._register_listener(functions, primary_matcher, matchers, middleware, auto_acknowledge) + return self._register_listener(functions, primary_matcher, matchers, middleware, auto_acknowledge, ack_timeout) return __call__
    @@ -2494,7 +2503,7 @@

    Args

    # Pass a function to this method app.message(":wave:")(say_hello) - Refer to https://api.slack.com/events/message for details of `message` events. + Refer to https://docs.slack.dev/reference/events/message/ for details of `message` events. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.async_args`'s API document. @@ -2532,6 +2541,8 @@

    Args

    asyncio=True, base_logger=self._base_logger, ) + if self._attaching_conversation_kwargs_enabled: + middleware.insert(0, AsyncAttachingConversationKwargs(self._assistant_thread_context_store)) middleware.insert(0, AsyncMessageListenerMatches(keyword)) return self._register_listener(list(functions), primary_matcher, matchers, middleware, True) @@ -2548,7 +2559,7 @@

    Args

    # Pass a function to this method app.message(":wave:")(say_hello) -

    Refer to https://api.slack.com/events/message for details of message events.

    +

    Refer to https://docs.slack.dev/reference/events/message/ for details of message events.

    To learn available arguments for middleware/listeners, see slack_bolt.kwargs_injection.async_args's API document.

    Args

    @@ -2688,8 +2699,8 @@

    Args

    Refer to the following documents for details: - * https://api.slack.com/reference/block-kit/block-elements#external_select - * https://api.slack.com/reference/block-kit/block-elements#external_multi_select + * https://docs.slack.dev/reference/block-kit/block-elements/select-menu-element#external_select + * https://docs.slack.dev/reference/block-kit/block-elements/multi-select-menu-element#external_multi_select To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.async_args`'s API document. @@ -2729,8 +2740,8 @@

    Args

    Refer to the following documents for details:

    To learn available arguments for middleware/listeners, see slack_bolt.kwargs_injection.async_args's API document.

    Args

    @@ -2819,7 +2830,7 @@

    Args

    # Pass a function to this method app.shortcut("open_modal")(open_modal) - Refer to https://api.slack.com/interactivity/shortcuts for details about Shortcuts. + Refer to https://docs.slack.dev/interactivity/implementing-shortcuts/ for details about Shortcuts. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.async_args`'s API document. @@ -2856,7 +2867,7 @@

    Args

    # Pass a function to this method app.shortcut("open_modal")(open_modal) -

    Refer to https://api.slack.com/interactivity/shortcuts for details about Shortcuts.

    +

    Refer to https://docs.slack.dev/interactivity/implementing-shortcuts/ for details about Shortcuts.

    To learn available arguments for middleware/listeners, see slack_bolt.kwargs_injection.async_args's API document.

    Args

    @@ -2919,7 +2930,7 @@

    Args

    """ Deprecated: Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://api.slack.com/automation/functions/custom-bolt + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ Registers a new step from app listener. @@ -2937,7 +2948,7 @@

    Args

    # Pass Step to set up listeners app.step(ws) - Refer to https://api.slack.com/workflows/steps for details of steps from apps. + Refer to https://docs.slack.dev/legacy/legacy-steps-from-apps/ for details of steps from apps. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.async_args`'s API document. For further information about AsyncWorkflowStep specific function arguments @@ -2953,7 +2964,7 @@

    Args

    warnings.warn( ( "Steps from apps for legacy workflows are now deprecated. " - "Use new custom steps: https://api.slack.com/automation/functions/custom-bolt" + "Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/" ), category=DeprecationWarning, ) @@ -2975,7 +2986,7 @@

    Args

    Deprecated

    Steps from apps for legacy workflows are now deprecated. -Use new custom steps: https://api.slack.com/automation/functions/custom-bolt

    +Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/

    Registers a new step from app listener.

    Unlike others, this method doesn't behave as a decorator. If you want to register a step from app by a decorator, use AsyncWorkflowStepBuilder's methods.

    @@ -2990,7 +3001,7 @@

    Args

    # Pass Step to set up listeners app.step(ws) -

    Refer to https://api.slack.com/workflows/steps for details of steps from apps.

    +

    Refer to https://docs.slack.dev/legacy/legacy-steps-from-apps/ for details of steps from apps.

    To learn available arguments for middleware/listeners, see slack_bolt.kwargs_injection.async_args's API document. For further information about AsyncWorkflowStep specific function arguments such as configure, update, complete, and fail, @@ -3058,7 +3069,7 @@

    Args

    # Pass a function to this method app.view("view_1")(handle_submission) - Refer to https://api.slack.com/reference/interaction-payloads/views for details of payloads. + Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload for details of payloads. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.async_args`'s API document. @@ -3099,7 +3110,7 @@

    Args

    # Pass a function to this method app.view("view_1")(handle_submission) -

    Refer to https://api.slack.com/reference/interaction-payloads/views for details of payloads.

    +

    Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload for details of payloads.

    To learn available arguments for middleware/listeners, see slack_bolt.kwargs_injection.async_args's API document.

    Args

    @@ -3128,7 +3139,7 @@

    Args

    middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None, ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]: """Registers a new `view_closed` listener. - Refer to https://api.slack.com/reference/interaction-payloads/views#view_closed for details.""" + Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_closed for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -3138,7 +3149,7 @@

    Args

    return __call__

    Registers a new view_closed listener. -Refer to https://api.slack.com/reference/interaction-payloads/views#view_closed for details.

    +Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_closed for details.

    def view_submission(self,
    constraints: str | Pattern,
    matchers: Sequence[Callable[..., Awaitable[bool]]] | None = None,
    middleware: Sequence[Callable | AsyncMiddleware] | None = None) ‑> Callable[..., Callable[..., Awaitable[BoltResponse | None]] | None]
    @@ -3155,7 +3166,9 @@

    Args

    middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None, ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]: """Registers a new `view_submission` listener. - Refer to https://api.slack.com/reference/interaction-payloads/views#view_submission for details.""" + Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for + details. + """ def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -3165,7 +3178,8 @@

    Args

    return __call__

    Registers a new view_submission listener. -Refer to https://api.slack.com/reference/interaction-payloads/views#view_submission for details.

    +Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for +details.

    def web_app(self, path: str = '/slack/events', port: int = 3000) ‑> aiohttp.web_app.Application @@ -3269,7 +3283,7 @@

    Args

    func=is_assistant_thread_started_event, asyncio=True, base_logger=self.base_logger, - ), # type:ignore[arg-type] + ), # type: ignore[arg-type] matchers, ) if is_used_without_argument(args): @@ -3278,7 +3292,7 @@

    Args

    self.build_listener( listener_or_functions=func, matchers=all_matchers, - middleware=middleware, # type:ignore[arg-type] + middleware=middleware, # type: ignore[arg-type] ) ) return func @@ -3315,7 +3329,7 @@

    Args

    func=is_user_message_event_in_assistant_thread, asyncio=True, base_logger=self.base_logger, - ), # type:ignore[arg-type] + ), # type: ignore[arg-type] matchers, ) if is_used_without_argument(args): @@ -3324,7 +3338,7 @@

    Args

    self.build_listener( listener_or_functions=func, matchers=all_matchers, - middleware=middleware, # type:ignore[arg-type] + middleware=middleware, # type: ignore[arg-type] ) ) return func @@ -3361,7 +3375,7 @@

    Args

    func=is_bot_message_event_in_assistant_thread, asyncio=True, base_logger=self.base_logger, - ), # type:ignore[arg-type] + ), # type: ignore[arg-type] matchers, ) if is_used_without_argument(args): @@ -3370,7 +3384,7 @@

    Args

    self.build_listener( listener_or_functions=func, matchers=all_matchers, - middleware=middleware, # type:ignore[arg-type] + middleware=middleware, # type: ignore[arg-type] ) ) return func @@ -3407,7 +3421,7 @@

    Args

    func=is_assistant_thread_context_changed_event, asyncio=True, base_logger=self.base_logger, - ), # type:ignore[arg-type] + ), # type: ignore[arg-type] matchers, ) if is_used_without_argument(args): @@ -3416,7 +3430,7 @@

    Args

    self.build_listener( listener_or_functions=func, matchers=all_matchers, - middleware=middleware, # type:ignore[arg-type] + middleware=middleware, # type: ignore[arg-type] ) ) return func @@ -3444,14 +3458,14 @@

    Args

    primary_matcher: Union[Callable[..., bool], AsyncListenerMatcher], custom_matchers: Optional[Union[Callable[..., bool], AsyncListenerMatcher]], ): - return [primary_matcher] + (custom_matchers or []) # type:ignore[operator] + return [primary_matcher] + (custom_matchers or []) # type: ignore[operator] @staticmethod async def default_thread_context_changed(save_thread_context: AsyncSaveThreadContext, payload: dict): new_context: dict = payload["assistant_thread"]["context"] await save_thread_context(new_context) - async def async_process( # type:ignore[return] + async def async_process( # type: ignore[return] self, *, req: AsyncBoltRequest, @@ -3471,6 +3485,15 @@

    Args

    if listeners is not None: for listener in listeners: if listener is not None and await listener.async_matches(req=req, resp=resp): + middleware_resp, next_was_not_called = await listener.run_async_middleware(req=req, resp=resp) + if next_was_not_called: + if middleware_resp is not None: + return middleware_resp + # The listener middleware didn't call next(). + # Skip this listener and try the next one. + continue + if middleware_resp is not None: + resp = middleware_resp return await listener_runner.run( request=req, response=resp, @@ -3490,13 +3513,14 @@

    Args

    middleware: Optional[List[AsyncMiddleware]] = None, base_logger: Optional[Logger] = None, ) -> AsyncListener: - if isinstance(listener_or_functions, Callable): # type:ignore[arg-type] - listener_or_functions = [listener_or_functions] # type:ignore[list-item] + if isinstance(listener_or_functions, Callable): # type: ignore[arg-type] + listener_or_functions = [listener_or_functions] # type: ignore[list-item] if isinstance(listener_or_functions, AsyncListener): return listener_or_functions elif isinstance(listener_or_functions, list): middleware = middleware if middleware else [] + middleware.insert(0, AsyncAttachingConversationKwargs(self.thread_context_store)) functions = listener_or_functions ack_function = functions.pop(0) @@ -3508,7 +3532,7 @@

    Args

    else: listener_matchers.append( build_listener_matcher( - func=matcher, # type:ignore[arg-type] + func=matcher, # type: ignore[arg-type] asyncio=True, base_logger=base_logger, ) @@ -3583,7 +3607,7 @@

    Methods

    func=is_bot_message_event_in_assistant_thread, asyncio=True, base_logger=self.base_logger, - ), # type:ignore[arg-type] + ), # type: ignore[arg-type] matchers, ) if is_used_without_argument(args): @@ -3592,7 +3616,7 @@

    Methods

    self.build_listener( listener_or_functions=func, matchers=all_matchers, - middleware=middleware, # type:ignore[arg-type] + middleware=middleware, # type: ignore[arg-type] ) ) return func @@ -3632,13 +3656,14 @@

    Methods

    middleware: Optional[List[AsyncMiddleware]] = None, base_logger: Optional[Logger] = None, ) -> AsyncListener: - if isinstance(listener_or_functions, Callable): # type:ignore[arg-type] - listener_or_functions = [listener_or_functions] # type:ignore[list-item] + if isinstance(listener_or_functions, Callable): # type: ignore[arg-type] + listener_or_functions = [listener_or_functions] # type: ignore[list-item] if isinstance(listener_or_functions, AsyncListener): return listener_or_functions elif isinstance(listener_or_functions, list): middleware = middleware if middleware else [] + middleware.insert(0, AsyncAttachingConversationKwargs(self.thread_context_store)) functions = listener_or_functions ack_function = functions.pop(0) @@ -3650,7 +3675,7 @@

    Methods

    else: listener_matchers.append( build_listener_matcher( - func=matcher, # type:ignore[arg-type] + func=matcher, # type: ignore[arg-type] asyncio=True, base_logger=base_logger, ) @@ -3691,7 +3716,7 @@

    Methods

    func=is_assistant_thread_context_changed_event, asyncio=True, base_logger=self.base_logger, - ), # type:ignore[arg-type] + ), # type: ignore[arg-type] matchers, ) if is_used_without_argument(args): @@ -3700,7 +3725,7 @@

    Methods

    self.build_listener( listener_or_functions=func, matchers=all_matchers, - middleware=middleware, # type:ignore[arg-type] + middleware=middleware, # type: ignore[arg-type] ) ) return func @@ -3747,7 +3772,7 @@

    Methods

    func=is_assistant_thread_started_event, asyncio=True, base_logger=self.base_logger, - ), # type:ignore[arg-type] + ), # type: ignore[arg-type] matchers, ) if is_used_without_argument(args): @@ -3756,7 +3781,7 @@

    Methods

    self.build_listener( listener_or_functions=func, matchers=all_matchers, - middleware=middleware, # type:ignore[arg-type] + middleware=middleware, # type: ignore[arg-type] ) ) return func @@ -3803,7 +3828,7 @@

    Methods

    func=is_user_message_event_in_assistant_thread, asyncio=True, base_logger=self.base_logger, - ), # type:ignore[arg-type] + ), # type: ignore[arg-type] matchers, ) if is_used_without_argument(args): @@ -3812,7 +3837,7 @@

    Methods

    self.build_listener( listener_or_functions=func, matchers=all_matchers, - middleware=middleware, # type:ignore[arg-type] + middleware=middleware, # type: ignore[arg-type] ) ) return func @@ -3882,7 +3907,7 @@

    Inherited members

    # The return type is intentionally string to avoid circular imports @property - def listener_runner(self) -> "AsyncioListenerRunner": # type: ignore[name-defined] + def listener_runner(self) -> "AsyncioListenerRunner": """The properly configured listener_runner that is available for middleware/listeners.""" return self["listener_runner"] @@ -3951,7 +3976,7 @@

    Inherited members

    Callable `say()` function """ if "say" not in self: - self["say"] = AsyncSay(client=self.client, channel=self.channel_id, thread_ts=self.thread_ts) + self["say"] = AsyncSay(client=self.client, channel=self.channel_id) return self["say"] @property @@ -4044,6 +4069,10 @@

    Inherited members

    def get_thread_context(self) -> Optional[AsyncGetThreadContext]: return self.get("get_thread_context") + @property + def say_stream(self) -> Optional[AsyncSayStream]: + return self.get("say_stream") + @property def save_thread_context(self) -> Optional[AsyncSaveThreadContext]: return self.get("save_thread_context")
    @@ -4259,7 +4288,7 @@

    Returns

    Expand source code
    @property
    -def listener_runner(self) -> "AsyncioListenerRunner":  # type: ignore[name-defined]
    +def listener_runner(self) -> "AsyncioListenerRunner":
         """The properly configured listener_runner that is available for middleware/listeners."""
         return self["listener_runner"]
    @@ -4349,7 +4378,7 @@

    Returns

    Callable `say()` function """ if "say" not in self: - self["say"] = AsyncSay(client=self.client, channel=self.channel_id, thread_ts=self.thread_ts) + self["say"] = AsyncSay(client=self.client, channel=self.channel_id) return self["say"]

    say() function for this request.

    @@ -4367,6 +4396,18 @@

    Returns

    Returns

    Callable say() function

    +
    prop say_streamAsyncSayStream | None
    +
    +
    + +Expand source code + +
    @property
    +def say_stream(self) -> Optional[AsyncSayStream]:
    +    return self.get("say_stream")
    +
    +
    +
    prop set_statusAsyncSetStatus | None
    @@ -4726,14 +4767,10 @@

    Inherited members

    if self.thread_context_loaded is True: return self._thread_context - if self.payload.get("assistant_thread") is not None: + thread = self.payload.get("assistant_thread") + if isinstance(thread, dict) and thread.get("context", {}).get("channel_id") is not None: # assistant_thread_started - thread = self.payload["assistant_thread"] - self._thread_context = ( - AssistantThreadContext(thread["context"]) - if thread.get("context", {}).get("channel_id") is not None - else None - ) + self._thread_context = AssistantThreadContext(thread["context"]) # for this event, the context will never be changed self.thread_context_loaded = True elif self.payload.get("channel") is not None and self.payload.get("thread_ts") is not None: @@ -4781,6 +4818,7 @@

    Class variables

    ack_function: Callable[..., Awaitable[BoltResponse]] lazy_functions: Sequence[Callable[..., Awaitable[None]]] auto_acknowledgement: bool + ack_timeout: int async def async_matches( self, @@ -4846,6 +4884,10 @@

    Class variables

    The type of the None singleton.

    +
    var ack_timeout : int
    +
    +

    The type of the None singleton.

    +
    var auto_acknowledgement : bool

    The type of the None singleton.

    @@ -5142,6 +5184,7 @@

    Class variables

    icon_emoji: Optional[str] = None, icon_url: Optional[str] = None, username: Optional[str] = None, + markdown_text: Optional[str] = None, mrkdwn: Optional[bool] = None, link_names: Optional[bool] = None, parse: Optional[str] = None, # none, full @@ -5167,6 +5210,7 @@

    Class variables

    icon_emoji=icon_emoji, icon_url=icon_url, username=username, + markdown_text=markdown_text, mrkdwn=mrkdwn, link_names=link_names, parse=parse, @@ -5208,6 +5252,106 @@

    Class variables

    +
    +class AsyncSayStream +(*,
    client: slack_sdk.web.async_client.AsyncWebClient,
    channel: str | None = None,
    recipient_team_id: str | None = None,
    recipient_user_id: str | None = None,
    thread_ts: str | None = None)
    +
    +
    +
    + +Expand source code + +
    class AsyncSayStream:
    +    client: AsyncWebClient
    +    channel: Optional[str]
    +    recipient_team_id: Optional[str]
    +    recipient_user_id: Optional[str]
    +    thread_ts: Optional[str]
    +
    +    def __init__(
    +        self,
    +        *,
    +        client: AsyncWebClient,
    +        channel: Optional[str] = None,
    +        recipient_team_id: Optional[str] = None,
    +        recipient_user_id: Optional[str] = None,
    +        thread_ts: Optional[str] = None,
    +    ):
    +        self.client = client
    +        self.channel = channel
    +        self.recipient_team_id = recipient_team_id
    +        self.recipient_user_id = recipient_user_id
    +        self.thread_ts = thread_ts
    +
    +    async def __call__(
    +        self,
    +        *,
    +        buffer_size: Optional[int] = None,
    +        channel: Optional[str] = None,
    +        recipient_team_id: Optional[str] = None,
    +        recipient_user_id: Optional[str] = None,
    +        thread_ts: Optional[str] = None,
    +        icon_emoji: Optional[str] = None,
    +        icon_url: Optional[str] = None,
    +        username: Optional[str] = None,
    +        **kwargs,
    +    ) -> AsyncChatStream:
    +        """Starts a new chat stream with context."""
    +        channel = channel or self.channel
    +        thread_ts = thread_ts or self.thread_ts
    +        if channel is None:
    +            raise ValueError("say_stream without channel here is unsupported")
    +        if thread_ts is None:
    +            raise ValueError("say_stream without thread_ts here is unsupported")
    +
    +        if buffer_size is not None:
    +            return await self.client.chat_stream(
    +                buffer_size=buffer_size,
    +                channel=channel,
    +                recipient_team_id=recipient_team_id or self.recipient_team_id,
    +                recipient_user_id=recipient_user_id or self.recipient_user_id,
    +                thread_ts=thread_ts,
    +                icon_emoji=icon_emoji,
    +                icon_url=icon_url,
    +                username=username,
    +                **kwargs,
    +            )
    +        return await self.client.chat_stream(
    +            channel=channel,
    +            recipient_team_id=recipient_team_id or self.recipient_team_id,
    +            recipient_user_id=recipient_user_id or self.recipient_user_id,
    +            thread_ts=thread_ts,
    +            icon_emoji=icon_emoji,
    +            icon_url=icon_url,
    +            username=username,
    +            **kwargs,
    +        )
    +
    +
    +

    Class variables

    +
    +
    var channel : str | None
    +
    +

    The type of the None singleton.

    +
    +
    var client : slack_sdk.web.async_client.AsyncWebClient
    +
    +

    The type of the None singleton.

    +
    +
    var recipient_team_id : str | None
    +
    +

    The type of the None singleton.

    +
    +
    var recipient_user_id : str | None
    +
    +

    The type of the None singleton.

    +
    +
    var thread_ts : str | None
    +
    +

    The type of the None singleton.

    +
    +
    +
    class AsyncSetStatus (client: slack_sdk.web.async_client.AsyncWebClient,
    channel_id: str,
    thread_ts: str)
    @@ -5232,11 +5376,24 @@

    Class variables

    self.channel_id = channel_id self.thread_ts = thread_ts - async def __call__(self, status: str) -> AsyncSlackResponse: + async def __call__( + self, + status: str, + loading_messages: Optional[List[str]] = None, + icon_emoji: Optional[str] = None, + icon_url: Optional[str] = None, + username: Optional[str] = None, + **kwargs, + ) -> AsyncSlackResponse: return await self.client.assistant_threads_setStatus( - status=status, channel_id=self.channel_id, thread_ts=self.thread_ts, + status=status, + loading_messages=loading_messages, + icon_emoji=icon_emoji, + icon_url=icon_url, + username=username, + **kwargs, )
    @@ -5258,7 +5415,7 @@

    Class variables

    class AsyncSetSuggestedPrompts -(client: slack_sdk.web.async_client.AsyncWebClient,
    channel_id: str,
    thread_ts: str)
    +(client: slack_sdk.web.async_client.AsyncWebClient,
    channel_id: str,
    thread_ts: str | None = None)
    @@ -5268,13 +5425,13 @@

    Class variables

    class AsyncSetSuggestedPrompts:
         client: AsyncWebClient
         channel_id: str
    -    thread_ts: str
    +    thread_ts: Optional[str]
     
         def __init__(
             self,
             client: AsyncWebClient,
             channel_id: str,
    -        thread_ts: str,
    +        thread_ts: Optional[str] = None,
         ):
             self.client = client
             self.channel_id = channel_id
    @@ -5282,8 +5439,9 @@ 

    Class variables

    async def __call__( self, - prompts: List[Union[str, Dict[str, str]]], + prompts: Sequence[Union[str, Dict[str, str]]], title: Optional[str] = None, + thread_ts: Optional[str] = None, ) -> AsyncSlackResponse: prompts_arg: List[Dict[str, str]] = [] for prompt in prompts: @@ -5294,7 +5452,7 @@

    Class variables

    return await self.client.assistant_threads_setSuggestedPrompts( channel_id=self.channel_id, - thread_ts=self.thread_ts, + thread_ts=thread_ts if thread_ts is not None else self.thread_ts, prompts=prompts_arg, title=title, )
    @@ -5310,7 +5468,7 @@

    Class variables

    The type of the None singleton.

    -
    var thread_ts : str
    +
    var thread_ts : str | None

    The type of the None singleton.

    @@ -5455,6 +5613,7 @@

    respond
  • save_thread_context
  • say
  • +
  • say_stream
  • set_status
  • set_suggested_prompts
  • set_title
  • @@ -5499,6 +5658,7 @@

    AsyncListener

    • ack_function
    • +
    • ack_timeout
    • async_matches
    • auto_acknowledgement
    • lazy_functions
    • @@ -5534,6 +5694,16 @@

      AsyncSayStream

      + + +
    • AsyncSetStatus

      • channel_id
      • @@ -5563,7 +5733,7 @@

        -

        Generated by pdoc 0.11.5.

        +

        Generated by pdoc 0.11.6.

        diff --git a/docs/static/api-docs/slack_bolt/authorization/async_authorize.html b/docs/reference/authorization/async_authorize.html similarity index 99% rename from docs/static/api-docs/slack_bolt/authorization/async_authorize.html rename to docs/reference/authorization/async_authorize.html index 0a0640780..b4dfa2682 100644 --- a/docs/static/api-docs/slack_bolt/authorization/async_authorize.html +++ b/docs/reference/authorization/async_authorize.html @@ -3,7 +3,7 @@ - + slack_bolt.authorization.async_authorize API documentation @@ -518,7 +518,7 @@

        -

        Generated by pdoc 0.11.5.

        +

        Generated by pdoc 0.11.6.

        diff --git a/docs/static/api-docs/slack_bolt/authorization/async_authorize_args.html b/docs/reference/authorization/async_authorize_args.html similarity index 99% rename from docs/static/api-docs/slack_bolt/authorization/async_authorize_args.html rename to docs/reference/authorization/async_authorize_args.html index 642b35f93..5de20f757 100644 --- a/docs/static/api-docs/slack_bolt/authorization/async_authorize_args.html +++ b/docs/reference/authorization/async_authorize_args.html @@ -3,7 +3,7 @@ - + slack_bolt.authorization.async_authorize_args API documentation @@ -158,7 +158,7 @@

        -

        Generated by pdoc 0.11.5.

        +

        Generated by pdoc 0.11.6.

        diff --git a/docs/static/api-docs/slack_bolt/authorization/authorize.html b/docs/reference/authorization/authorize.html similarity index 99% rename from docs/static/api-docs/slack_bolt/authorization/authorize.html rename to docs/reference/authorization/authorize.html index 255c87196..33b50be02 100644 --- a/docs/static/api-docs/slack_bolt/authorization/authorize.html +++ b/docs/reference/authorization/authorize.html @@ -3,7 +3,7 @@ - + slack_bolt.authorization.authorize API documentation @@ -516,7 +516,7 @@

        -

        Generated by pdoc 0.11.5.

        +

        Generated by pdoc 0.11.6.

        diff --git a/docs/static/api-docs/slack_bolt/authorization/authorize_args.html b/docs/reference/authorization/authorize_args.html similarity index 99% rename from docs/static/api-docs/slack_bolt/authorization/authorize_args.html rename to docs/reference/authorization/authorize_args.html index 660ac17eb..78423fc40 100644 --- a/docs/static/api-docs/slack_bolt/authorization/authorize_args.html +++ b/docs/reference/authorization/authorize_args.html @@ -3,7 +3,7 @@ - + slack_bolt.authorization.authorize_args API documentation @@ -158,7 +158,7 @@

        diff --git a/docs/static/api-docs/slack_bolt/authorization/authorize_result.html b/docs/reference/authorization/authorize_result.html similarity index 97% rename from docs/static/api-docs/slack_bolt/authorization/authorize_result.html rename to docs/reference/authorization/authorize_result.html index 3bddc0a35..d53c5cd5c 100644 --- a/docs/static/api-docs/slack_bolt/authorization/authorize_result.html +++ b/docs/reference/authorization/authorize_result.html @@ -3,7 +3,7 @@ - + slack_bolt.authorization.authorize_result API documentation @@ -48,7 +48,7 @@

        Classes

        class AuthorizeResult -(*,
        enterprise_id: str | None,
        team_id: str | None,
        team: str | None = None,
        url: str | None = None,
        bot_user_id: str | None = None,
        bot_id: str | None = None,
        bot_token: str | None = None,
        bot_scopes: str | Sequence[str] | None = None,
        user_id: str | None = None,
        user: str | None = None,
        user_token: str | None = None,
        user_scopes: str | Sequence[str] | None = None)
        +(*,
        enterprise_id: str | None,
        team_id: str | None,
        team: str | None = None,
        url: str | None = None,
        bot_user_id: str | None = None,
        bot_id: str | None = None,
        bot_token: str | None = None,
        bot_scopes: Sequence[str] | str | None = None,
        user_id: str | None = None,
        user: str | None = None,
        user_token: str | None = None,
        user_scopes: Sequence[str] | str | None = None)
        @@ -246,7 +246,7 @@

        Class variables

        Static methods

        -def from_auth_test_response(*,
        bot_token: str | None = None,
        user_token: str | None = None,
        bot_scopes: str | Sequence[str] | None = None,
        user_scopes: str | Sequence[str] | None = None,
        auth_test_response: slack_sdk.web.slack_response.SlackResponse | ForwardRef('AsyncSlackResponse'),
        user_auth_test_response: slack_sdk.web.slack_response.SlackResponse | ForwardRef('AsyncSlackResponse') | None = None)
        +def from_auth_test_response(*,
        bot_token: str | None = None,
        user_token: str | None = None,
        bot_scopes: Sequence[str] | str | None = None,
        user_scopes: Sequence[str] | str | None = None,
        auth_test_response: slack_sdk.web.slack_response.SlackResponse | AsyncSlackResponse,
        user_auth_test_response: slack_sdk.web.slack_response.SlackResponse | AsyncSlackResponse | None = None)
        @@ -292,7 +292,7 @@

        diff --git a/docs/static/api-docs/slack_bolt/authorization/index.html b/docs/reference/authorization/index.html similarity index 96% rename from docs/static/api-docs/slack_bolt/authorization/index.html rename to docs/reference/authorization/index.html index eaa267d29..2fdd1f916 100644 --- a/docs/static/api-docs/slack_bolt/authorization/index.html +++ b/docs/reference/authorization/index.html @@ -3,7 +3,7 @@ - + slack_bolt.authorization API documentation @@ -39,7 +39,7 @@

        Module slack_bolt.authorization

        Authorization is the process of determining which Slack credentials should be available while processing an incoming Slack event.

        -

        Refer to https://slack.dev/bolt-python/concepts#authorization for details.

        +

        Refer to https://docs.slack.dev/tools/bolt-python/concepts/authorization for details.

        Sub-modules

        @@ -75,7 +75,7 @@

        Classes

        class AuthorizeResult -(*,
        enterprise_id: str | None,
        team_id: str | None,
        team: str | None = None,
        url: str | None = None,
        bot_user_id: str | None = None,
        bot_id: str | None = None,
        bot_token: str | None = None,
        bot_scopes: str | Sequence[str] | None = None,
        user_id: str | None = None,
        user: str | None = None,
        user_token: str | None = None,
        user_scopes: str | Sequence[str] | None = None)
        +(*,
        enterprise_id: str | None,
        team_id: str | None,
        team: str | None = None,
        url: str | None = None,
        bot_user_id: str | None = None,
        bot_id: str | None = None,
        bot_token: str | None = None,
        bot_scopes: Sequence[str] | str | None = None,
        user_id: str | None = None,
        user: str | None = None,
        user_token: str | None = None,
        user_scopes: Sequence[str] | str | None = None)
        @@ -273,7 +273,7 @@

        Class variables

        Static methods

        -def from_auth_test_response(*,
        bot_token: str | None = None,
        user_token: str | None = None,
        bot_scopes: str | Sequence[str] | None = None,
        user_scopes: str | Sequence[str] | None = None,
        auth_test_response: slack_sdk.web.slack_response.SlackResponse | ForwardRef('AsyncSlackResponse'),
        user_auth_test_response: slack_sdk.web.slack_response.SlackResponse | ForwardRef('AsyncSlackResponse') | None = None)
        +def from_auth_test_response(*,
        bot_token: str | None = None,
        user_token: str | None = None,
        bot_scopes: Sequence[str] | str | None = None,
        user_scopes: Sequence[str] | str | None = None,
        auth_test_response: slack_sdk.web.slack_response.SlackResponse | AsyncSlackResponse,
        user_auth_test_response: slack_sdk.web.slack_response.SlackResponse | AsyncSlackResponse | None = None)
        @@ -328,7 +328,7 @@

        -

        Generated by pdoc 0.11.5.

        +

        Generated by pdoc 0.11.6.

        diff --git a/docs/static/api-docs/slack_bolt/context/ack/ack.html b/docs/reference/context/ack/ack.html similarity index 98% rename from docs/static/api-docs/slack_bolt/context/ack/ack.html rename to docs/reference/context/ack/ack.html index e1b71bcb9..a8b808d86 100644 --- a/docs/static/api-docs/slack_bolt/context/ack/ack.html +++ b/docs/reference/context/ack/ack.html @@ -3,7 +3,7 @@ - + slack_bolt.context.ack.ack API documentation @@ -127,7 +127,7 @@

        -

        Generated by pdoc 0.11.5.

        +

        Generated by pdoc 0.11.6.

        diff --git a/docs/static/api-docs/slack_bolt/context/ack/async_ack.html b/docs/reference/context/ack/async_ack.html similarity index 98% rename from docs/static/api-docs/slack_bolt/context/ack/async_ack.html rename to docs/reference/context/ack/async_ack.html index 0f2989e9f..f744d5693 100644 --- a/docs/static/api-docs/slack_bolt/context/ack/async_ack.html +++ b/docs/reference/context/ack/async_ack.html @@ -3,7 +3,7 @@ - + slack_bolt.context.ack.async_ack API documentation @@ -127,7 +127,7 @@

        -

        Generated by pdoc 0.11.5.

        +

        Generated by pdoc 0.11.6.

        diff --git a/docs/static/api-docs/slack_bolt/context/ack/index.html b/docs/reference/context/ack/index.html similarity index 98% rename from docs/static/api-docs/slack_bolt/context/ack/index.html rename to docs/reference/context/ack/index.html index 230da5d99..89f0600e8 100644 --- a/docs/static/api-docs/slack_bolt/context/ack/index.html +++ b/docs/reference/context/ack/index.html @@ -3,7 +3,7 @@ - + slack_bolt.context.ack API documentation @@ -149,7 +149,7 @@

        -

        Generated by pdoc 0.11.5.

        +

        Generated by pdoc 0.11.6.

        diff --git a/docs/static/api-docs/slack_bolt/context/ack/internals.html b/docs/reference/context/ack/internals.html similarity index 98% rename from docs/static/api-docs/slack_bolt/context/ack/internals.html rename to docs/reference/context/ack/internals.html index 2fa3d8028..f7f776241 100644 --- a/docs/static/api-docs/slack_bolt/context/ack/internals.html +++ b/docs/reference/context/ack/internals.html @@ -3,7 +3,7 @@ - + slack_bolt.context.ack.internals API documentation @@ -60,7 +60,7 @@

        Module slack_bolt.context.ack.internals

        diff --git a/docs/static/api-docs/slack_bolt/context/assistant/assistant_utilities.html b/docs/reference/context/assistant/assistant_utilities.html similarity index 84% rename from docs/static/api-docs/slack_bolt/context/assistant/assistant_utilities.html rename to docs/reference/context/assistant/assistant_utilities.html index 7bfeeeee9..2200c4f10 100644 --- a/docs/static/api-docs/slack_bolt/context/assistant/assistant_utilities.html +++ b/docs/reference/context/assistant/assistant_utilities.html @@ -3,7 +3,7 @@ - + slack_bolt.context.assistant.assistant_utilities API documentation @@ -86,21 +86,10 @@

        Classes

        # When moving this code to Bolt internals, no need to raise an exception for this pattern raise ValueError(f"Cannot instantiate Assistant for this event pattern ({self.payload})") - def is_valid(self) -> bool: - return self.channel_id is not None and self.thread_ts is not None - - @property - def set_status(self) -> SetStatus: - return SetStatus(self.client, self.channel_id, self.thread_ts) - @property def set_title(self) -> SetTitle: return SetTitle(self.client, self.channel_id, self.thread_ts) - @property - def set_suggested_prompts(self) -> SetSuggestedPrompts: - return SetSuggestedPrompts(self.client, self.channel_id, self.thread_ts) - @property def say(self) -> Say: def build_metadata() -> Optional[dict]: @@ -197,30 +186,6 @@

        Instance variables

        -
        prop set_statusSetStatus
        -
        -
        - -Expand source code - -
        @property
        -def set_status(self) -> SetStatus:
        -    return SetStatus(self.client, self.channel_id, self.thread_ts)
        -
        -
        -
        -
        prop set_suggested_promptsSetSuggestedPrompts
        -
        -
        - -Expand source code - -
        @property
        -def set_suggested_prompts(self) -> SetSuggestedPrompts:
        -    return SetSuggestedPrompts(self.client, self.channel_id, self.thread_ts)
        -
        -
        -
        prop set_titleSetTitle
        @@ -234,22 +199,6 @@

        Instance variables

        -

        Methods

        -
        -
        -def is_valid(self) ‑> bool -
        -
        -
        - -Expand source code - -
        def is_valid(self) -> bool:
        -    return self.channel_id is not None and self.thread_ts is not None
        -
        -
        -
        -

        @@ -272,12 +221,9 @@

        channel_id
      • client
      • get_thread_context
      • -
      • is_valid
      • payload
      • save_thread_context
      • say
      • -
      • set_status
      • -
      • set_suggested_prompts
      • set_title
      • thread_context_store
      • thread_ts
      • @@ -289,7 +235,7 @@

        -

        Generated by pdoc 0.11.5.

        +

        Generated by pdoc 0.11.6.

        diff --git a/docs/static/api-docs/slack_bolt/context/assistant/async_assistant_utilities.html b/docs/reference/context/assistant/async_assistant_utilities.html similarity index 84% rename from docs/static/api-docs/slack_bolt/context/assistant/async_assistant_utilities.html rename to docs/reference/context/assistant/async_assistant_utilities.html index b4af582a7..70f4d0d23 100644 --- a/docs/static/api-docs/slack_bolt/context/assistant/async_assistant_utilities.html +++ b/docs/reference/context/assistant/async_assistant_utilities.html @@ -3,7 +3,7 @@ - + slack_bolt.context.assistant.async_assistant_utilities API documentation @@ -86,21 +86,10 @@

        Classes

        # When moving this code to Bolt internals, no need to raise an exception for this pattern raise ValueError(f"Cannot instantiate Assistant for this event pattern ({self.payload})") - def is_valid(self) -> bool: - return self.channel_id is not None and self.thread_ts is not None - - @property - def set_status(self) -> AsyncSetStatus: - return AsyncSetStatus(self.client, self.channel_id, self.thread_ts) - @property def set_title(self) -> AsyncSetTitle: return AsyncSetTitle(self.client, self.channel_id, self.thread_ts) - @property - def set_suggested_prompts(self) -> AsyncSetSuggestedPrompts: - return AsyncSetSuggestedPrompts(self.client, self.channel_id, self.thread_ts) - @property def say(self) -> AsyncSay: return AsyncSay( @@ -191,30 +180,6 @@

        Instance variables

        -
        prop set_statusAsyncSetStatus
        -
        -
        - -Expand source code - -
        @property
        -def set_status(self) -> AsyncSetStatus:
        -    return AsyncSetStatus(self.client, self.channel_id, self.thread_ts)
        -
        -
        -
        -
        prop set_suggested_promptsAsyncSetSuggestedPrompts
        -
        -
        - -Expand source code - -
        @property
        -def set_suggested_prompts(self) -> AsyncSetSuggestedPrompts:
        -    return AsyncSetSuggestedPrompts(self.client, self.channel_id, self.thread_ts)
        -
        -
        -
        prop set_titleAsyncSetTitle
        @@ -228,22 +193,6 @@

        Instance variables

        -

        Methods

        -
        -
        -def is_valid(self) ‑> bool -
        -
        -
        - -Expand source code - -
        def is_valid(self) -> bool:
        -    return self.channel_id is not None and self.thread_ts is not None
        -
        -
        -
        -

    @@ -266,12 +215,9 @@

    channel_id
  • client
  • get_thread_context
  • -
  • is_valid
  • payload
  • save_thread_context
  • say
  • -
  • set_status
  • -
  • set_suggested_prompts
  • set_title
  • thread_context_store
  • thread_ts
  • @@ -283,7 +229,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/context/assistant/index.html b/docs/reference/context/assistant/index.html similarity index 98% rename from docs/static/api-docs/slack_bolt/context/assistant/index.html rename to docs/reference/context/assistant/index.html index 73dcff282..d442e26cf 100644 --- a/docs/static/api-docs/slack_bolt/context/assistant/index.html +++ b/docs/reference/context/assistant/index.html @@ -3,7 +3,7 @@ - + slack_bolt.context.assistant API documentation @@ -92,7 +92,7 @@

    Sub-modules

    diff --git a/docs/static/api-docs/slack_bolt/context/assistant/internals.html b/docs/reference/context/assistant/internals.html similarity index 98% rename from docs/static/api-docs/slack_bolt/context/assistant/internals.html rename to docs/reference/context/assistant/internals.html index b1558b9d2..242bd6f19 100644 --- a/docs/static/api-docs/slack_bolt/context/assistant/internals.html +++ b/docs/reference/context/assistant/internals.html @@ -3,7 +3,7 @@ - + slack_bolt.context.assistant.internals API documentation @@ -89,7 +89,7 @@

    Functions

    diff --git a/docs/static/api-docs/slack_bolt/context/assistant/thread_context/index.html b/docs/reference/context/assistant/thread_context/index.html similarity index 98% rename from docs/static/api-docs/slack_bolt/context/assistant/thread_context/index.html rename to docs/reference/context/assistant/thread_context/index.html index 7d1232b1d..f3767a1cf 100644 --- a/docs/static/api-docs/slack_bolt/context/assistant/thread_context/index.html +++ b/docs/reference/context/assistant/thread_context/index.html @@ -3,7 +3,7 @@ - + slack_bolt.context.assistant.thread_context API documentation @@ -126,7 +126,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/context/assistant/thread_context_store/async_store.html b/docs/reference/context/assistant/thread_context_store/async_store.html similarity index 98% rename from docs/static/api-docs/slack_bolt/context/assistant/thread_context_store/async_store.html rename to docs/reference/context/assistant/thread_context_store/async_store.html index f5045739f..64f4e53ed 100644 --- a/docs/static/api-docs/slack_bolt/context/assistant/thread_context_store/async_store.html +++ b/docs/reference/context/assistant/thread_context_store/async_store.html @@ -3,7 +3,7 @@ - + slack_bolt.context.assistant.thread_context_store.async_store API documentation @@ -124,7 +124,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/context/assistant/thread_context_store/default_async_store.html b/docs/reference/context/assistant/thread_context_store/default_async_store.html similarity index 99% rename from docs/static/api-docs/slack_bolt/context/assistant/thread_context_store/default_async_store.html rename to docs/reference/context/assistant/thread_context_store/default_async_store.html index 8344971de..f6cd66060 100644 --- a/docs/static/api-docs/slack_bolt/context/assistant/thread_context_store/default_async_store.html +++ b/docs/reference/context/assistant/thread_context_store/default_async_store.html @@ -3,7 +3,7 @@ - + slack_bolt.context.assistant.thread_context_store.default_async_store API documentation @@ -190,7 +190,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/context/assistant/thread_context_store/default_store.html b/docs/reference/context/assistant/thread_context_store/default_store.html similarity index 99% rename from docs/static/api-docs/slack_bolt/context/assistant/thread_context_store/default_store.html rename to docs/reference/context/assistant/thread_context_store/default_store.html index d647b9c78..1594c5d38 100644 --- a/docs/static/api-docs/slack_bolt/context/assistant/thread_context_store/default_store.html +++ b/docs/reference/context/assistant/thread_context_store/default_store.html @@ -3,7 +3,7 @@ - + slack_bolt.context.assistant.thread_context_store.default_store API documentation @@ -188,7 +188,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/context/assistant/thread_context_store/file/index.html b/docs/reference/context/assistant/thread_context_store/file/index.html similarity index 99% rename from docs/static/api-docs/slack_bolt/context/assistant/thread_context_store/file/index.html rename to docs/reference/context/assistant/thread_context_store/file/index.html index 4190a948f..4a5d944e1 100644 --- a/docs/static/api-docs/slack_bolt/context/assistant/thread_context_store/file/index.html +++ b/docs/reference/context/assistant/thread_context_store/file/index.html @@ -3,7 +3,7 @@ - + slack_bolt.context.assistant.thread_context_store.file API documentation @@ -159,7 +159,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/context/assistant/thread_context_store/index.html b/docs/reference/context/assistant/thread_context_store/index.html similarity index 98% rename from docs/static/api-docs/slack_bolt/context/assistant/thread_context_store/index.html rename to docs/reference/context/assistant/thread_context_store/index.html index 400b3c37a..3083275d9 100644 --- a/docs/static/api-docs/slack_bolt/context/assistant/thread_context_store/index.html +++ b/docs/reference/context/assistant/thread_context_store/index.html @@ -3,7 +3,7 @@ - + slack_bolt.context.assistant.thread_context_store API documentation @@ -92,7 +92,7 @@

    Sub-modules

    diff --git a/docs/static/api-docs/slack_bolt/context/assistant/thread_context_store/store.html b/docs/reference/context/assistant/thread_context_store/store.html similarity index 98% rename from docs/static/api-docs/slack_bolt/context/assistant/thread_context_store/store.html rename to docs/reference/context/assistant/thread_context_store/store.html index fde47afc9..a0a177b09 100644 --- a/docs/static/api-docs/slack_bolt/context/assistant/thread_context_store/store.html +++ b/docs/reference/context/assistant/thread_context_store/store.html @@ -3,7 +3,7 @@ - + slack_bolt.context.assistant.thread_context_store.store API documentation @@ -125,7 +125,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/context/async_context.html b/docs/reference/context/async_context.html similarity index 97% rename from docs/static/api-docs/slack_bolt/context/async_context.html rename to docs/reference/context/async_context.html index 76ac8c5de..8fc6d36bf 100644 --- a/docs/static/api-docs/slack_bolt/context/async_context.html +++ b/docs/reference/context/async_context.html @@ -3,7 +3,7 @@ - + slack_bolt.context.async_context API documentation @@ -80,7 +80,7 @@

    Classes

    # The return type is intentionally string to avoid circular imports @property - def listener_runner(self) -> "AsyncioListenerRunner": # type: ignore[name-defined] + def listener_runner(self) -> "AsyncioListenerRunner": """The properly configured listener_runner that is available for middleware/listeners.""" return self["listener_runner"] @@ -149,7 +149,7 @@

    Classes

    Callable `say()` function """ if "say" not in self: - self["say"] = AsyncSay(client=self.client, channel=self.channel_id, thread_ts=self.thread_ts) + self["say"] = AsyncSay(client=self.client, channel=self.channel_id) return self["say"] @property @@ -242,6 +242,10 @@

    Classes

    def get_thread_context(self) -> Optional[AsyncGetThreadContext]: return self.get("get_thread_context") + @property + def say_stream(self) -> Optional[AsyncSayStream]: + return self.get("say_stream") + @property def save_thread_context(self) -> Optional[AsyncSaveThreadContext]: return self.get("save_thread_context")
    @@ -457,7 +461,7 @@

    Returns

    Expand source code
    @property
    -def listener_runner(self) -> "AsyncioListenerRunner":  # type: ignore[name-defined]
    +def listener_runner(self) -> "AsyncioListenerRunner":
         """The properly configured listener_runner that is available for middleware/listeners."""
         return self["listener_runner"]
    @@ -547,7 +551,7 @@

    Returns

    Callable `say()` function """ if "say" not in self: - self["say"] = AsyncSay(client=self.client, channel=self.channel_id, thread_ts=self.thread_ts) + self["say"] = AsyncSay(client=self.client, channel=self.channel_id) return self["say"]

    say() function for this request.

    @@ -565,6 +569,18 @@

    Returns

    Returns

    Callable say() function

    +
    prop say_streamAsyncSayStream | None
    +
    +
    + +Expand source code + +
    @property
    +def say_stream(self) -> Optional[AsyncSayStream]:
    +    return self.get("say_stream")
    +
    +
    +
    prop set_statusAsyncSetStatus | None
    @@ -694,6 +710,7 @@

    respond
  • save_thread_context
  • say
  • +
  • say_stream
  • set_status
  • set_suggested_prompts
  • set_title
  • @@ -706,7 +723,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/context/base_context.html b/docs/reference/context/base_context.html similarity index 99% rename from docs/static/api-docs/slack_bolt/context/base_context.html rename to docs/reference/context/base_context.html index 54617176b..afe571163 100644 --- a/docs/static/api-docs/slack_bolt/context/base_context.html +++ b/docs/reference/context/base_context.html @@ -3,7 +3,7 @@ - + slack_bolt.context.base_context API documentation @@ -89,6 +89,7 @@

    Classes

    "set_status", "set_title", "set_suggested_prompts", + "say_stream", ] # Note that these items are not copyable, so when you add new items to this list, # you must modify ThreadListenerRunner/AsyncioListenerRunner's _build_lazy_request method to pass the values. @@ -640,7 +641,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/context/complete/async_complete.html b/docs/reference/context/complete/async_complete.html similarity index 86% rename from docs/static/api-docs/slack_bolt/context/complete/async_complete.html rename to docs/reference/context/complete/async_complete.html index e6ce03d74..f0546a950 100644 --- a/docs/static/api-docs/slack_bolt/context/complete/async_complete.html +++ b/docs/reference/context/complete/async_complete.html @@ -3,7 +3,7 @@ - + slack_bolt.context.complete.async_complete API documentation @@ -58,6 +58,7 @@

    Classes

    class AsyncComplete:
         client: AsyncWebClient
         function_execution_id: Optional[str]
    +    _called: bool
     
         def __init__(
             self,
    @@ -66,6 +67,7 @@ 

    Classes

    ): self.client = client self.function_execution_id = function_execution_id + self._called = False async def __call__(self, outputs: Optional[Dict[str, Any]] = None) -> AsyncSlackResponse: """Signal the successful completion of the custom function. @@ -82,9 +84,18 @@

    Classes

    if self.function_execution_id is None: raise ValueError("complete is unsupported here as there is no function_execution_id") + self._called = True return await self.client.functions_completeSuccess( function_execution_id=self.function_execution_id, outputs=outputs or {} - )
    + ) + + def has_been_called(self) -> bool: + """Check if this complete function has been called. + + Returns: + bool: True if the complete function has been called, False otherwise. + """ + return self._called

    Class variables

    @@ -98,6 +109,32 @@

    Class variables

    The type of the None singleton.

    +

    Methods

    +
    +
    +def has_been_called(self) ‑> bool +
    +
    +
    + +Expand source code + +
    def has_been_called(self) -> bool:
    +    """Check if this complete function has been called.
    +
    +    Returns:
    +        bool: True if the complete function has been called, False otherwise.
    +    """
    +    return self._called
    +
    +

    Check if this complete function has been called.

    +

    Returns

    +
    +
    bool
    +
    True if the complete function has been called, False otherwise.
    +
    +
    +
    @@ -119,6 +156,7 @@

  • client
  • function_execution_id
  • +
  • has_been_called
  • @@ -127,7 +165,7 @@

    diff --git a/docs/static/api-docs/slack_bolt/context/complete/complete.html b/docs/reference/context/complete/complete.html similarity index 85% rename from docs/static/api-docs/slack_bolt/context/complete/complete.html rename to docs/reference/context/complete/complete.html index ef6c6c78f..b8c1b083b 100644 --- a/docs/static/api-docs/slack_bolt/context/complete/complete.html +++ b/docs/reference/context/complete/complete.html @@ -3,7 +3,7 @@ - + slack_bolt.context.complete.complete API documentation @@ -58,6 +58,7 @@

    Classes

    class Complete:
         client: WebClient
         function_execution_id: Optional[str]
    +    _called: bool
     
         def __init__(
             self,
    @@ -66,6 +67,7 @@ 

    Classes

    ): self.client = client self.function_execution_id = function_execution_id + self._called = False def __call__(self, outputs: Optional[Dict[str, Any]] = None) -> SlackResponse: """Signal the successful completion of the custom function. @@ -82,7 +84,16 @@

    Classes

    if self.function_execution_id is None: raise ValueError("complete is unsupported here as there is no function_execution_id") - return self.client.functions_completeSuccess(function_execution_id=self.function_execution_id, outputs=outputs or {})
    + self._called = True + return self.client.functions_completeSuccess(function_execution_id=self.function_execution_id, outputs=outputs or {}) + + def has_been_called(self) -> bool: + """Check if this complete function has been called. + + Returns: + bool: True if the complete function has been called, False otherwise. + """ + return self._called

    Class variables

    @@ -96,6 +107,32 @@

    Class variables

    The type of the None singleton.

    +

    Methods

    +
    +
    +def has_been_called(self) ‑> bool +
    +
    +
    + +Expand source code + +
    def has_been_called(self) -> bool:
    +    """Check if this complete function has been called.
    +
    +    Returns:
    +        bool: True if the complete function has been called, False otherwise.
    +    """
    +    return self._called
    +
    +

    Check if this complete function has been called.

    +

    Returns

    +
    +
    bool
    +
    True if the complete function has been called, False otherwise.
    +
    +
    +
    @@ -117,6 +154,7 @@

  • client
  • function_execution_id
  • +
  • has_been_called
  • @@ -125,7 +163,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/context/complete/index.html b/docs/reference/context/complete/index.html similarity index 86% rename from docs/static/api-docs/slack_bolt/context/complete/index.html rename to docs/reference/context/complete/index.html index f476fa258..dddd26a84 100644 --- a/docs/static/api-docs/slack_bolt/context/complete/index.html +++ b/docs/reference/context/complete/index.html @@ -3,7 +3,7 @@ - + slack_bolt.context.complete API documentation @@ -69,6 +69,7 @@

    Classes

    class Complete:
         client: WebClient
         function_execution_id: Optional[str]
    +    _called: bool
     
         def __init__(
             self,
    @@ -77,6 +78,7 @@ 

    Classes

    ): self.client = client self.function_execution_id = function_execution_id + self._called = False def __call__(self, outputs: Optional[Dict[str, Any]] = None) -> SlackResponse: """Signal the successful completion of the custom function. @@ -93,7 +95,16 @@

    Classes

    if self.function_execution_id is None: raise ValueError("complete is unsupported here as there is no function_execution_id") - return self.client.functions_completeSuccess(function_execution_id=self.function_execution_id, outputs=outputs or {})
    + self._called = True + return self.client.functions_completeSuccess(function_execution_id=self.function_execution_id, outputs=outputs or {}) + + def has_been_called(self) -> bool: + """Check if this complete function has been called. + + Returns: + bool: True if the complete function has been called, False otherwise. + """ + return self._called

    Class variables

    @@ -107,6 +118,32 @@

    Class variables

    The type of the None singleton.

    +

    Methods

    +
    +
    +def has_been_called(self) ‑> bool +
    +
    +
    + +Expand source code + +
    def has_been_called(self) -> bool:
    +    """Check if this complete function has been called.
    +
    +    Returns:
    +        bool: True if the complete function has been called, False otherwise.
    +    """
    +    return self._called
    +
    +

    Check if this complete function has been called.

    +

    Returns

    +
    +
    bool
    +
    True if the complete function has been called, False otherwise.
    +
    +
    +
    @@ -134,6 +171,7 @@

  • client
  • function_execution_id
  • +
  • has_been_called
  • @@ -142,7 +180,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/context/context.html b/docs/reference/context/context.html similarity index 97% rename from docs/static/api-docs/slack_bolt/context/context.html rename to docs/reference/context/context.html index c1ae2789e..a7b531c20 100644 --- a/docs/static/api-docs/slack_bolt/context/context.html +++ b/docs/reference/context/context.html @@ -3,7 +3,7 @@ - + slack_bolt.context.context API documentation @@ -81,7 +81,7 @@

    Classes

    # The return type is intentionally string to avoid circular imports @property - def listener_runner(self) -> "ThreadListenerRunner": # type: ignore[name-defined] + def listener_runner(self) -> "ThreadListenerRunner": """The properly configured listener_runner that is available for middleware/listeners.""" return self["listener_runner"] @@ -150,7 +150,7 @@

    Classes

    Callable `say()` function """ if "say" not in self: - self["say"] = Say(client=self.client, channel=self.channel_id, thread_ts=self.thread_ts) + self["say"] = Say(client=self.client, channel=self.channel_id) return self["say"] @property @@ -243,6 +243,10 @@

    Classes

    def get_thread_context(self) -> Optional[GetThreadContext]: return self.get("get_thread_context") + @property + def say_stream(self) -> Optional[SayStream]: + return self.get("say_stream") + @property def save_thread_context(self) -> Optional[SaveThreadContext]: return self.get("save_thread_context")
    @@ -458,7 +462,7 @@

    Returns

    Expand source code
    @property
    -def listener_runner(self) -> "ThreadListenerRunner":  # type: ignore[name-defined]
    +def listener_runner(self) -> "ThreadListenerRunner":
         """The properly configured listener_runner that is available for middleware/listeners."""
         return self["listener_runner"]
    @@ -548,7 +552,7 @@

    Returns

    Callable `say()` function """ if "say" not in self: - self["say"] = Say(client=self.client, channel=self.channel_id, thread_ts=self.thread_ts) + self["say"] = Say(client=self.client, channel=self.channel_id) return self["say"]

    say() function for this request.

    @@ -566,6 +570,18 @@

    Returns

    Returns

    Callable say() function

    +
    prop say_streamSayStream | None
    +
    +
    + +Expand source code + +
    @property
    +def say_stream(self) -> Optional[SayStream]:
    +    return self.get("say_stream")
    +
    +
    +
    prop set_statusSetStatus | None
    @@ -696,6 +712,7 @@

    respond
  • save_thread_context
  • say
  • +
  • say_stream
  • set_status
  • set_suggested_prompts
  • set_title
  • @@ -708,7 +725,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/context/fail/async_fail.html b/docs/reference/context/fail/async_fail.html similarity index 86% rename from docs/static/api-docs/slack_bolt/context/fail/async_fail.html rename to docs/reference/context/fail/async_fail.html index 91497eff9..80f19d18c 100644 --- a/docs/static/api-docs/slack_bolt/context/fail/async_fail.html +++ b/docs/reference/context/fail/async_fail.html @@ -3,7 +3,7 @@ - + slack_bolt.context.fail.async_fail API documentation @@ -58,6 +58,7 @@

    Classes

    class AsyncFail:
         client: AsyncWebClient
         function_execution_id: Optional[str]
    +    _called: bool
     
         def __init__(
             self,
    @@ -66,6 +67,7 @@ 

    Classes

    ): self.client = client self.function_execution_id = function_execution_id + self._called = False async def __call__(self, error: str) -> AsyncSlackResponse: """Signal that the custom function failed to complete. @@ -82,7 +84,16 @@

    Classes

    if self.function_execution_id is None: raise ValueError("fail is unsupported here as there is no function_execution_id") - return await self.client.functions_completeError(function_execution_id=self.function_execution_id, error=error)
    + self._called = True + return await self.client.functions_completeError(function_execution_id=self.function_execution_id, error=error) + + def has_been_called(self) -> bool: + """Check if this fail function has been called. + + Returns: + bool: True if the fail function has been called, False otherwise. + """ + return self._called

    Class variables

    @@ -96,6 +107,32 @@

    Class variables

    The type of the None singleton.

    +

    Methods

    +
    +
    +def has_been_called(self) ‑> bool +
    +
    +
    + +Expand source code + +
    def has_been_called(self) -> bool:
    +    """Check if this fail function has been called.
    +
    +    Returns:
    +        bool: True if the fail function has been called, False otherwise.
    +    """
    +    return self._called
    +
    +

    Check if this fail function has been called.

    +

    Returns

    +
    +
    bool
    +
    True if the fail function has been called, False otherwise.
    +
    +
    +
    @@ -117,6 +154,7 @@

  • client
  • function_execution_id
  • +
  • has_been_called
  • @@ -125,7 +163,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/context/fail/fail.html b/docs/reference/context/fail/fail.html similarity index 86% rename from docs/static/api-docs/slack_bolt/context/fail/fail.html rename to docs/reference/context/fail/fail.html index 20d44d1d5..51f4896a4 100644 --- a/docs/static/api-docs/slack_bolt/context/fail/fail.html +++ b/docs/reference/context/fail/fail.html @@ -3,7 +3,7 @@ - + slack_bolt.context.fail.fail API documentation @@ -58,6 +58,7 @@

    Classes

    class Fail:
         client: WebClient
         function_execution_id: Optional[str]
    +    _called: bool
     
         def __init__(
             self,
    @@ -66,6 +67,7 @@ 

    Classes

    ): self.client = client self.function_execution_id = function_execution_id + self._called = False def __call__(self, error: str) -> SlackResponse: """Signal that the custom function failed to complete. @@ -82,7 +84,16 @@

    Classes

    if self.function_execution_id is None: raise ValueError("fail is unsupported here as there is no function_execution_id") - return self.client.functions_completeError(function_execution_id=self.function_execution_id, error=error)
    + self._called = True + return self.client.functions_completeError(function_execution_id=self.function_execution_id, error=error) + + def has_been_called(self) -> bool: + """Check if this fail function has been called. + + Returns: + bool: True if the fail function has been called, False otherwise. + """ + return self._called

    Class variables

    @@ -96,6 +107,32 @@

    Class variables

    The type of the None singleton.

    +

    Methods

    +
    +
    +def has_been_called(self) ‑> bool +
    +
    +
    + +Expand source code + +
    def has_been_called(self) -> bool:
    +    """Check if this fail function has been called.
    +
    +    Returns:
    +        bool: True if the fail function has been called, False otherwise.
    +    """
    +    return self._called
    +
    +

    Check if this fail function has been called.

    +

    Returns

    +
    +
    bool
    +
    True if the fail function has been called, False otherwise.
    +
    +
    +
    @@ -117,6 +154,7 @@

  • client
  • function_execution_id
  • +
  • has_been_called
  • @@ -125,7 +163,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/context/fail/index.html b/docs/reference/context/fail/index.html similarity index 87% rename from docs/static/api-docs/slack_bolt/context/fail/index.html rename to docs/reference/context/fail/index.html index 2b14ac772..3b35dd6aa 100644 --- a/docs/static/api-docs/slack_bolt/context/fail/index.html +++ b/docs/reference/context/fail/index.html @@ -3,7 +3,7 @@ - + slack_bolt.context.fail API documentation @@ -69,6 +69,7 @@

    Classes

    class Fail:
         client: WebClient
         function_execution_id: Optional[str]
    +    _called: bool
     
         def __init__(
             self,
    @@ -77,6 +78,7 @@ 

    Classes

    ): self.client = client self.function_execution_id = function_execution_id + self._called = False def __call__(self, error: str) -> SlackResponse: """Signal that the custom function failed to complete. @@ -93,7 +95,16 @@

    Classes

    if self.function_execution_id is None: raise ValueError("fail is unsupported here as there is no function_execution_id") - return self.client.functions_completeError(function_execution_id=self.function_execution_id, error=error)
    + self._called = True + return self.client.functions_completeError(function_execution_id=self.function_execution_id, error=error) + + def has_been_called(self) -> bool: + """Check if this fail function has been called. + + Returns: + bool: True if the fail function has been called, False otherwise. + """ + return self._called

    Class variables

    @@ -107,6 +118,32 @@

    Class variables

    The type of the None singleton.

    +

    Methods

    +
    +
    +def has_been_called(self) ‑> bool +
    +
    +
    + +Expand source code + +
    def has_been_called(self) -> bool:
    +    """Check if this fail function has been called.
    +
    +    Returns:
    +        bool: True if the fail function has been called, False otherwise.
    +    """
    +    return self._called
    +
    +

    Check if this fail function has been called.

    +

    Returns

    +
    +
    bool
    +
    True if the fail function has been called, False otherwise.
    +
    +
    +
    @@ -134,6 +171,7 @@

  • client
  • function_execution_id
  • +
  • has_been_called
  • @@ -142,7 +180,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/context/get_thread_context/async_get_thread_context.html b/docs/reference/context/get_thread_context/async_get_thread_context.html similarity index 96% rename from docs/static/api-docs/slack_bolt/context/get_thread_context/async_get_thread_context.html rename to docs/reference/context/get_thread_context/async_get_thread_context.html index 66500752e..967581b50 100644 --- a/docs/static/api-docs/slack_bolt/context/get_thread_context/async_get_thread_context.html +++ b/docs/reference/context/get_thread_context/async_get_thread_context.html @@ -3,7 +3,7 @@ - + slack_bolt.context.get_thread_context.async_get_thread_context API documentation @@ -82,14 +82,10 @@

    Classes

    if self.thread_context_loaded is True: return self._thread_context - if self.payload.get("assistant_thread") is not None: + thread = self.payload.get("assistant_thread") + if isinstance(thread, dict) and thread.get("context", {}).get("channel_id") is not None: # assistant_thread_started - thread = self.payload["assistant_thread"] - self._thread_context = ( - AssistantThreadContext(thread["context"]) - if thread.get("context", {}).get("channel_id") is not None - else None - ) + self._thread_context = AssistantThreadContext(thread["context"]) # for this event, the context will never be changed self.thread_context_loaded = True elif self.payload.get("channel") is not None and self.payload.get("thread_ts") is not None: @@ -154,7 +150,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/context/get_thread_context/get_thread_context.html b/docs/reference/context/get_thread_context/get_thread_context.html similarity index 96% rename from docs/static/api-docs/slack_bolt/context/get_thread_context/get_thread_context.html rename to docs/reference/context/get_thread_context/get_thread_context.html index a6777da30..cf2e17a86 100644 --- a/docs/static/api-docs/slack_bolt/context/get_thread_context/get_thread_context.html +++ b/docs/reference/context/get_thread_context/get_thread_context.html @@ -3,7 +3,7 @@ - + slack_bolt.context.get_thread_context.get_thread_context API documentation @@ -82,14 +82,10 @@

    Classes

    if self.thread_context_loaded is True: return self._thread_context - if self.payload.get("assistant_thread") is not None: + thread = self.payload.get("assistant_thread") + if isinstance(thread, dict) and thread.get("context", {}).get("channel_id") is not None: # assistant_thread_started - thread = self.payload["assistant_thread"] - self._thread_context = ( - AssistantThreadContext(thread["context"]) - if thread.get("context", {}).get("channel_id") is not None - else None - ) + self._thread_context = AssistantThreadContext(thread["context"]) # for this event, the context will never be changed self.thread_context_loaded = True elif self.payload.get("channel") is not None and self.payload.get("thread_ts") is not None: @@ -154,7 +150,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/context/get_thread_context/index.html b/docs/reference/context/get_thread_context/index.html similarity index 96% rename from docs/static/api-docs/slack_bolt/context/get_thread_context/index.html rename to docs/reference/context/get_thread_context/index.html index ffd095911..5f9e38e71 100644 --- a/docs/static/api-docs/slack_bolt/context/get_thread_context/index.html +++ b/docs/reference/context/get_thread_context/index.html @@ -3,7 +3,7 @@ - + slack_bolt.context.get_thread_context API documentation @@ -93,14 +93,10 @@

    Classes

    if self.thread_context_loaded is True: return self._thread_context - if self.payload.get("assistant_thread") is not None: + thread = self.payload.get("assistant_thread") + if isinstance(thread, dict) and thread.get("context", {}).get("channel_id") is not None: # assistant_thread_started - thread = self.payload["assistant_thread"] - self._thread_context = ( - AssistantThreadContext(thread["context"]) - if thread.get("context", {}).get("channel_id") is not None - else None - ) + self._thread_context = AssistantThreadContext(thread["context"]) # for this event, the context will never be changed self.thread_context_loaded = True elif self.payload.get("channel") is not None and self.payload.get("thread_ts") is not None: @@ -171,7 +167,7 @@

    diff --git a/docs/static/api-docs/slack_bolt/context/index.html b/docs/reference/context/index.html similarity index 96% rename from docs/static/api-docs/slack_bolt/context/index.html rename to docs/reference/context/index.html index 4d3f472cc..ebdfe8aa8 100644 --- a/docs/static/api-docs/slack_bolt/context/index.html +++ b/docs/reference/context/index.html @@ -3,7 +3,7 @@ - + slack_bolt.context API documentation @@ -40,7 +40,7 @@

    Module slack_bolt.context

    All listeners have access to a context dictionary, which can be used to enrich events with additional information. Bolt automatically attaches information that is included in the incoming event, like user_id, team_id, channel_id, and enterprise_id.

    -

    Refer to https://slack.dev/bolt-python/concepts#context for details.

    +

    Refer to https://docs.slack.dev/tools/bolt-python/concepts/context for details.

    Sub-modules

    @@ -89,6 +89,10 @@

    Sub-modules

    +
    slack_bolt.context.say_stream
    +
    +
    +
    slack_bolt.context.set_status
    @@ -145,7 +149,7 @@

    Classes

    # The return type is intentionally string to avoid circular imports @property - def listener_runner(self) -> "ThreadListenerRunner": # type: ignore[name-defined] + def listener_runner(self) -> "ThreadListenerRunner": """The properly configured listener_runner that is available for middleware/listeners.""" return self["listener_runner"] @@ -214,7 +218,7 @@

    Classes

    Callable `say()` function """ if "say" not in self: - self["say"] = Say(client=self.client, channel=self.channel_id, thread_ts=self.thread_ts) + self["say"] = Say(client=self.client, channel=self.channel_id) return self["say"] @property @@ -307,6 +311,10 @@

    Classes

    def get_thread_context(self) -> Optional[GetThreadContext]: return self.get("get_thread_context") + @property + def say_stream(self) -> Optional[SayStream]: + return self.get("say_stream") + @property def save_thread_context(self) -> Optional[SaveThreadContext]: return self.get("save_thread_context")
    @@ -522,7 +530,7 @@

    Returns

    Expand source code
    @property
    -def listener_runner(self) -> "ThreadListenerRunner":  # type: ignore[name-defined]
    +def listener_runner(self) -> "ThreadListenerRunner":
         """The properly configured listener_runner that is available for middleware/listeners."""
         return self["listener_runner"]
    @@ -612,7 +620,7 @@

    Returns

    Callable `say()` function """ if "say" not in self: - self["say"] = Say(client=self.client, channel=self.channel_id, thread_ts=self.thread_ts) + self["say"] = Say(client=self.client, channel=self.channel_id) return self["say"]

    slack_bolt.context.say function for this request.

    @@ -630,6 +638,18 @@

    Returns

    Returns

    Callable slack_bolt.context.say function

    +
    prop say_streamSayStream | None
    +
    +
    + +Expand source code + +
    @property
    +def say_stream(self) -> Optional[SayStream]:
    +    return self.get("say_stream")
    +
    +
    +
    prop set_statusSetStatus | None
    @@ -759,6 +779,7 @@

    Inherited members

  • slack_bolt.context.respond
  • slack_bolt.context.save_thread_context
  • slack_bolt.context.say
  • +
  • slack_bolt.context.say_stream
  • slack_bolt.context.set_status
  • slack_bolt.context.set_suggested_prompts
  • slack_bolt.context.set_title
  • @@ -778,6 +799,7 @@

    respond
  • save_thread_context
  • say
  • +
  • say_stream
  • set_status
  • set_suggested_prompts
  • set_title
  • @@ -790,7 +812,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/context/respond/async_respond.html b/docs/reference/context/respond/async_respond.html similarity index 99% rename from docs/static/api-docs/slack_bolt/context/respond/async_respond.html rename to docs/reference/context/respond/async_respond.html index 148e1607e..ed071afaf 100644 --- a/docs/static/api-docs/slack_bolt/context/respond/async_respond.html +++ b/docs/reference/context/respond/async_respond.html @@ -3,7 +3,7 @@ - + slack_bolt.context.respond.async_respond API documentation @@ -160,7 +160,7 @@

    diff --git a/docs/static/api-docs/slack_bolt/context/respond/index.html b/docs/reference/context/respond/index.html similarity index 99% rename from docs/static/api-docs/slack_bolt/context/respond/index.html rename to docs/reference/context/respond/index.html index 94693dccf..8c116c956 100644 --- a/docs/static/api-docs/slack_bolt/context/respond/index.html +++ b/docs/reference/context/respond/index.html @@ -3,7 +3,7 @@ - + slack_bolt.context.respond API documentation @@ -182,7 +182,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/context/respond/internals.html b/docs/reference/context/respond/internals.html similarity index 98% rename from docs/static/api-docs/slack_bolt/context/respond/internals.html rename to docs/reference/context/respond/internals.html index 295a793a3..e61988ef6 100644 --- a/docs/static/api-docs/slack_bolt/context/respond/internals.html +++ b/docs/reference/context/respond/internals.html @@ -3,7 +3,7 @@ - + slack_bolt.context.respond.internals API documentation @@ -60,7 +60,7 @@

    Module slack_bolt.context.respond.internals

    diff --git a/docs/static/api-docs/slack_bolt/context/respond/respond.html b/docs/reference/context/respond/respond.html similarity index 98% rename from docs/static/api-docs/slack_bolt/context/respond/respond.html rename to docs/reference/context/respond/respond.html index 5fb010d20..af2271eb6 100644 --- a/docs/static/api-docs/slack_bolt/context/respond/respond.html +++ b/docs/reference/context/respond/respond.html @@ -3,7 +3,7 @@ - + slack_bolt.context.respond.respond API documentation @@ -160,7 +160,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/context/save_thread_context/async_save_thread_context.html b/docs/reference/context/save_thread_context/async_save_thread_context.html similarity index 98% rename from docs/static/api-docs/slack_bolt/context/save_thread_context/async_save_thread_context.html rename to docs/reference/context/save_thread_context/async_save_thread_context.html index 796970dd7..f57291c3c 100644 --- a/docs/static/api-docs/slack_bolt/context/save_thread_context/async_save_thread_context.html +++ b/docs/reference/context/save_thread_context/async_save_thread_context.html @@ -3,7 +3,7 @@ - + slack_bolt.context.save_thread_context.async_save_thread_context API documentation @@ -123,7 +123,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/context/save_thread_context/index.html b/docs/reference/context/save_thread_context/index.html similarity index 99% rename from docs/static/api-docs/slack_bolt/context/save_thread_context/index.html rename to docs/reference/context/save_thread_context/index.html index 8b974a213..01f63ecd8 100644 --- a/docs/static/api-docs/slack_bolt/context/save_thread_context/index.html +++ b/docs/reference/context/save_thread_context/index.html @@ -3,7 +3,7 @@ - + slack_bolt.context.save_thread_context API documentation @@ -140,7 +140,7 @@

    diff --git a/docs/static/api-docs/slack_bolt/context/save_thread_context/save_thread_context.html b/docs/reference/context/save_thread_context/save_thread_context.html similarity index 98% rename from docs/static/api-docs/slack_bolt/context/save_thread_context/save_thread_context.html rename to docs/reference/context/save_thread_context/save_thread_context.html index 17c147505..328441034 100644 --- a/docs/static/api-docs/slack_bolt/context/save_thread_context/save_thread_context.html +++ b/docs/reference/context/save_thread_context/save_thread_context.html @@ -3,7 +3,7 @@ - + slack_bolt.context.save_thread_context.save_thread_context API documentation @@ -123,7 +123,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/context/say/async_say.html b/docs/reference/context/say/async_say.html similarity index 98% rename from docs/static/api-docs/slack_bolt/context/say/async_say.html rename to docs/reference/context/say/async_say.html index 78d0b83a6..e170251fe 100644 --- a/docs/static/api-docs/slack_bolt/context/say/async_say.html +++ b/docs/reference/context/say/async_say.html @@ -3,7 +3,7 @@ - + slack_bolt.context.say.async_say API documentation @@ -87,6 +87,7 @@

    Classes

    icon_emoji: Optional[str] = None, icon_url: Optional[str] = None, username: Optional[str] = None, + markdown_text: Optional[str] = None, mrkdwn: Optional[bool] = None, link_names: Optional[bool] = None, parse: Optional[str] = None, # none, full @@ -112,6 +113,7 @@

    Classes

    icon_emoji=icon_emoji, icon_url=icon_url, username=username, + markdown_text=markdown_text, mrkdwn=mrkdwn, link_names=link_names, parse=parse, @@ -183,7 +185,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/context/say/index.html b/docs/reference/context/say/index.html similarity index 98% rename from docs/static/api-docs/slack_bolt/context/say/index.html rename to docs/reference/context/say/index.html index 5e2897f38..e2ed0d03f 100644 --- a/docs/static/api-docs/slack_bolt/context/say/index.html +++ b/docs/reference/context/say/index.html @@ -3,7 +3,7 @@ - + slack_bolt.context.say API documentation @@ -105,6 +105,7 @@

    Classes

    icon_emoji: Optional[str] = None, icon_url: Optional[str] = None, username: Optional[str] = None, + markdown_text: Optional[str] = None, mrkdwn: Optional[bool] = None, link_names: Optional[bool] = None, parse: Optional[str] = None, # none, full @@ -130,6 +131,7 @@

    Classes

    icon_emoji=icon_emoji, icon_url=icon_url, username=username, + markdown_text=markdown_text, mrkdwn=mrkdwn, link_names=link_names, parse=parse, @@ -214,7 +216,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/context/say/internals.html b/docs/reference/context/say/internals.html similarity index 98% rename from docs/static/api-docs/slack_bolt/context/say/internals.html rename to docs/reference/context/say/internals.html index ac349a4b5..861065203 100644 --- a/docs/static/api-docs/slack_bolt/context/say/internals.html +++ b/docs/reference/context/say/internals.html @@ -3,7 +3,7 @@ - + slack_bolt.context.say.internals API documentation @@ -60,7 +60,7 @@

    Module slack_bolt.context.say.internals

    diff --git a/docs/static/api-docs/slack_bolt/context/say/say.html b/docs/reference/context/say/say.html similarity index 98% rename from docs/static/api-docs/slack_bolt/context/say/say.html rename to docs/reference/context/say/say.html index 20ad41c0e..c66e2776f 100644 --- a/docs/static/api-docs/slack_bolt/context/say/say.html +++ b/docs/reference/context/say/say.html @@ -3,7 +3,7 @@ - + slack_bolt.context.say.say API documentation @@ -90,6 +90,7 @@

    Classes

    icon_emoji: Optional[str] = None, icon_url: Optional[str] = None, username: Optional[str] = None, + markdown_text: Optional[str] = None, mrkdwn: Optional[bool] = None, link_names: Optional[bool] = None, parse: Optional[str] = None, # none, full @@ -115,6 +116,7 @@

    Classes

    icon_emoji=icon_emoji, icon_url=icon_url, username=username, + markdown_text=markdown_text, mrkdwn=mrkdwn, link_names=link_names, parse=parse, @@ -192,7 +194,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/reference/context/say_stream/async_say_stream.html b/docs/reference/context/say_stream/async_say_stream.html new file mode 100644 index 000000000..3a1978299 --- /dev/null +++ b/docs/reference/context/say_stream/async_say_stream.html @@ -0,0 +1,183 @@ + + + + + + +slack_bolt.context.say_stream.async_say_stream API documentation + + + + + + + + + + + +
    +
    +
    +

    Module slack_bolt.context.say_stream.async_say_stream

    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    Classes

    +
    +
    +class AsyncSayStream +(*,
    client: slack_sdk.web.async_client.AsyncWebClient,
    channel: str | None = None,
    recipient_team_id: str | None = None,
    recipient_user_id: str | None = None,
    thread_ts: str | None = None)
    +
    +
    +
    + +Expand source code + +
    class AsyncSayStream:
    +    client: AsyncWebClient
    +    channel: Optional[str]
    +    recipient_team_id: Optional[str]
    +    recipient_user_id: Optional[str]
    +    thread_ts: Optional[str]
    +
    +    def __init__(
    +        self,
    +        *,
    +        client: AsyncWebClient,
    +        channel: Optional[str] = None,
    +        recipient_team_id: Optional[str] = None,
    +        recipient_user_id: Optional[str] = None,
    +        thread_ts: Optional[str] = None,
    +    ):
    +        self.client = client
    +        self.channel = channel
    +        self.recipient_team_id = recipient_team_id
    +        self.recipient_user_id = recipient_user_id
    +        self.thread_ts = thread_ts
    +
    +    async def __call__(
    +        self,
    +        *,
    +        buffer_size: Optional[int] = None,
    +        channel: Optional[str] = None,
    +        recipient_team_id: Optional[str] = None,
    +        recipient_user_id: Optional[str] = None,
    +        thread_ts: Optional[str] = None,
    +        icon_emoji: Optional[str] = None,
    +        icon_url: Optional[str] = None,
    +        username: Optional[str] = None,
    +        **kwargs,
    +    ) -> AsyncChatStream:
    +        """Starts a new chat stream with context."""
    +        channel = channel or self.channel
    +        thread_ts = thread_ts or self.thread_ts
    +        if channel is None:
    +            raise ValueError("say_stream without channel here is unsupported")
    +        if thread_ts is None:
    +            raise ValueError("say_stream without thread_ts here is unsupported")
    +
    +        if buffer_size is not None:
    +            return await self.client.chat_stream(
    +                buffer_size=buffer_size,
    +                channel=channel,
    +                recipient_team_id=recipient_team_id or self.recipient_team_id,
    +                recipient_user_id=recipient_user_id or self.recipient_user_id,
    +                thread_ts=thread_ts,
    +                icon_emoji=icon_emoji,
    +                icon_url=icon_url,
    +                username=username,
    +                **kwargs,
    +            )
    +        return await self.client.chat_stream(
    +            channel=channel,
    +            recipient_team_id=recipient_team_id or self.recipient_team_id,
    +            recipient_user_id=recipient_user_id or self.recipient_user_id,
    +            thread_ts=thread_ts,
    +            icon_emoji=icon_emoji,
    +            icon_url=icon_url,
    +            username=username,
    +            **kwargs,
    +        )
    +
    +
    +

    Class variables

    +
    +
    var channel : str | None
    +
    +

    The type of the None singleton.

    +
    +
    var client : slack_sdk.web.async_client.AsyncWebClient
    +
    +

    The type of the None singleton.

    +
    +
    var recipient_team_id : str | None
    +
    +

    The type of the None singleton.

    +
    +
    var recipient_user_id : str | None
    +
    +

    The type of the None singleton.

    +
    +
    var thread_ts : str | None
    +
    +

    The type of the None singleton.

    +
    +
    +
    +
    +
    +
    + +
    + + + diff --git a/docs/reference/context/say_stream/index.html b/docs/reference/context/say_stream/index.html new file mode 100644 index 000000000..5ed62587b --- /dev/null +++ b/docs/reference/context/say_stream/index.html @@ -0,0 +1,200 @@ + + + + + + +slack_bolt.context.say_stream API documentation + + + + + + + + + + + +
    +
    +
    +

    Module slack_bolt.context.say_stream

    +
    +
    +
    +
    +

    Sub-modules

    +
    +
    slack_bolt.context.say_stream.async_say_stream
    +
    +
    +
    +
    slack_bolt.context.say_stream.say_stream
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    Classes

    +
    +
    +class SayStream +(*,
    client: slack_sdk.web.client.WebClient,
    channel: str | None = None,
    recipient_team_id: str | None = None,
    recipient_user_id: str | None = None,
    thread_ts: str | None = None)
    +
    +
    +
    + +Expand source code + +
    class SayStream:
    +    client: WebClient
    +    channel: Optional[str]
    +    recipient_team_id: Optional[str]
    +    recipient_user_id: Optional[str]
    +    thread_ts: Optional[str]
    +
    +    def __init__(
    +        self,
    +        *,
    +        client: WebClient,
    +        channel: Optional[str] = None,
    +        recipient_team_id: Optional[str] = None,
    +        recipient_user_id: Optional[str] = None,
    +        thread_ts: Optional[str] = None,
    +    ):
    +        self.client = client
    +        self.channel = channel
    +        self.recipient_team_id = recipient_team_id
    +        self.recipient_user_id = recipient_user_id
    +        self.thread_ts = thread_ts
    +
    +    def __call__(
    +        self,
    +        *,
    +        buffer_size: Optional[int] = None,
    +        channel: Optional[str] = None,
    +        recipient_team_id: Optional[str] = None,
    +        recipient_user_id: Optional[str] = None,
    +        thread_ts: Optional[str] = None,
    +        icon_emoji: Optional[str] = None,
    +        icon_url: Optional[str] = None,
    +        username: Optional[str] = None,
    +        **kwargs,
    +    ) -> ChatStream:
    +        """Starts a new chat stream with context."""
    +        channel = channel or self.channel
    +        thread_ts = thread_ts or self.thread_ts
    +        if channel is None:
    +            raise ValueError("say_stream without channel here is unsupported")
    +        if thread_ts is None:
    +            raise ValueError("say_stream without thread_ts here is unsupported")
    +
    +        if buffer_size is not None:
    +            return self.client.chat_stream(
    +                buffer_size=buffer_size,
    +                channel=channel,
    +                recipient_team_id=recipient_team_id or self.recipient_team_id,
    +                recipient_user_id=recipient_user_id or self.recipient_user_id,
    +                thread_ts=thread_ts,
    +                icon_emoji=icon_emoji,
    +                icon_url=icon_url,
    +                username=username,
    +                **kwargs,
    +            )
    +        return self.client.chat_stream(
    +            channel=channel,
    +            recipient_team_id=recipient_team_id or self.recipient_team_id,
    +            recipient_user_id=recipient_user_id or self.recipient_user_id,
    +            thread_ts=thread_ts,
    +            icon_emoji=icon_emoji,
    +            icon_url=icon_url,
    +            username=username,
    +            **kwargs,
    +        )
    +
    +
    +

    Class variables

    +
    +
    var channel : str | None
    +
    +

    The type of the None singleton.

    +
    +
    var client : slack_sdk.web.client.WebClient
    +
    +

    The type of the None singleton.

    +
    +
    var recipient_team_id : str | None
    +
    +

    The type of the None singleton.

    +
    +
    var recipient_user_id : str | None
    +
    +

    The type of the None singleton.

    +
    +
    var thread_ts : str | None
    +
    +

    The type of the None singleton.

    +
    +
    +
    +
    +
    +
    + +
    + + + diff --git a/docs/reference/context/say_stream/say_stream.html b/docs/reference/context/say_stream/say_stream.html new file mode 100644 index 000000000..e7bc33bff --- /dev/null +++ b/docs/reference/context/say_stream/say_stream.html @@ -0,0 +1,183 @@ + + + + + + +slack_bolt.context.say_stream.say_stream API documentation + + + + + + + + + + + +
    +
    +
    +

    Module slack_bolt.context.say_stream.say_stream

    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    Classes

    +
    +
    +class SayStream +(*,
    client: slack_sdk.web.client.WebClient,
    channel: str | None = None,
    recipient_team_id: str | None = None,
    recipient_user_id: str | None = None,
    thread_ts: str | None = None)
    +
    +
    +
    + +Expand source code + +
    class SayStream:
    +    client: WebClient
    +    channel: Optional[str]
    +    recipient_team_id: Optional[str]
    +    recipient_user_id: Optional[str]
    +    thread_ts: Optional[str]
    +
    +    def __init__(
    +        self,
    +        *,
    +        client: WebClient,
    +        channel: Optional[str] = None,
    +        recipient_team_id: Optional[str] = None,
    +        recipient_user_id: Optional[str] = None,
    +        thread_ts: Optional[str] = None,
    +    ):
    +        self.client = client
    +        self.channel = channel
    +        self.recipient_team_id = recipient_team_id
    +        self.recipient_user_id = recipient_user_id
    +        self.thread_ts = thread_ts
    +
    +    def __call__(
    +        self,
    +        *,
    +        buffer_size: Optional[int] = None,
    +        channel: Optional[str] = None,
    +        recipient_team_id: Optional[str] = None,
    +        recipient_user_id: Optional[str] = None,
    +        thread_ts: Optional[str] = None,
    +        icon_emoji: Optional[str] = None,
    +        icon_url: Optional[str] = None,
    +        username: Optional[str] = None,
    +        **kwargs,
    +    ) -> ChatStream:
    +        """Starts a new chat stream with context."""
    +        channel = channel or self.channel
    +        thread_ts = thread_ts or self.thread_ts
    +        if channel is None:
    +            raise ValueError("say_stream without channel here is unsupported")
    +        if thread_ts is None:
    +            raise ValueError("say_stream without thread_ts here is unsupported")
    +
    +        if buffer_size is not None:
    +            return self.client.chat_stream(
    +                buffer_size=buffer_size,
    +                channel=channel,
    +                recipient_team_id=recipient_team_id or self.recipient_team_id,
    +                recipient_user_id=recipient_user_id or self.recipient_user_id,
    +                thread_ts=thread_ts,
    +                icon_emoji=icon_emoji,
    +                icon_url=icon_url,
    +                username=username,
    +                **kwargs,
    +            )
    +        return self.client.chat_stream(
    +            channel=channel,
    +            recipient_team_id=recipient_team_id or self.recipient_team_id,
    +            recipient_user_id=recipient_user_id or self.recipient_user_id,
    +            thread_ts=thread_ts,
    +            icon_emoji=icon_emoji,
    +            icon_url=icon_url,
    +            username=username,
    +            **kwargs,
    +        )
    +
    +
    +

    Class variables

    +
    +
    var channel : str | None
    +
    +

    The type of the None singleton.

    +
    +
    var client : slack_sdk.web.client.WebClient
    +
    +

    The type of the None singleton.

    +
    +
    var recipient_team_id : str | None
    +
    +

    The type of the None singleton.

    +
    +
    var recipient_user_id : str | None
    +
    +

    The type of the None singleton.

    +
    +
    var thread_ts : str | None
    +
    +

    The type of the None singleton.

    +
    +
    +
    +
    +
    +
    + +
    + + + diff --git a/docs/static/api-docs/slack_bolt/context/set_status/async_set_status.html b/docs/reference/context/set_status/async_set_status.html similarity index 94% rename from docs/static/api-docs/slack_bolt/context/set_status/async_set_status.html rename to docs/reference/context/set_status/async_set_status.html index d22fb3aa8..770583e4a 100644 --- a/docs/static/api-docs/slack_bolt/context/set_status/async_set_status.html +++ b/docs/reference/context/set_status/async_set_status.html @@ -3,7 +3,7 @@ - + slack_bolt.context.set_status.async_set_status API documentation @@ -70,11 +70,24 @@

    Classes

    self.channel_id = channel_id self.thread_ts = thread_ts - async def __call__(self, status: str) -> AsyncSlackResponse: + async def __call__( + self, + status: str, + loading_messages: Optional[List[str]] = None, + icon_emoji: Optional[str] = None, + icon_url: Optional[str] = None, + username: Optional[str] = None, + **kwargs, + ) -> AsyncSlackResponse: return await self.client.assistant_threads_setStatus( - status=status, channel_id=self.channel_id, thread_ts=self.thread_ts, + status=status, + loading_messages=loading_messages, + icon_emoji=icon_emoji, + icon_url=icon_url, + username=username, + **kwargs, )

    @@ -123,7 +136,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/context/set_status/index.html b/docs/reference/context/set_status/index.html similarity index 94% rename from docs/static/api-docs/slack_bolt/context/set_status/index.html rename to docs/reference/context/set_status/index.html index 5a2e8be48..380e37f4f 100644 --- a/docs/static/api-docs/slack_bolt/context/set_status/index.html +++ b/docs/reference/context/set_status/index.html @@ -3,7 +3,7 @@ - + slack_bolt.context.set_status API documentation @@ -81,11 +81,24 @@

    Classes

    self.channel_id = channel_id self.thread_ts = thread_ts - def __call__(self, status: str) -> SlackResponse: + def __call__( + self, + status: str, + loading_messages: Optional[List[str]] = None, + icon_emoji: Optional[str] = None, + icon_url: Optional[str] = None, + username: Optional[str] = None, + **kwargs, + ) -> SlackResponse: return self.client.assistant_threads_setStatus( - status=status, channel_id=self.channel_id, thread_ts=self.thread_ts, + status=status, + loading_messages=loading_messages, + icon_emoji=icon_emoji, + icon_url=icon_url, + username=username, + **kwargs, )
    @@ -140,7 +153,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/context/set_status/set_status.html b/docs/reference/context/set_status/set_status.html similarity index 94% rename from docs/static/api-docs/slack_bolt/context/set_status/set_status.html rename to docs/reference/context/set_status/set_status.html index 337fbf576..b0a0a9ee7 100644 --- a/docs/static/api-docs/slack_bolt/context/set_status/set_status.html +++ b/docs/reference/context/set_status/set_status.html @@ -3,7 +3,7 @@ - + slack_bolt.context.set_status.set_status API documentation @@ -70,11 +70,24 @@

    Classes

    self.channel_id = channel_id self.thread_ts = thread_ts - def __call__(self, status: str) -> SlackResponse: + def __call__( + self, + status: str, + loading_messages: Optional[List[str]] = None, + icon_emoji: Optional[str] = None, + icon_url: Optional[str] = None, + username: Optional[str] = None, + **kwargs, + ) -> SlackResponse: return self.client.assistant_threads_setStatus( - status=status, channel_id=self.channel_id, thread_ts=self.thread_ts, + status=status, + loading_messages=loading_messages, + icon_emoji=icon_emoji, + icon_url=icon_url, + username=username, + **kwargs, )
    @@ -123,7 +136,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/context/set_suggested_prompts/async_set_suggested_prompts.html b/docs/reference/context/set_suggested_prompts/async_set_suggested_prompts.html similarity index 95% rename from docs/static/api-docs/slack_bolt/context/set_suggested_prompts/async_set_suggested_prompts.html rename to docs/reference/context/set_suggested_prompts/async_set_suggested_prompts.html index ee7458fbc..1c7656456 100644 --- a/docs/static/api-docs/slack_bolt/context/set_suggested_prompts/async_set_suggested_prompts.html +++ b/docs/reference/context/set_suggested_prompts/async_set_suggested_prompts.html @@ -3,7 +3,7 @@ - + slack_bolt.context.set_suggested_prompts.async_set_suggested_prompts API documentation @@ -48,7 +48,7 @@

    Classes

    class AsyncSetSuggestedPrompts -(client: slack_sdk.web.async_client.AsyncWebClient,
    channel_id: str,
    thread_ts: str)
    +(client: slack_sdk.web.async_client.AsyncWebClient,
    channel_id: str,
    thread_ts: str | None = None)
    @@ -58,13 +58,13 @@

    Classes

    class AsyncSetSuggestedPrompts:
         client: AsyncWebClient
         channel_id: str
    -    thread_ts: str
    +    thread_ts: Optional[str]
     
         def __init__(
             self,
             client: AsyncWebClient,
             channel_id: str,
    -        thread_ts: str,
    +        thread_ts: Optional[str] = None,
         ):
             self.client = client
             self.channel_id = channel_id
    @@ -72,8 +72,9 @@ 

    Classes

    async def __call__( self, - prompts: List[Union[str, Dict[str, str]]], + prompts: Sequence[Union[str, Dict[str, str]]], title: Optional[str] = None, + thread_ts: Optional[str] = None, ) -> AsyncSlackResponse: prompts_arg: List[Dict[str, str]] = [] for prompt in prompts: @@ -84,7 +85,7 @@

    Classes

    return await self.client.assistant_threads_setSuggestedPrompts( channel_id=self.channel_id, - thread_ts=self.thread_ts, + thread_ts=thread_ts if thread_ts is not None else self.thread_ts, prompts=prompts_arg, title=title, )
    @@ -100,7 +101,7 @@

    Class variables

    The type of the None singleton.

    -
    var thread_ts : str
    +
    var thread_ts : str | None

    The type of the None singleton.

    @@ -135,7 +136,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/context/set_suggested_prompts/index.html b/docs/reference/context/set_suggested_prompts/index.html similarity index 95% rename from docs/static/api-docs/slack_bolt/context/set_suggested_prompts/index.html rename to docs/reference/context/set_suggested_prompts/index.html index f3084288a..cf606ae2f 100644 --- a/docs/static/api-docs/slack_bolt/context/set_suggested_prompts/index.html +++ b/docs/reference/context/set_suggested_prompts/index.html @@ -3,7 +3,7 @@ - + slack_bolt.context.set_suggested_prompts API documentation @@ -59,7 +59,7 @@

    Classes

    class SetSuggestedPrompts -(client: slack_sdk.web.client.WebClient, channel_id: str, thread_ts: str) +(client: slack_sdk.web.client.WebClient,
    channel_id: str,
    thread_ts: str | None = None)
    @@ -69,13 +69,13 @@

    Classes

    class SetSuggestedPrompts:
         client: WebClient
         channel_id: str
    -    thread_ts: str
    +    thread_ts: Optional[str]
     
         def __init__(
             self,
             client: WebClient,
             channel_id: str,
    -        thread_ts: str,
    +        thread_ts: Optional[str] = None,
         ):
             self.client = client
             self.channel_id = channel_id
    @@ -83,8 +83,9 @@ 

    Classes

    def __call__( self, - prompts: List[Union[str, Dict[str, str]]], + prompts: Sequence[Union[str, Dict[str, str]]], title: Optional[str] = None, + thread_ts: Optional[str] = None, ) -> SlackResponse: prompts_arg: List[Dict[str, str]] = [] for prompt in prompts: @@ -95,7 +96,7 @@

    Classes

    return self.client.assistant_threads_setSuggestedPrompts( channel_id=self.channel_id, - thread_ts=self.thread_ts, + thread_ts=thread_ts if thread_ts is not None else self.thread_ts, prompts=prompts_arg, title=title, )
    @@ -111,7 +112,7 @@

    Class variables

    The type of the None singleton.

    -
    var thread_ts : str
    +
    var thread_ts : str | None

    The type of the None singleton.

    @@ -152,7 +153,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/context/set_suggested_prompts/set_suggested_prompts.html b/docs/reference/context/set_suggested_prompts/set_suggested_prompts.html similarity index 94% rename from docs/static/api-docs/slack_bolt/context/set_suggested_prompts/set_suggested_prompts.html rename to docs/reference/context/set_suggested_prompts/set_suggested_prompts.html index bdb31a3ca..f034fc677 100644 --- a/docs/static/api-docs/slack_bolt/context/set_suggested_prompts/set_suggested_prompts.html +++ b/docs/reference/context/set_suggested_prompts/set_suggested_prompts.html @@ -3,7 +3,7 @@ - + slack_bolt.context.set_suggested_prompts.set_suggested_prompts API documentation @@ -48,7 +48,7 @@

    Classes

    class SetSuggestedPrompts -(client: slack_sdk.web.client.WebClient, channel_id: str, thread_ts: str) +(client: slack_sdk.web.client.WebClient,
    channel_id: str,
    thread_ts: str | None = None)
    @@ -58,13 +58,13 @@

    Classes

    class SetSuggestedPrompts:
         client: WebClient
         channel_id: str
    -    thread_ts: str
    +    thread_ts: Optional[str]
     
         def __init__(
             self,
             client: WebClient,
             channel_id: str,
    -        thread_ts: str,
    +        thread_ts: Optional[str] = None,
         ):
             self.client = client
             self.channel_id = channel_id
    @@ -72,8 +72,9 @@ 

    Classes

    def __call__( self, - prompts: List[Union[str, Dict[str, str]]], + prompts: Sequence[Union[str, Dict[str, str]]], title: Optional[str] = None, + thread_ts: Optional[str] = None, ) -> SlackResponse: prompts_arg: List[Dict[str, str]] = [] for prompt in prompts: @@ -84,7 +85,7 @@

    Classes

    return self.client.assistant_threads_setSuggestedPrompts( channel_id=self.channel_id, - thread_ts=self.thread_ts, + thread_ts=thread_ts if thread_ts is not None else self.thread_ts, prompts=prompts_arg, title=title, )
    @@ -100,7 +101,7 @@

    Class variables

    The type of the None singleton.

    -
    var thread_ts : str
    +
    var thread_ts : str | None

    The type of the None singleton.

    @@ -135,7 +136,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/context/set_title/async_set_title.html b/docs/reference/context/set_title/async_set_title.html similarity index 98% rename from docs/static/api-docs/slack_bolt/context/set_title/async_set_title.html rename to docs/reference/context/set_title/async_set_title.html index 9c195664c..e7db1ca1c 100644 --- a/docs/static/api-docs/slack_bolt/context/set_title/async_set_title.html +++ b/docs/reference/context/set_title/async_set_title.html @@ -3,7 +3,7 @@ - + slack_bolt.context.set_title.async_set_title API documentation @@ -123,7 +123,7 @@

    diff --git a/docs/static/api-docs/slack_bolt/context/set_title/index.html b/docs/reference/context/set_title/index.html similarity index 98% rename from docs/static/api-docs/slack_bolt/context/set_title/index.html rename to docs/reference/context/set_title/index.html index 4c88c8539..7ae070fe8 100644 --- a/docs/static/api-docs/slack_bolt/context/set_title/index.html +++ b/docs/reference/context/set_title/index.html @@ -3,7 +3,7 @@ - + slack_bolt.context.set_title API documentation @@ -140,7 +140,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/context/set_title/set_title.html b/docs/reference/context/set_title/set_title.html similarity index 98% rename from docs/static/api-docs/slack_bolt/context/set_title/set_title.html rename to docs/reference/context/set_title/set_title.html index 59a4498bf..cd4d1e27e 100644 --- a/docs/static/api-docs/slack_bolt/context/set_title/set_title.html +++ b/docs/reference/context/set_title/set_title.html @@ -3,7 +3,7 @@ - + slack_bolt.context.set_title.set_title API documentation @@ -123,7 +123,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/error/index.html b/docs/reference/error/index.html similarity index 97% rename from docs/static/api-docs/slack_bolt/error/index.html rename to docs/reference/error/index.html index 8e578eb61..9a9998e63 100644 --- a/docs/static/api-docs/slack_bolt/error/index.html +++ b/docs/reference/error/index.html @@ -3,7 +3,7 @@ - + slack_bolt.error API documentation @@ -72,7 +72,7 @@

    Subclasses

    class BoltUnhandledRequestError -(*,
    request: ForwardRef('BoltRequest') | ForwardRef('AsyncBoltRequest'),
    current_response: ForwardRef('BoltResponse') | None,
    last_global_middleware_name: str | None = None)
    +(*,
    request: BoltRequest | AsyncBoltRequest,
    current_response: BoltResponse | None,
    last_global_middleware_name: str | None = None)
    @@ -160,7 +160,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/index.html b/docs/reference/index.html similarity index 92% rename from docs/static/api-docs/slack_bolt/index.html rename to docs/reference/index.html index ced72594b..ac1666851 100644 --- a/docs/static/api-docs/slack_bolt/index.html +++ b/docs/reference/index.html @@ -3,7 +3,7 @@ - + slack_bolt API documentation @@ -36,9 +36,9 @@

    Package slack_bolt

    -

    A Python framework to build Slack apps in a flash with the latest platform features.Read the getting started guide and look at our code examples to learn how to build apps using Bolt.

    +

    A Python framework to build Slack apps in a flash with the latest platform features.Read the getting started guide and look at our code examples to learn how to build apps using Bolt.

    @@ -188,7 +188,7 @@

    Class variables

    class App -(*,
    logger: logging.Logger | None = None,
    name: str | None = None,
    process_before_response: bool = False,
    raise_error_for_unhandled_request: bool = False,
    signing_secret: str | None = None,
    token: str | None = None,
    token_verification_enabled: bool = True,
    client: slack_sdk.web.client.WebClient | None = None,
    before_authorize: Middleware | Callable[..., Any] | None = None,
    authorize: Callable[..., AuthorizeResult] | None = None,
    user_facing_authorize_error_message: str | None = None,
    installation_store: slack_sdk.oauth.installation_store.installation_store.InstallationStore | None = None,
    installation_store_bot_only: bool | None = None,
    request_verification_enabled: bool = True,
    ignoring_self_events_enabled: bool = True,
    ignoring_self_assistant_message_events_enabled: bool = True,
    ssl_check_enabled: bool = True,
    url_verification_enabled: bool = True,
    attaching_function_token_enabled: bool = True,
    oauth_settings: OAuthSettings | None = None,
    oauth_flow: OAuthFlow | None = None,
    verification_token: str | None = None,
    listener_executor: concurrent.futures._base.Executor | None = None,
    assistant_thread_context_store: AssistantThreadContextStore | None = None)
    +(*,
    logger: logging.Logger | None = None,
    name: str | None = None,
    process_before_response: bool = False,
    raise_error_for_unhandled_request: bool = False,
    signing_secret: str | None = None,
    token: str | None = None,
    token_verification_enabled: bool = True,
    client: slack_sdk.web.client.WebClient | None = None,
    before_authorize: Middleware | Callable[..., Any] | None = None,
    authorize: Callable[..., AuthorizeResult] | None = None,
    user_facing_authorize_error_message: str | None = None,
    installation_store: slack_sdk.oauth.installation_store.installation_store.InstallationStore | None = None,
    installation_store_bot_only: bool | None = None,
    request_verification_enabled: bool = True,
    ignoring_self_events_enabled: bool = True,
    ignoring_self_assistant_message_events_enabled: bool = True,
    ssl_check_enabled: bool = True,
    url_verification_enabled: bool = True,
    attaching_function_token_enabled: bool = True,
    oauth_settings: OAuthSettings | None = None,
    oauth_flow: OAuthFlow | None = None,
    verification_token: str | None = None,
    listener_executor: concurrent.futures._base.Executor | None = None,
    assistant_thread_context_store: AssistantThreadContextStore | None = None,
    attaching_conversation_kwargs_enabled: bool = True)
    @@ -235,6 +235,7 @@

    Class variables

    listener_executor: Optional[Executor] = None, # for AI Agents & Assistants assistant_thread_context_store: Optional[AssistantThreadContextStore] = None, + attaching_conversation_kwargs_enabled: bool = True, ): """Bolt App that provides functionalities to register middleware/listeners. @@ -257,10 +258,10 @@

    Class variables

    if __name__ == "__main__": app.start(port=int(os.environ.get("PORT", 3000))) - Refer to https://slack.dev/bolt-python/tutorial/getting-started for details. + Refer to https://docs.slack.dev/tools/bolt-python/creating-an-app for details. If you would like to build an OAuth app for enabling the app to run with multiple workspaces, - refer to https://slack.dev/bolt-python/concepts#authenticating-oauth to learn how to configure the app. + refer to https://docs.slack.dev/tools/bolt-python/concepts/authenticating-oauth to learn how to configure the app. Args: logger: The custom logger that can be used in this app. @@ -455,6 +456,7 @@

    Class variables

    listener_executor = ThreadPoolExecutor(max_workers=5) self._assistant_thread_context_store = assistant_thread_context_store + self._attaching_conversation_kwargs_enabled = attaching_conversation_kwargs_enabled self._process_before_response = process_before_response self._listener_runner = ThreadListenerRunner( @@ -769,7 +771,7 @@

    Class variables

    # Pass a function to this method app.middleware(middleware_func) - Refer to https://slack.dev/bolt-python/concepts#global-middleware for details. + Refer to https://docs.slack.dev/tools/bolt-python/concepts/global-middleware for details. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -815,7 +817,7 @@

    Class variables

    """ Deprecated: Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://api.slack.com/automation/functions/custom-bolt + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ Registers a new step from app listener. @@ -833,7 +835,7 @@

    Class variables

    # Pass Step to set up listeners app.step(ws) - Refer to https://api.slack.com/workflows/steps for details of steps from apps. + Refer to https://docs.slack.dev/legacy/legacy-steps-from-apps/ for details of steps from apps. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -850,7 +852,7 @@

    Class variables

    warnings.warn( ( "Steps from apps for legacy workflows are now deprecated. " - "Use new custom steps: https://api.slack.com/automation/functions/custom-bolt" + "Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/" ), category=DeprecationWarning, ) @@ -927,7 +929,7 @@

    Class variables

    # Pass a function to this method app.event("team_join")(ask_for_introduction) - Refer to https://api.slack.com/apis/connections/events-api for details of Events API. + Refer to https://docs.slack.dev/apis/events-api/ for details of Events API. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -939,10 +941,13 @@

    Class variables

    middleware: A list of lister middleware functions. Only when all the middleware call `next()` method, the listener function can be invoked. """ + middleware = list(middleware) if middleware else [] def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) primary_matcher = builtin_matchers.event(event, base_logger=self._base_logger) + if self._attaching_conversation_kwargs_enabled: + middleware.insert(0, AttachingConversationKwargs(self._assistant_thread_context_store)) return self._register_listener(list(functions), primary_matcher, matchers, middleware, True) return __call__ @@ -965,7 +970,7 @@

    Class variables

    # Pass a function to this method app.message(":wave:")(say_hello) - Refer to https://api.slack.com/events/message for details of `message` events. + Refer to https://docs.slack.dev/reference/events/message/ for details of `message` events. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -1000,6 +1005,8 @@

    Class variables

    primary_matcher = builtin_matchers.message_event( keyword=keyword, constraints=constraints, base_logger=self._base_logger ) + if self._attaching_conversation_kwargs_enabled: + middleware.insert(0, AttachingConversationKwargs(self._assistant_thread_context_store)) middleware.insert(0, MessageListenerMatches(keyword)) return self._register_listener(list(functions), primary_matcher, matchers, middleware, True) @@ -1011,6 +1018,7 @@

    Class variables

    matchers: Optional[Sequence[Callable[..., bool]]] = None, middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, auto_acknowledge: bool = True, + ack_timeout: int = 3, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new Function listener. This method can be used as either a decorator or a method. @@ -1039,13 +1047,17 @@

    Class variables

    Only when all the middleware call `next()` method, the listener function can be invoked. """ + if auto_acknowledge is True: + if ack_timeout != 3: + self._framework_logger.warning(warning_ack_timeout_has_no_effect(callback_id, ack_timeout)) + matchers = list(matchers) if matchers else [] middleware = list(middleware) if middleware else [] def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) primary_matcher = builtin_matchers.function_executed(callback_id=callback_id, base_logger=self._base_logger) - return self._register_listener(functions, primary_matcher, matchers, middleware, auto_acknowledge) + return self._register_listener(functions, primary_matcher, matchers, middleware, auto_acknowledge, ack_timeout) return __call__ @@ -1071,7 +1083,7 @@

    Class variables

    # Pass a function to this method app.command("/echo")(repeat_text) - Refer to https://api.slack.com/interactivity/slash-commands for details of Slash Commands. + Refer to https://docs.slack.dev/interactivity/implementing-slash-commands/ for details of Slash Commands. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -1118,7 +1130,7 @@

    Class variables

    # Pass a function to this method app.shortcut("open_modal")(open_modal) - Refer to https://api.slack.com/interactivity/shortcuts for details about Shortcuts. + Refer to https://docs.slack.dev/interactivity/implementing-shortcuts/ for details about Shortcuts. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -1186,9 +1198,9 @@

    Class variables

    # Pass a function to this method app.action("approve_button")(update_message) - * Refer to https://api.slack.com/reference/interaction-payloads/block-actions for actions in `blocks`. - * Refer to https://api.slack.com/legacy/message-buttons for actions in `attachments`. - * Refer to https://api.slack.com/dialogs for actions in dialogs. + * Refer to https://docs.slack.dev/reference/interaction-payloads/block_actions-payload/ for actions in `blocks`. + * Refer to https://docs.slack.dev/legacy/legacy-messaging/legacy-message-buttons/ for actions in `attachments`. + * Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for actions in dialogs. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -1214,7 +1226,7 @@

    Class variables

    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new `block_actions` action listener. - Refer to https://api.slack.com/reference/interaction-payloads/block-actions for details. + Refer to https://docs.slack.dev/reference/interaction-payloads/block_actions-payload/ for details. """ def __call__(*args, **kwargs): @@ -1231,7 +1243,7 @@

    Class variables

    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new `interactive_message` action listener. - Refer to https://api.slack.com/legacy/message-buttons for details.""" + Refer to https://docs.slack.dev/legacy/legacy-messaging/legacy-message-buttons/ for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1247,7 +1259,7 @@

    Class variables

    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new `dialog_submission` listener. - Refer to https://api.slack.com/dialogs for details.""" + Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1263,7 +1275,7 @@

    Class variables

    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new `dialog_cancellation` listener. - Refer to https://api.slack.com/dialogs for details.""" + Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1304,7 +1316,7 @@

    Class variables

    # Pass a function to this method app.view("view_1")(handle_submission) - Refer to https://api.slack.com/reference/interaction-payloads/views for details of payloads. + Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload for details of payloads. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -1330,7 +1342,9 @@

    Class variables

    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new `view_submission` listener. - Refer to https://api.slack.com/reference/interaction-payloads/views#view_submission for details.""" + Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for + details. + """ def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1346,7 +1360,7 @@

    Class variables

    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new `view_closed` listener. - Refer to https://api.slack.com/reference/interaction-payloads/views#view_closed for details.""" + Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_closed for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1387,8 +1401,8 @@

    Class variables

    Refer to the following documents for details: - * https://api.slack.com/reference/block-kit/block-elements#external_select - * https://api.slack.com/reference/block-kit/block-elements#external_multi_select + * https://docs.slack.dev/reference/block-kit/block-elements/select-menu-element#external_select + * https://docs.slack.dev/reference/block-kit/block-elements/multi-select-menu-element#external_multi_select To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -1428,7 +1442,7 @@

    Class variables

    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new `dialog_suggestion` listener. - Refer to https://api.slack.com/dialogs for details.""" + Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1480,6 +1494,7 @@

    Class variables

    proxy=self._client.proxy, headers=self._client.headers, team_id=req.context.team_id, + logger=self._client.logger, retry_handlers=self._client.retry_handlers.copy() if self._client.retry_handlers is not None else None, ) req.context["client"] = client_per_request @@ -1488,20 +1503,6 @@

    Class variables

    # It is intended for apps that start lazy listeners from their custom global middleware. req.context["listener_runner"] = self.listener_runner - # For AI Agents & Assistants - if is_assistant_event(req.body): - assistant = AssistantUtilities( - payload=to_event(req.body), # type:ignore[arg-type] - context=req.context, - thread_context_store=self._assistant_thread_context_store, - ) - req.context["say"] = assistant.say - req.context["set_status"] = assistant.set_status - req.context["set_title"] = assistant.set_title - req.context["set_suggested_prompts"] = assistant.set_suggested_prompts - req.context["get_thread_context"] = assistant.get_thread_context - req.context["save_thread_context"] = assistant.save_thread_context - @staticmethod def _to_listener_functions( kwargs: dict, @@ -1520,6 +1521,7 @@

    Class variables

    matchers: Optional[Sequence[Callable[..., bool]]], middleware: Optional[Sequence[Union[Callable, Middleware]]], auto_acknowledgement: bool = False, + ack_timeout: int = 3, ) -> Optional[Callable[..., Optional[BoltResponse]]]: value_to_return = None if not isinstance(functions, list): @@ -1546,10 +1548,11 @@

    Class variables

    CustomListener( app_name=self.name, ack_function=functions.pop(0), - lazy_functions=functions, + lazy_functions=functions, # type: ignore[arg-type] matchers=listener_matchers, middleware=listener_middleware, auto_acknowledgement=auto_acknowledgement, + ack_timeout=ack_timeout, base_logger=self._base_logger, ) ) @@ -1575,9 +1578,9 @@

    Class variables

    if __name__ == "__main__": app.start(port=int(os.environ.get("PORT", 3000)))
    -

    Refer to https://slack.dev/bolt-python/tutorial/getting-started for details.

    +

    Refer to https://docs.slack.dev/tools/bolt-python/creating-an-app for details.

    If you would like to build an OAuth app for enabling the app to run with multiple workspaces, -refer to https://slack.dev/bolt-python/concepts#authenticating-oauth to learn how to configure the app.

    +refer to https://docs.slack.dev/tools/bolt-python/concepts/authenticating-oauth to learn how to configure the app.

    Args

    logger
    @@ -1769,9 +1772,9 @@

    Methods

    # Pass a function to this method app.action("approve_button")(update_message) - * Refer to https://api.slack.com/reference/interaction-payloads/block-actions for actions in `blocks`. - * Refer to https://api.slack.com/legacy/message-buttons for actions in `attachments`. - * Refer to https://api.slack.com/dialogs for actions in dialogs. + * Refer to https://docs.slack.dev/reference/interaction-payloads/block_actions-payload/ for actions in `blocks`. + * Refer to https://docs.slack.dev/legacy/legacy-messaging/legacy-message-buttons/ for actions in `attachments`. + * Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for actions in dialogs. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -1800,9 +1803,9 @@

    Methods

    app.action("approve_button")(update_message)

    To learn available arguments for middleware/listeners, see slack_bolt.kwargs_injection.args's API document.

    Args

    @@ -1845,7 +1848,7 @@

    Args

    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new `interactive_message` action listener. - Refer to https://api.slack.com/legacy/message-buttons for details.""" + Refer to https://docs.slack.dev/legacy/legacy-messaging/legacy-message-buttons/ for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1855,7 +1858,7 @@

    Args

    return __call__

    Registers a new interactive_message action listener. -Refer to https://api.slack.com/legacy/message-buttons for details.

    +Refer to https://docs.slack.dev/legacy/legacy-messaging/legacy-message-buttons/ for details.

    def block_action(self,
    constraints: str | Pattern | Dict[str, str | Pattern],
    matchers: Sequence[Callable[..., bool]] | None = None,
    middleware: Sequence[Callable | Middleware] | None = None) ‑> Callable[..., Callable[..., BoltResponse | None] | None]
    @@ -1872,7 +1875,7 @@

    Args

    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new `block_actions` action listener. - Refer to https://api.slack.com/reference/interaction-payloads/block-actions for details. + Refer to https://docs.slack.dev/reference/interaction-payloads/block_actions-payload/ for details. """ def __call__(*args, **kwargs): @@ -1883,7 +1886,7 @@

    Args

    return __call__

    Registers a new block_actions action listener. -Refer to https://api.slack.com/reference/interaction-payloads/block-actions for details.

    +Refer to https://docs.slack.dev/reference/interaction-payloads/block_actions-payload/ for details.

    def block_suggestion(self,
    action_id: str | Pattern,
    matchers: Sequence[Callable[..., bool]] | None = None,
    middleware: Sequence[Callable | Middleware] | None = None) ‑> Callable[..., Callable[..., BoltResponse | None] | None]
    @@ -1937,7 +1940,7 @@

    Args

    # Pass a function to this method app.command("/echo")(repeat_text) - Refer to https://api.slack.com/interactivity/slash-commands for details of Slash Commands. + Refer to https://docs.slack.dev/interactivity/implementing-slash-commands/ for details of Slash Commands. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -1968,7 +1971,7 @@

    Args

    # Pass a function to this method app.command("/echo")(repeat_text)
    -

    Refer to https://api.slack.com/interactivity/slash-commands for details of Slash Commands.

    +

    Refer to https://docs.slack.dev/interactivity/implementing-slash-commands/ for details of Slash Commands.

    To learn available arguments for middleware/listeners, see slack_bolt.kwargs_injection.args's API document.

    Args

    @@ -2031,7 +2034,7 @@

    Args

    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new `dialog_cancellation` listener. - Refer to https://api.slack.com/dialogs for details.""" + Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -2041,7 +2044,7 @@

    Args

    return __call__

    Registers a new dialog_cancellation listener. -Refer to https://api.slack.com/dialogs for details.

    +Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.

    def dialog_submission(self,
    callback_id: str | Pattern,
    matchers: Sequence[Callable[..., bool]] | None = None,
    middleware: Sequence[Callable | Middleware] | None = None) ‑> Callable[..., Callable[..., BoltResponse | None] | None]
    @@ -2058,7 +2061,7 @@

    Args

    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new `dialog_submission` listener. - Refer to https://api.slack.com/dialogs for details.""" + Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -2068,7 +2071,7 @@

    Args

    return __call__

    Registers a new dialog_submission listener. -Refer to https://api.slack.com/dialogs for details.

    +Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.

    def dialog_suggestion(self,
    callback_id: str | Pattern,
    matchers: Sequence[Callable[..., bool]] | None = None,
    middleware: Sequence[Callable | Middleware] | None = None) ‑> Callable[..., Callable[..., BoltResponse | None] | None]
    @@ -2085,7 +2088,7 @@

    Args

    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new `dialog_suggestion` listener. - Refer to https://api.slack.com/dialogs for details.""" + Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -2095,7 +2098,7 @@

    Args

    return __call__

    Registers a new dialog_suggestion listener. -Refer to https://api.slack.com/dialogs for details.

    +Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.

    def dispatch(self,
    req: BoltRequest) ‑> BoltResponse
    @@ -2321,7 +2324,7 @@

    Args

    # Pass a function to this method app.event("team_join")(ask_for_introduction) - Refer to https://api.slack.com/apis/connections/events-api for details of Events API. + Refer to https://docs.slack.dev/apis/events-api/ for details of Events API. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -2333,10 +2336,13 @@

    Args

    middleware: A list of lister middleware functions. Only when all the middleware call `next()` method, the listener function can be invoked. """ + middleware = list(middleware) if middleware else [] def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) primary_matcher = builtin_matchers.event(event, base_logger=self._base_logger) + if self._attaching_conversation_kwargs_enabled: + middleware.insert(0, AttachingConversationKwargs(self._assistant_thread_context_store)) return self._register_listener(list(functions), primary_matcher, matchers, middleware, True) return __call__
    @@ -2353,7 +2359,7 @@

    Args

    # Pass a function to this method app.event("team_join")(ask_for_introduction) -

    Refer to https://api.slack.com/apis/connections/events-api for details of Events API.

    +

    Refer to https://docs.slack.dev/apis/events-api/ for details of Events API.

    To learn available arguments for middleware/listeners, see slack_bolt.kwargs_injection.args's API document.

    Args

    @@ -2369,7 +2375,7 @@

    Args

    -def function(self,
    callback_id: str | Pattern,
    matchers: Sequence[Callable[..., bool]] | None = None,
    middleware: Sequence[Callable | Middleware] | None = None,
    auto_acknowledge: bool = True) ‑> Callable[..., Callable[..., BoltResponse | None] | None]
    +def function(self,
    callback_id: str | Pattern,
    matchers: Sequence[Callable[..., bool]] | None = None,
    middleware: Sequence[Callable | Middleware] | None = None,
    auto_acknowledge: bool = True,
    ack_timeout: int = 3) ‑> Callable[..., Callable[..., BoltResponse | None] | None]
    @@ -2382,6 +2388,7 @@

    Args

    matchers: Optional[Sequence[Callable[..., bool]]] = None, middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, auto_acknowledge: bool = True, + ack_timeout: int = 3, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new Function listener. This method can be used as either a decorator or a method. @@ -2410,13 +2417,17 @@

    Args

    Only when all the middleware call `next()` method, the listener function can be invoked. """ + if auto_acknowledge is True: + if ack_timeout != 3: + self._framework_logger.warning(warning_ack_timeout_has_no_effect(callback_id, ack_timeout)) + matchers = list(matchers) if matchers else [] middleware = list(middleware) if middleware else [] def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) primary_matcher = builtin_matchers.function_executed(callback_id=callback_id, base_logger=self._base_logger) - return self._register_listener(functions, primary_matcher, matchers, middleware, auto_acknowledge) + return self._register_listener(functions, primary_matcher, matchers, middleware, auto_acknowledge, ack_timeout) return __call__
    @@ -2500,7 +2511,7 @@

    Args

    # Pass a function to this method app.message(":wave:")(say_hello) - Refer to https://api.slack.com/events/message for details of `message` events. + Refer to https://docs.slack.dev/reference/events/message/ for details of `message` events. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -2535,6 +2546,8 @@

    Args

    primary_matcher = builtin_matchers.message_event( keyword=keyword, constraints=constraints, base_logger=self._base_logger ) + if self._attaching_conversation_kwargs_enabled: + middleware.insert(0, AttachingConversationKwargs(self._assistant_thread_context_store)) middleware.insert(0, MessageListenerMatches(keyword)) return self._register_listener(list(functions), primary_matcher, matchers, middleware, True) @@ -2551,7 +2564,7 @@

    Args

    # Pass a function to this method app.message(":wave:")(say_hello) -

    Refer to https://api.slack.com/events/message for details of message events.

    +

    Refer to https://docs.slack.dev/reference/events/message/ for details of message events.

    To learn available arguments for middleware/listeners, see slack_bolt.kwargs_injection.args's API document.

    Args

    @@ -2611,7 +2624,7 @@

    Args

    # Pass a function to this method app.middleware(middleware_func) - Refer to https://slack.dev/bolt-python/concepts#global-middleware for details. + Refer to https://docs.slack.dev/tools/bolt-python/concepts/global-middleware for details. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -2649,7 +2662,7 @@

    Args

    # Pass a function to this method app.middleware(middleware_func) -

    Refer to https://slack.dev/bolt-python/concepts#global-middleware for details.

    +

    Refer to https://docs.slack.dev/tools/bolt-python/concepts/global-middleware for details.

    To learn available arguments for middleware/listeners, see slack_bolt.kwargs_injection.args's API document.

    Args

    @@ -2694,8 +2707,8 @@

    Args

    Refer to the following documents for details: - * https://api.slack.com/reference/block-kit/block-elements#external_select - * https://api.slack.com/reference/block-kit/block-elements#external_multi_select + * https://docs.slack.dev/reference/block-kit/block-elements/select-menu-element#external_select + * https://docs.slack.dev/reference/block-kit/block-elements/multi-select-menu-element#external_multi_select To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -2735,8 +2748,8 @@

    Args

    Refer to the following documents for details:

    To learn available arguments for middleware/listeners, see slack_bolt.kwargs_injection.args's API document.

    Args

    @@ -2782,7 +2795,7 @@

    Args

    # Pass a function to this method app.shortcut("open_modal")(open_modal) - Refer to https://api.slack.com/interactivity/shortcuts for details about Shortcuts. + Refer to https://docs.slack.dev/interactivity/implementing-shortcuts/ for details about Shortcuts. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -2819,7 +2832,7 @@

    Args

    # Pass a function to this method app.shortcut("open_modal")(open_modal) -

    Refer to https://api.slack.com/interactivity/shortcuts for details about Shortcuts.

    +

    Refer to https://docs.slack.dev/interactivity/implementing-shortcuts/ for details about Shortcuts.

    To learn available arguments for middleware/listeners, see slack_bolt.kwargs_injection.args's API document.

    Args

    @@ -2905,7 +2918,7 @@

    Args

    """ Deprecated: Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://api.slack.com/automation/functions/custom-bolt + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ Registers a new step from app listener. @@ -2923,7 +2936,7 @@

    Args

    # Pass Step to set up listeners app.step(ws) - Refer to https://api.slack.com/workflows/steps for details of steps from apps. + Refer to https://docs.slack.dev/legacy/legacy-steps-from-apps/ for details of steps from apps. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -2940,7 +2953,7 @@

    Args

    warnings.warn( ( "Steps from apps for legacy workflows are now deprecated. " - "Use new custom steps: https://api.slack.com/automation/functions/custom-bolt" + "Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/" ), category=DeprecationWarning, ) @@ -2962,7 +2975,7 @@

    Args

    Deprecated

    Steps from apps for legacy workflows are now deprecated. -Use new custom steps: https://api.slack.com/automation/functions/custom-bolt

    +Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/

    Registers a new step from app listener.

    Unlike others, this method doesn't behave as a decorator. If you want to register a step from app by a decorator, use WorkflowStepBuilder's methods.

    @@ -2977,7 +2990,7 @@

    Args

    # Pass Step to set up listeners app.step(ws) -

    Refer to https://api.slack.com/workflows/steps for details of steps from apps.

    +

    Refer to https://docs.slack.dev/legacy/legacy-steps-from-apps/ for details of steps from apps.

    To learn available arguments for middleware/listeners, see slack_bolt.kwargs_injection.args's API document.

    For further information about WorkflowStep specific function arguments such as configure, update, complete, and fail, @@ -3048,7 +3061,7 @@

    Args

    # Pass a function to this method app.view("view_1")(handle_submission) - Refer to https://api.slack.com/reference/interaction-payloads/views for details of payloads. + Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload for details of payloads. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -3089,7 +3102,7 @@

    Args

    # Pass a function to this method app.view("view_1")(handle_submission) -

    Refer to https://api.slack.com/reference/interaction-payloads/views for details of payloads.

    +

    Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload for details of payloads.

    To learn available arguments for middleware/listeners, see slack_bolt.kwargs_injection.args's API document.

    Args

    @@ -3118,7 +3131,7 @@

    Args

    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new `view_closed` listener. - Refer to https://api.slack.com/reference/interaction-payloads/views#view_closed for details.""" + Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_closed for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -3128,7 +3141,7 @@

    Args

    return __call__

    Registers a new view_closed listener. -Refer to https://api.slack.com/reference/interaction-payloads/views#view_closed for details.

    +Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_closed for details.

    def view_submission(self,
    constraints: str | Pattern,
    matchers: Sequence[Callable[..., bool]] | None = None,
    middleware: Sequence[Callable | Middleware] | None = None) ‑> Callable[..., Callable[..., BoltResponse | None] | None]
    @@ -3145,7 +3158,9 @@

    Args

    middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new `view_submission` listener. - Refer to https://api.slack.com/reference/interaction-payloads/views#view_submission for details.""" + Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for + details. + """ def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -3155,13 +3170,14 @@

    Args

    return __call__

    Registers a new view_submission listener. -Refer to https://api.slack.com/reference/interaction-payloads/views#view_submission for details.

    +Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for +details.

    class Args -(*,
    logger: logging.Logger,
    client: slack_sdk.web.client.WebClient,
    req: BoltRequest,
    resp: BoltResponse,
    context: BoltContext,
    body: Dict[str, Any],
    payload: Dict[str, Any],
    options: Dict[str, Any] | None = None,
    shortcut: Dict[str, Any] | None = None,
    action: Dict[str, Any] | None = None,
    view: Dict[str, Any] | None = None,
    command: Dict[str, Any] | None = None,
    event: Dict[str, Any] | None = None,
    message: Dict[str, Any] | None = None,
    ack: Ack,
    say: Say,
    respond: Respond,
    complete: Complete,
    fail: Fail,
    set_status: SetStatus | None = None,
    set_title: SetTitle | None = None,
    set_suggested_prompts: SetSuggestedPrompts | None = None,
    get_thread_context: GetThreadContext | None = None,
    save_thread_context: SaveThreadContext | None = None,
    next: Callable[[], None],
    **kwargs)
    +(*,
    logger: logging.Logger,
    client: slack_sdk.web.client.WebClient,
    req: BoltRequest,
    resp: BoltResponse,
    context: BoltContext,
    body: Dict[str, Any],
    payload: Dict[str, Any],
    options: Dict[str, Any] | None = None,
    shortcut: Dict[str, Any] | None = None,
    action: Dict[str, Any] | None = None,
    view: Dict[str, Any] | None = None,
    command: Dict[str, Any] | None = None,
    event: Dict[str, Any] | None = None,
    message: Dict[str, Any] | None = None,
    ack: Ack,
    say: Say,
    respond: Respond,
    complete: Complete,
    fail: Fail,
    set_status: SetStatus | None = None,
    set_title: SetTitle | None = None,
    set_suggested_prompts: SetSuggestedPrompts | None = None,
    get_thread_context: GetThreadContext | None = None,
    save_thread_context: SaveThreadContext | None = None,
    say_stream: SayStream | None = None,
    next: Callable[[], None],
    **kwargs)
    @@ -3252,6 +3268,8 @@

    Args

    """`get_thread_context()` utility function for AI Agents & Assistants""" save_thread_context: Optional[SaveThreadContext] """`save_thread_context()` utility function for AI Agents & Assistants""" + say_stream: Optional[SayStream] + """`say_stream()` utility function for conversations, AI Agents & Assistants""" # middleware next: Callable[[], None] """`next()` utility function, which tells the middleware chain that it can continue with the next one""" @@ -3285,6 +3303,7 @@

    Args

    set_suggested_prompts: Optional[SetSuggestedPrompts] = None, get_thread_context: Optional[GetThreadContext] = None, save_thread_context: Optional[SaveThreadContext] = None, + say_stream: Optional[SayStream] = None, # As this method is not supposed to be invoked by bolt-python users, # the naming conflict with the built-in one affects # only the internals of this method @@ -3318,6 +3337,7 @@

    Args

    self.set_suggested_prompts = set_suggested_prompts self.get_thread_context = get_thread_context self.save_thread_context = save_thread_context + self.say_stream = say_stream self.next: Callable[[], None] = next self.next_: Callable[[], None] = next @@ -3441,6 +3461,10 @@

    Class variables

    say() utility function, which calls chat.postMessage API with the associated channel ID

    +
    var say_streamSayStream | None
    +
    +

    say_stream() utility function for conversations, AI Agents & Assistants

    +
    var set_statusSetStatus | None

    set_status() utility function for AI Agents & Assistants

    @@ -3513,7 +3537,7 @@

    Class variables

    self.build_listener( listener_or_functions=func, matchers=all_matchers, - middleware=middleware, # type:ignore[arg-type] + middleware=middleware, # type: ignore[arg-type] ) ) return func @@ -3552,7 +3576,7 @@

    Class variables

    self.build_listener( listener_or_functions=func, matchers=all_matchers, - middleware=middleware, # type:ignore[arg-type] + middleware=middleware, # type: ignore[arg-type] ) ) return func @@ -3591,7 +3615,7 @@

    Class variables

    self.build_listener( listener_or_functions=func, matchers=all_matchers, - middleware=middleware, # type:ignore[arg-type] + middleware=middleware, # type: ignore[arg-type] ) ) return func @@ -3630,7 +3654,7 @@

    Class variables

    self.build_listener( listener_or_functions=func, matchers=all_matchers, - middleware=middleware, # type:ignore[arg-type] + middleware=middleware, # type: ignore[arg-type] ) ) return func @@ -3660,13 +3684,13 @@

    Class variables

    ): return [CustomListenerMatcher(app_name=self.app_name, func=primary_matcher)] + ( custom_matchers or [] - ) # type:ignore[operator] + ) # type: ignore[operator] @staticmethod def default_thread_context_changed(save_thread_context: SaveThreadContext, payload: dict): save_thread_context(payload["assistant_thread"]["context"]) - def process( # type:ignore[return] + def process( # type: ignore[return] self, *, req: BoltRequest, resp: BoltResponse, next: Callable[[], BoltResponse] ) -> Optional[BoltResponse]: if self._thread_context_changed_listeners is None: @@ -3682,6 +3706,15 @@

    Class variables

    if listeners is not None: for listener in listeners: if listener.matches(req=req, resp=resp): + middleware_resp, next_was_not_called = listener.run_middleware(req=req, resp=resp) + if next_was_not_called: + if middleware_resp is not None: + return middleware_resp + # The listener middleware didn't call next(). + # Skip this listener and try the next one. + continue + if middleware_resp is not None: + resp = middleware_resp return listener_runner.run( request=req, response=resp, @@ -3701,13 +3734,14 @@

    Class variables

    middleware: Optional[List[Middleware]] = None, base_logger: Optional[Logger] = None, ) -> Listener: - if isinstance(listener_or_functions, Callable): # type:ignore[arg-type] - listener_or_functions = [listener_or_functions] # type:ignore[list-item] + if isinstance(listener_or_functions, Callable): # type: ignore[arg-type] + listener_or_functions = [listener_or_functions] # type: ignore[list-item] if isinstance(listener_or_functions, Listener): return listener_or_functions elif isinstance(listener_or_functions, list): middleware = middleware if middleware else [] + middleware.insert(0, AttachingConversationKwargs(self.thread_context_store)) functions = listener_or_functions ack_function = functions.pop(0) @@ -3716,7 +3750,7 @@

    Class variables

    for matcher in matchers: if isinstance(matcher, ListenerMatcher): listener_matchers.append(matcher) - elif isinstance(matcher, Callable): # type:ignore[arg-type] + elif isinstance(matcher, Callable): # type: ignore[arg-type] listener_matchers.append( build_listener_matcher( func=matcher, @@ -3795,7 +3829,7 @@

    Methods

    self.build_listener( listener_or_functions=func, matchers=all_matchers, - middleware=middleware, # type:ignore[arg-type] + middleware=middleware, # type: ignore[arg-type] ) ) return func @@ -3835,13 +3869,14 @@

    Methods

    middleware: Optional[List[Middleware]] = None, base_logger: Optional[Logger] = None, ) -> Listener: - if isinstance(listener_or_functions, Callable): # type:ignore[arg-type] - listener_or_functions = [listener_or_functions] # type:ignore[list-item] + if isinstance(listener_or_functions, Callable): # type: ignore[arg-type] + listener_or_functions = [listener_or_functions] # type: ignore[list-item] if isinstance(listener_or_functions, Listener): return listener_or_functions elif isinstance(listener_or_functions, list): middleware = middleware if middleware else [] + middleware.insert(0, AttachingConversationKwargs(self.thread_context_store)) functions = listener_or_functions ack_function = functions.pop(0) @@ -3850,7 +3885,7 @@

    Methods

    for matcher in matchers: if isinstance(matcher, ListenerMatcher): listener_matchers.append(matcher) - elif isinstance(matcher, Callable): # type:ignore[arg-type] + elif isinstance(matcher, Callable): # type: ignore[arg-type] listener_matchers.append( build_listener_matcher( func=matcher, @@ -3896,7 +3931,7 @@

    Methods

    self.build_listener( listener_or_functions=func, matchers=all_matchers, - middleware=middleware, # type:ignore[arg-type] + middleware=middleware, # type: ignore[arg-type] ) ) return func @@ -3945,7 +3980,7 @@

    Methods

    self.build_listener( listener_or_functions=func, matchers=all_matchers, - middleware=middleware, # type:ignore[arg-type] + middleware=middleware, # type: ignore[arg-type] ) ) return func @@ -3994,7 +4029,7 @@

    Methods

    self.build_listener( listener_or_functions=func, matchers=all_matchers, - middleware=middleware, # type:ignore[arg-type] + middleware=middleware, # type: ignore[arg-type] ) ) return func @@ -4167,7 +4202,7 @@

    Methods

    # The return type is intentionally string to avoid circular imports @property - def listener_runner(self) -> "ThreadListenerRunner": # type: ignore[name-defined] + def listener_runner(self) -> "ThreadListenerRunner": """The properly configured listener_runner that is available for middleware/listeners.""" return self["listener_runner"] @@ -4236,7 +4271,7 @@

    Methods

    Callable `say()` function """ if "say" not in self: - self["say"] = Say(client=self.client, channel=self.channel_id, thread_ts=self.thread_ts) + self["say"] = Say(client=self.client, channel=self.channel_id) return self["say"] @property @@ -4329,6 +4364,10 @@

    Methods

    def get_thread_context(self) -> Optional[GetThreadContext]: return self.get("get_thread_context") + @property + def say_stream(self) -> Optional[SayStream]: + return self.get("say_stream") + @property def save_thread_context(self) -> Optional[SaveThreadContext]: return self.get("save_thread_context") @@ -4544,7 +4583,7 @@

    Returns

    Expand source code
    @property
    -def listener_runner(self) -> "ThreadListenerRunner":  # type: ignore[name-defined]
    +def listener_runner(self) -> "ThreadListenerRunner":
         """The properly configured listener_runner that is available for middleware/listeners."""
         return self["listener_runner"]
    @@ -4634,7 +4673,7 @@

    Returns

    Callable `say()` function """ if "say" not in self: - self["say"] = Say(client=self.client, channel=self.channel_id, thread_ts=self.thread_ts) + self["say"] = Say(client=self.client, channel=self.channel_id) return self["say"]

    say() function for this request.

    @@ -4652,6 +4691,18 @@

    Returns

    Returns

    Callable say() function

    +
    prop say_streamSayStream | None
    +
    +
    + +Expand source code + +
    @property
    +def say_stream(self) -> Optional[SayStream]:
    +    return self.get("say_stream")
    +
    +
    +
    prop set_statusSetStatus | None
    @@ -5055,6 +5106,7 @@

    Methods

    class Complete:
         client: WebClient
         function_execution_id: Optional[str]
    +    _called: bool
     
         def __init__(
             self,
    @@ -5063,6 +5115,7 @@ 

    Methods

    ): self.client = client self.function_execution_id = function_execution_id + self._called = False def __call__(self, outputs: Optional[Dict[str, Any]] = None) -> SlackResponse: """Signal the successful completion of the custom function. @@ -5079,7 +5132,16 @@

    Methods

    if self.function_execution_id is None: raise ValueError("complete is unsupported here as there is no function_execution_id") - return self.client.functions_completeSuccess(function_execution_id=self.function_execution_id, outputs=outputs or {})
    + self._called = True + return self.client.functions_completeSuccess(function_execution_id=self.function_execution_id, outputs=outputs or {}) + + def has_been_called(self) -> bool: + """Check if this complete function has been called. + + Returns: + bool: True if the complete function has been called, False otherwise. + """ + return self._called

    Class variables

    @@ -5093,6 +5155,32 @@

    Class variables

    The type of the None singleton.

    +

    Methods

    +
    +
    +def has_been_called(self) ‑> bool +
    +
    +
    + +Expand source code + +
    def has_been_called(self) -> bool:
    +    """Check if this complete function has been called.
    +
    +    Returns:
    +        bool: True if the complete function has been called, False otherwise.
    +    """
    +    return self._called
    +
    +

    Check if this complete function has been called.

    +

    Returns

    +
    +
    bool
    +
    True if the complete function has been called, False otherwise.
    +
    +
    +
    class CustomListenerMatcher @@ -5171,6 +5259,7 @@

    Inherited members

    class Fail:
         client: WebClient
         function_execution_id: Optional[str]
    +    _called: bool
     
         def __init__(
             self,
    @@ -5179,6 +5268,7 @@ 

    Inherited members

    ): self.client = client self.function_execution_id = function_execution_id + self._called = False def __call__(self, error: str) -> SlackResponse: """Signal that the custom function failed to complete. @@ -5195,7 +5285,16 @@

    Inherited members

    if self.function_execution_id is None: raise ValueError("fail is unsupported here as there is no function_execution_id") - return self.client.functions_completeError(function_execution_id=self.function_execution_id, error=error)
    + self._called = True + return self.client.functions_completeError(function_execution_id=self.function_execution_id, error=error) + + def has_been_called(self) -> bool: + """Check if this fail function has been called. + + Returns: + bool: True if the fail function has been called, False otherwise. + """ + return self._called

    Class variables

    @@ -5209,6 +5308,32 @@

    Class variables

    The type of the None singleton.

    +

    Methods

    +
    +
    +def has_been_called(self) ‑> bool +
    +
    +
    + +Expand source code + +
    def has_been_called(self) -> bool:
    +    """Check if this fail function has been called.
    +
    +    Returns:
    +        bool: True if the fail function has been called, False otherwise.
    +    """
    +    return self._called
    +
    +

    Check if this fail function has been called.

    +

    Returns

    +
    +
    bool
    +
    True if the fail function has been called, False otherwise.
    +
    +
    +
    class FileAssistantThreadContextStore @@ -5309,6 +5434,7 @@

    Methods

    ack_function: Callable[..., BoltResponse] lazy_functions: Sequence[Callable[..., None]] auto_acknowledgement: bool + ack_timeout: int = 3 def matches( self, @@ -5374,6 +5500,10 @@

    Class variables

    The type of the None singleton.

    +
    var ack_timeout : int
    +
    +

    The type of the None singleton.

    +
    var auto_acknowledgement : bool

    The type of the None singleton.

    @@ -5673,6 +5803,7 @@

    Class variables

    icon_emoji: Optional[str] = None, icon_url: Optional[str] = None, username: Optional[str] = None, + markdown_text: Optional[str] = None, mrkdwn: Optional[bool] = None, link_names: Optional[bool] = None, parse: Optional[str] = None, # none, full @@ -5698,6 +5829,7 @@

    Class variables

    icon_emoji=icon_emoji, icon_url=icon_url, username=username, + markdown_text=markdown_text, mrkdwn=mrkdwn, link_names=link_names, parse=parse, @@ -5744,6 +5876,106 @@

    Class variables

    +
    +class SayStream +(*,
    client: slack_sdk.web.client.WebClient,
    channel: str | None = None,
    recipient_team_id: str | None = None,
    recipient_user_id: str | None = None,
    thread_ts: str | None = None)
    +
    +
    +
    + +Expand source code + +
    class SayStream:
    +    client: WebClient
    +    channel: Optional[str]
    +    recipient_team_id: Optional[str]
    +    recipient_user_id: Optional[str]
    +    thread_ts: Optional[str]
    +
    +    def __init__(
    +        self,
    +        *,
    +        client: WebClient,
    +        channel: Optional[str] = None,
    +        recipient_team_id: Optional[str] = None,
    +        recipient_user_id: Optional[str] = None,
    +        thread_ts: Optional[str] = None,
    +    ):
    +        self.client = client
    +        self.channel = channel
    +        self.recipient_team_id = recipient_team_id
    +        self.recipient_user_id = recipient_user_id
    +        self.thread_ts = thread_ts
    +
    +    def __call__(
    +        self,
    +        *,
    +        buffer_size: Optional[int] = None,
    +        channel: Optional[str] = None,
    +        recipient_team_id: Optional[str] = None,
    +        recipient_user_id: Optional[str] = None,
    +        thread_ts: Optional[str] = None,
    +        icon_emoji: Optional[str] = None,
    +        icon_url: Optional[str] = None,
    +        username: Optional[str] = None,
    +        **kwargs,
    +    ) -> ChatStream:
    +        """Starts a new chat stream with context."""
    +        channel = channel or self.channel
    +        thread_ts = thread_ts or self.thread_ts
    +        if channel is None:
    +            raise ValueError("say_stream without channel here is unsupported")
    +        if thread_ts is None:
    +            raise ValueError("say_stream without thread_ts here is unsupported")
    +
    +        if buffer_size is not None:
    +            return self.client.chat_stream(
    +                buffer_size=buffer_size,
    +                channel=channel,
    +                recipient_team_id=recipient_team_id or self.recipient_team_id,
    +                recipient_user_id=recipient_user_id or self.recipient_user_id,
    +                thread_ts=thread_ts,
    +                icon_emoji=icon_emoji,
    +                icon_url=icon_url,
    +                username=username,
    +                **kwargs,
    +            )
    +        return self.client.chat_stream(
    +            channel=channel,
    +            recipient_team_id=recipient_team_id or self.recipient_team_id,
    +            recipient_user_id=recipient_user_id or self.recipient_user_id,
    +            thread_ts=thread_ts,
    +            icon_emoji=icon_emoji,
    +            icon_url=icon_url,
    +            username=username,
    +            **kwargs,
    +        )
    +
    +
    +

    Class variables

    +
    +
    var channel : str | None
    +
    +

    The type of the None singleton.

    +
    +
    var client : slack_sdk.web.client.WebClient
    +
    +

    The type of the None singleton.

    +
    +
    var recipient_team_id : str | None
    +
    +

    The type of the None singleton.

    +
    +
    var recipient_user_id : str | None
    +
    +

    The type of the None singleton.

    +
    +
    var thread_ts : str | None
    +
    +

    The type of the None singleton.

    +
    +
    +
    class SetStatus (client: slack_sdk.web.client.WebClient, channel_id: str, thread_ts: str) @@ -5768,11 +6000,24 @@

    Class variables

    self.channel_id = channel_id self.thread_ts = thread_ts - def __call__(self, status: str) -> SlackResponse: + def __call__( + self, + status: str, + loading_messages: Optional[List[str]] = None, + icon_emoji: Optional[str] = None, + icon_url: Optional[str] = None, + username: Optional[str] = None, + **kwargs, + ) -> SlackResponse: return self.client.assistant_threads_setStatus( - status=status, channel_id=self.channel_id, thread_ts=self.thread_ts, + status=status, + loading_messages=loading_messages, + icon_emoji=icon_emoji, + icon_url=icon_url, + username=username, + **kwargs, )
    @@ -5794,7 +6039,7 @@

    Class variables

    class SetSuggestedPrompts -(client: slack_sdk.web.client.WebClient, channel_id: str, thread_ts: str) +(client: slack_sdk.web.client.WebClient,
    channel_id: str,
    thread_ts: str | None = None)
    @@ -5804,13 +6049,13 @@

    Class variables

    class SetSuggestedPrompts:
         client: WebClient
         channel_id: str
    -    thread_ts: str
    +    thread_ts: Optional[str]
     
         def __init__(
             self,
             client: WebClient,
             channel_id: str,
    -        thread_ts: str,
    +        thread_ts: Optional[str] = None,
         ):
             self.client = client
             self.channel_id = channel_id
    @@ -5818,8 +6063,9 @@ 

    Class variables

    def __call__( self, - prompts: List[Union[str, Dict[str, str]]], + prompts: Sequence[Union[str, Dict[str, str]]], title: Optional[str] = None, + thread_ts: Optional[str] = None, ) -> SlackResponse: prompts_arg: List[Dict[str, str]] = [] for prompt in prompts: @@ -5830,7 +6076,7 @@

    Class variables

    return self.client.assistant_threads_setSuggestedPrompts( channel_id=self.channel_id, - thread_ts=self.thread_ts, + thread_ts=thread_ts if thread_ts is not None else self.thread_ts, prompts=prompts_arg, title=title, )
    @@ -5846,7 +6092,7 @@

    Class variables

    The type of the None singleton.

    -
    var thread_ts : str
    +
    var thread_ts : str | None

    The type of the None singleton.

    @@ -6004,6 +6250,7 @@

    Args

    response
  • save_thread_context
  • say
  • +
  • say_stream
  • set_status
  • set_suggested_prompts
  • set_title
  • @@ -6051,6 +6298,7 @@

    BoltC
  • respond
  • save_thread_context
  • say
  • +
  • say_stream
  • set_status
  • set_suggested_prompts
  • set_title
  • @@ -6088,6 +6336,7 @@

    Complete
  • client
  • function_execution_id
  • +
  • has_been_called
  • @@ -6104,6 +6353,7 @@

    Fail

  • client
  • function_execution_id
  • +
  • has_been_called
  • @@ -6117,6 +6367,7 @@

    Listener

  • +

    SayStream

    + +
  • +
  • SetStatus

    • channel_id
    • @@ -6182,7 +6443,7 @@

      SetTitle diff --git a/docs/static/api-docs/slack_bolt/kwargs_injection/args.html b/docs/reference/kwargs_injection/args.html similarity index 96% rename from docs/static/api-docs/slack_bolt/kwargs_injection/args.html rename to docs/reference/kwargs_injection/args.html index 9d2eb2c40..bbba71eb8 100644 --- a/docs/static/api-docs/slack_bolt/kwargs_injection/args.html +++ b/docs/reference/kwargs_injection/args.html @@ -3,7 +3,7 @@ - + slack_bolt.kwargs_injection.args API documentation @@ -48,7 +48,7 @@

      Classes

      class Args -(*,
      logger: logging.Logger,
      client: slack_sdk.web.client.WebClient,
      req: BoltRequest,
      resp: BoltResponse,
      context: BoltContext,
      body: Dict[str, Any],
      payload: Dict[str, Any],
      options: Dict[str, Any] | None = None,
      shortcut: Dict[str, Any] | None = None,
      action: Dict[str, Any] | None = None,
      view: Dict[str, Any] | None = None,
      command: Dict[str, Any] | None = None,
      event: Dict[str, Any] | None = None,
      message: Dict[str, Any] | None = None,
      ack: Ack,
      say: Say,
      respond: Respond,
      complete: Complete,
      fail: Fail,
      set_status: SetStatus | None = None,
      set_title: SetTitle | None = None,
      set_suggested_prompts: SetSuggestedPrompts | None = None,
      get_thread_context: GetThreadContext | None = None,
      save_thread_context: SaveThreadContext | None = None,
      next: Callable[[], None],
      **kwargs)
      +(*,
      logger: logging.Logger,
      client: slack_sdk.web.client.WebClient,
      req: BoltRequest,
      resp: BoltResponse,
      context: BoltContext,
      body: Dict[str, Any],
      payload: Dict[str, Any],
      options: Dict[str, Any] | None = None,
      shortcut: Dict[str, Any] | None = None,
      action: Dict[str, Any] | None = None,
      view: Dict[str, Any] | None = None,
      command: Dict[str, Any] | None = None,
      event: Dict[str, Any] | None = None,
      message: Dict[str, Any] | None = None,
      ack: Ack,
      say: Say,
      respond: Respond,
      complete: Complete,
      fail: Fail,
      set_status: SetStatus | None = None,
      set_title: SetTitle | None = None,
      set_suggested_prompts: SetSuggestedPrompts | None = None,
      get_thread_context: GetThreadContext | None = None,
      save_thread_context: SaveThreadContext | None = None,
      say_stream: SayStream | None = None,
      next: Callable[[], None],
      **kwargs)
      @@ -139,6 +139,8 @@

      Classes

      """`get_thread_context()` utility function for AI Agents & Assistants""" save_thread_context: Optional[SaveThreadContext] """`save_thread_context()` utility function for AI Agents & Assistants""" + say_stream: Optional[SayStream] + """`say_stream()` utility function for conversations, AI Agents & Assistants""" # middleware next: Callable[[], None] """`next()` utility function, which tells the middleware chain that it can continue with the next one""" @@ -172,6 +174,7 @@

      Classes

      set_suggested_prompts: Optional[SetSuggestedPrompts] = None, get_thread_context: Optional[GetThreadContext] = None, save_thread_context: Optional[SaveThreadContext] = None, + say_stream: Optional[SayStream] = None, # As this method is not supposed to be invoked by bolt-python users, # the naming conflict with the built-in one affects # only the internals of this method @@ -205,6 +208,7 @@

      Classes

      self.set_suggested_prompts = set_suggested_prompts self.get_thread_context = get_thread_context self.save_thread_context = save_thread_context + self.say_stream = say_stream self.next: Callable[[], None] = next self.next_: Callable[[], None] = next
      @@ -328,6 +332,10 @@

      Class variables

      say() utility function, which calls chat.postMessage API with the associated channel ID

      +
      var say_streamSayStream | None
      +
      +

      say_stream() utility function for conversations, AI Agents & Assistants

      +
      var set_statusSetStatus | None

      set_status() utility function for AI Agents & Assistants

      @@ -391,6 +399,7 @@

      response
    • save_thread_context
    • say
    • +
    • say_stream
    • set_status
    • set_suggested_prompts
    • set_title
    • @@ -404,7 +413,7 @@

      -

      Generated by pdoc 0.11.5.

      +

      Generated by pdoc 0.11.6.

      diff --git a/docs/static/api-docs/slack_bolt/kwargs_injection/async_args.html b/docs/reference/kwargs_injection/async_args.html similarity index 96% rename from docs/static/api-docs/slack_bolt/kwargs_injection/async_args.html rename to docs/reference/kwargs_injection/async_args.html index 65a14c2d1..5b0e7b70e 100644 --- a/docs/static/api-docs/slack_bolt/kwargs_injection/async_args.html +++ b/docs/reference/kwargs_injection/async_args.html @@ -3,7 +3,7 @@ - + slack_bolt.kwargs_injection.async_args API documentation @@ -48,7 +48,7 @@

      Classes

      class AsyncArgs -(*,
      logger: logging.Logger,
      client: slack_sdk.web.async_client.AsyncWebClient,
      req: AsyncBoltRequest,
      resp: BoltResponse,
      context: AsyncBoltContext,
      body: Dict[str, Any],
      payload: Dict[str, Any],
      options: Dict[str, Any] | None = None,
      shortcut: Dict[str, Any] | None = None,
      action: Dict[str, Any] | None = None,
      view: Dict[str, Any] | None = None,
      command: Dict[str, Any] | None = None,
      event: Dict[str, Any] | None = None,
      message: Dict[str, Any] | None = None,
      ack: AsyncAck,
      say: AsyncSay,
      respond: AsyncRespond,
      complete: AsyncComplete,
      fail: AsyncFail,
      set_status: AsyncSetStatus | None = None,
      set_title: AsyncSetTitle | None = None,
      set_suggested_prompts: AsyncSetSuggestedPrompts | None = None,
      get_thread_context: AsyncGetThreadContext | None = None,
      save_thread_context: AsyncSaveThreadContext | None = None,
      next: Callable[[], Awaitable[None]],
      **kwargs)
      +(*,
      logger: logging.Logger,
      client: slack_sdk.web.async_client.AsyncWebClient,
      req: AsyncBoltRequest,
      resp: BoltResponse,
      context: AsyncBoltContext,
      body: Dict[str, Any],
      payload: Dict[str, Any],
      options: Dict[str, Any] | None = None,
      shortcut: Dict[str, Any] | None = None,
      action: Dict[str, Any] | None = None,
      view: Dict[str, Any] | None = None,
      command: Dict[str, Any] | None = None,
      event: Dict[str, Any] | None = None,
      message: Dict[str, Any] | None = None,
      ack: AsyncAck,
      say: AsyncSay,
      respond: AsyncRespond,
      complete: AsyncComplete,
      fail: AsyncFail,
      set_status: AsyncSetStatus | None = None,
      set_title: AsyncSetTitle | None = None,
      set_suggested_prompts: AsyncSetSuggestedPrompts | None = None,
      get_thread_context: AsyncGetThreadContext | None = None,
      save_thread_context: AsyncSaveThreadContext | None = None,
      say_stream: AsyncSayStream | None = None,
      next: Callable[[], Awaitable[None]],
      **kwargs)
      @@ -139,6 +139,8 @@

      Classes

      """`get_thread_context()` utility function for AI Agents & Assistants""" save_thread_context: Optional[AsyncSaveThreadContext] """`save_thread_context()` utility function for AI Agents & Assistants""" + say_stream: Optional[AsyncSayStream] + """`say_stream()` utility function for AI Agents & Assistants""" # middleware next: Callable[[], Awaitable[None]] """`next()` utility function, which tells the middleware chain that it can continue with the next one""" @@ -172,6 +174,7 @@

      Classes

      set_suggested_prompts: Optional[AsyncSetSuggestedPrompts] = None, get_thread_context: Optional[AsyncGetThreadContext] = None, save_thread_context: Optional[AsyncSaveThreadContext] = None, + say_stream: Optional[AsyncSayStream] = None, next: Callable[[], Awaitable[None]], **kwargs, # noqa ): @@ -202,6 +205,7 @@

      Classes

      self.set_suggested_prompts = set_suggested_prompts self.get_thread_context = get_thread_context self.save_thread_context = save_thread_context + self.say_stream = say_stream self.next: Callable[[], Awaitable[None]] = next self.next_: Callable[[], Awaitable[None]] = next
      @@ -325,6 +329,10 @@

      Class variables

      say() utility function, which calls chat.postMessage API with the associated channel ID

      +
      var say_streamAsyncSayStream | None
      +
      +

      say_stream() utility function for AI Agents & Assistants

      +
      var set_statusAsyncSetStatus | None

      set_status() utility function for AI Agents & Assistants

      @@ -388,6 +396,7 @@

      response
    • save_thread_context
    • say
    • +
    • say_stream
    • set_status
    • set_suggested_prompts
    • set_title
    • @@ -401,7 +410,7 @@

      -

      Generated by pdoc 0.11.5.

      +

      Generated by pdoc 0.11.6.

      diff --git a/docs/static/api-docs/slack_bolt/kwargs_injection/async_utils.html b/docs/reference/kwargs_injection/async_utils.html similarity index 97% rename from docs/static/api-docs/slack_bolt/kwargs_injection/async_utils.html rename to docs/reference/kwargs_injection/async_utils.html index 6f433ddca..7af3a7679 100644 --- a/docs/static/api-docs/slack_bolt/kwargs_injection/async_utils.html +++ b/docs/reference/kwargs_injection/async_utils.html @@ -3,7 +3,7 @@ - + slack_bolt.kwargs_injection.async_utils API documentation @@ -63,7 +63,7 @@

      Functions

      error: Optional[Exception] = None, # for error handlers next_keys_required: bool = True, # False for listeners / middleware / error handlers ) -> Dict[str, Any]: - all_available_args = { + all_available_args: Dict[str, Any] = { "logger": logger, "client": request.context.client, "req": request, @@ -92,6 +92,7 @@

      Functions

      "set_suggested_prompts": request.context.set_suggested_prompts, "get_thread_context": request.context.get_thread_context, "save_thread_context": request.context.save_thread_context, + "say_stream": request.context.say_stream, # middleware "next": next_func, "next_": next_func, # for the middleware using Python's built-in `next()` function @@ -136,7 +137,7 @@

      Functions

      for name in required_arg_names: if name == "args": if isinstance(request, AsyncBoltRequest): - kwargs[name] = AsyncArgs(**all_available_args) # type: ignore[arg-type] + kwargs[name] = AsyncArgs(**all_available_args) else: logger.warning(f"Unknown Request object type detected ({type(request)})") @@ -171,7 +172,7 @@

      Functions

      diff --git a/docs/static/api-docs/slack_bolt/kwargs_injection/index.html b/docs/reference/kwargs_injection/index.html similarity index 96% rename from docs/static/api-docs/slack_bolt/kwargs_injection/index.html rename to docs/reference/kwargs_injection/index.html index 4132bbaba..cb17cea5d 100644 --- a/docs/static/api-docs/slack_bolt/kwargs_injection/index.html +++ b/docs/reference/kwargs_injection/index.html @@ -3,7 +3,7 @@ - + slack_bolt.kwargs_injection API documentation @@ -85,7 +85,7 @@

      Functions

      error: Optional[Exception] = None, # for error handlers next_keys_required: bool = True, # False for listeners / middleware / error handlers ) -> Dict[str, Any]: - all_available_args = { + all_available_args: Dict[str, Any] = { "logger": logger, "client": request.context.client, "req": request, @@ -113,6 +113,7 @@

      Functions

      "set_title": request.context.set_title, "set_suggested_prompts": request.context.set_suggested_prompts, "save_thread_context": request.context.save_thread_context, + "say_stream": request.context.say_stream, # middleware "next": next_func, "next_": next_func, # for the middleware using Python's built-in `next()` function @@ -157,7 +158,7 @@

      Functions

      for name in required_arg_names: if name == "args": if isinstance(request, BoltRequest): - kwargs[name] = Args(**all_available_args) # type: ignore[arg-type] + kwargs[name] = Args(**all_available_args) else: logger.warning(f"Unknown Request object type detected ({type(request)})") @@ -175,7 +176,7 @@

      Classes

      class Args -(*,
      logger: logging.Logger,
      client: slack_sdk.web.client.WebClient,
      req: BoltRequest,
      resp: BoltResponse,
      context: BoltContext,
      body: Dict[str, Any],
      payload: Dict[str, Any],
      options: Dict[str, Any] | None = None,
      shortcut: Dict[str, Any] | None = None,
      action: Dict[str, Any] | None = None,
      view: Dict[str, Any] | None = None,
      command: Dict[str, Any] | None = None,
      event: Dict[str, Any] | None = None,
      message: Dict[str, Any] | None = None,
      ack: Ack,
      say: Say,
      respond: Respond,
      complete: Complete,
      fail: Fail,
      set_status: SetStatus | None = None,
      set_title: SetTitle | None = None,
      set_suggested_prompts: SetSuggestedPrompts | None = None,
      get_thread_context: GetThreadContext | None = None,
      save_thread_context: SaveThreadContext | None = None,
      next: Callable[[], None],
      **kwargs)
      +(*,
      logger: logging.Logger,
      client: slack_sdk.web.client.WebClient,
      req: BoltRequest,
      resp: BoltResponse,
      context: BoltContext,
      body: Dict[str, Any],
      payload: Dict[str, Any],
      options: Dict[str, Any] | None = None,
      shortcut: Dict[str, Any] | None = None,
      action: Dict[str, Any] | None = None,
      view: Dict[str, Any] | None = None,
      command: Dict[str, Any] | None = None,
      event: Dict[str, Any] | None = None,
      message: Dict[str, Any] | None = None,
      ack: Ack,
      say: Say,
      respond: Respond,
      complete: Complete,
      fail: Fail,
      set_status: SetStatus | None = None,
      set_title: SetTitle | None = None,
      set_suggested_prompts: SetSuggestedPrompts | None = None,
      get_thread_context: GetThreadContext | None = None,
      save_thread_context: SaveThreadContext | None = None,
      say_stream: SayStream | None = None,
      next: Callable[[], None],
      **kwargs)
      @@ -266,6 +267,8 @@

      Classes

      """`get_thread_context()` utility function for AI Agents & Assistants""" save_thread_context: Optional[SaveThreadContext] """`save_thread_context()` utility function for AI Agents & Assistants""" + say_stream: Optional[SayStream] + """`say_stream()` utility function for conversations, AI Agents & Assistants""" # middleware next: Callable[[], None] """`next()` utility function, which tells the middleware chain that it can continue with the next one""" @@ -299,6 +302,7 @@

      Classes

      set_suggested_prompts: Optional[SetSuggestedPrompts] = None, get_thread_context: Optional[GetThreadContext] = None, save_thread_context: Optional[SaveThreadContext] = None, + say_stream: Optional[SayStream] = None, # As this method is not supposed to be invoked by bolt-python users, # the naming conflict with the built-in one affects # only the internals of this method @@ -332,6 +336,7 @@

      Classes

      self.set_suggested_prompts = set_suggested_prompts self.get_thread_context = get_thread_context self.save_thread_context = save_thread_context + self.say_stream = say_stream self.next: Callable[[], None] = next self.next_: Callable[[], None] = next
      @@ -455,6 +460,10 @@

      Class variables

      say() utility function, which calls chat.postMessage API with the associated channel ID

      +
      var say_streamSayStream | None
      +
      +

      say_stream() utility function for conversations, AI Agents & Assistants

      +
      var set_statusSetStatus | None

      set_status() utility function for AI Agents & Assistants

      @@ -531,6 +540,7 @@

      response
    • save_thread_context
    • say
    • +
    • say_stream
    • set_status
    • set_suggested_prompts
    • set_title
    • @@ -544,7 +554,7 @@

      -

      Generated by pdoc 0.11.5.

      +

      Generated by pdoc 0.11.6.

      diff --git a/docs/static/api-docs/slack_bolt/kwargs_injection/utils.html b/docs/reference/kwargs_injection/utils.html similarity index 97% rename from docs/static/api-docs/slack_bolt/kwargs_injection/utils.html rename to docs/reference/kwargs_injection/utils.html index a589350c9..0289fd410 100644 --- a/docs/static/api-docs/slack_bolt/kwargs_injection/utils.html +++ b/docs/reference/kwargs_injection/utils.html @@ -3,7 +3,7 @@ - + slack_bolt.kwargs_injection.utils API documentation @@ -63,7 +63,7 @@

      Functions

      error: Optional[Exception] = None, # for error handlers next_keys_required: bool = True, # False for listeners / middleware / error handlers ) -> Dict[str, Any]: - all_available_args = { + all_available_args: Dict[str, Any] = { "logger": logger, "client": request.context.client, "req": request, @@ -91,6 +91,7 @@

      Functions

      "set_title": request.context.set_title, "set_suggested_prompts": request.context.set_suggested_prompts, "save_thread_context": request.context.save_thread_context, + "say_stream": request.context.say_stream, # middleware "next": next_func, "next_": next_func, # for the middleware using Python's built-in `next()` function @@ -135,7 +136,7 @@

      Functions

      for name in required_arg_names: if name == "args": if isinstance(request, BoltRequest): - kwargs[name] = Args(**all_available_args) # type: ignore[arg-type] + kwargs[name] = Args(**all_available_args) else: logger.warning(f"Unknown Request object type detected ({type(request)})") @@ -170,7 +171,7 @@

      Functions

      diff --git a/docs/static/api-docs/slack_bolt/lazy_listener/async_internals.html b/docs/reference/lazy_listener/async_internals.html similarity index 98% rename from docs/static/api-docs/slack_bolt/lazy_listener/async_internals.html rename to docs/reference/lazy_listener/async_internals.html index 19becac19..9d86a02e5 100644 --- a/docs/static/api-docs/slack_bolt/lazy_listener/async_internals.html +++ b/docs/reference/lazy_listener/async_internals.html @@ -3,7 +3,7 @@ - + slack_bolt.lazy_listener.async_internals API documentation @@ -102,7 +102,7 @@

      Functions

      diff --git a/docs/static/api-docs/slack_bolt/lazy_listener/async_runner.html b/docs/reference/lazy_listener/async_runner.html similarity index 99% rename from docs/static/api-docs/slack_bolt/lazy_listener/async_runner.html rename to docs/reference/lazy_listener/async_runner.html index e58b0a044..701f1640a 100644 --- a/docs/static/api-docs/slack_bolt/lazy_listener/async_runner.html +++ b/docs/reference/lazy_listener/async_runner.html @@ -3,7 +3,7 @@ - + slack_bolt.lazy_listener.async_runner API documentation @@ -184,7 +184,7 @@

      -

      Generated by pdoc 0.11.5.

      +

      Generated by pdoc 0.11.6.

      diff --git a/docs/static/api-docs/slack_bolt/lazy_listener/asyncio_runner.html b/docs/reference/lazy_listener/asyncio_runner.html similarity index 98% rename from docs/static/api-docs/slack_bolt/lazy_listener/asyncio_runner.html rename to docs/reference/lazy_listener/asyncio_runner.html index d05a4c9ac..2fdcf8ffe 100644 --- a/docs/static/api-docs/slack_bolt/lazy_listener/asyncio_runner.html +++ b/docs/reference/lazy_listener/asyncio_runner.html @@ -3,7 +3,7 @@ - + slack_bolt.lazy_listener.asyncio_runner API documentation @@ -113,7 +113,7 @@

      -

      Generated by pdoc 0.11.5.

      +

      Generated by pdoc 0.11.6.

      diff --git a/docs/static/api-docs/slack_bolt/lazy_listener/index.html b/docs/reference/lazy_listener/index.html similarity index 98% rename from docs/static/api-docs/slack_bolt/lazy_listener/index.html rename to docs/reference/lazy_listener/index.html index 374164af8..6bc17015e 100644 --- a/docs/static/api-docs/slack_bolt/lazy_listener/index.html +++ b/docs/reference/lazy_listener/index.html @@ -3,7 +3,7 @@ - + slack_bolt.lazy_listener API documentation @@ -56,7 +56,7 @@

      Module slack_bolt.lazy_listener

      lazy=[run_long_process] )
      -

      Refer to https://slack.dev/bolt-python/concepts#lazy-listeners for more details.

      +

      Refer to https://docs.slack.dev/tools/bolt-python/concepts/lazy-listeners for more details.

  • Sub-modules

    @@ -295,7 +295,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/lazy_listener/internals.html b/docs/reference/lazy_listener/internals.html similarity index 98% rename from docs/static/api-docs/slack_bolt/lazy_listener/internals.html rename to docs/reference/lazy_listener/internals.html index 96c04a56c..1801abafd 100644 --- a/docs/static/api-docs/slack_bolt/lazy_listener/internals.html +++ b/docs/reference/lazy_listener/internals.html @@ -3,7 +3,7 @@ - + slack_bolt.lazy_listener.internals API documentation @@ -102,7 +102,7 @@

    Functions

    diff --git a/docs/static/api-docs/slack_bolt/lazy_listener/runner.html b/docs/reference/lazy_listener/runner.html similarity index 99% rename from docs/static/api-docs/slack_bolt/lazy_listener/runner.html rename to docs/reference/lazy_listener/runner.html index 56216c9c8..ff4f449a0 100644 --- a/docs/static/api-docs/slack_bolt/lazy_listener/runner.html +++ b/docs/reference/lazy_listener/runner.html @@ -3,7 +3,7 @@ - + slack_bolt.lazy_listener.runner API documentation @@ -185,7 +185,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/lazy_listener/thread_runner.html b/docs/reference/lazy_listener/thread_runner.html similarity index 98% rename from docs/static/api-docs/slack_bolt/lazy_listener/thread_runner.html rename to docs/reference/lazy_listener/thread_runner.html index 19e6ff29e..b4ca0711a 100644 --- a/docs/static/api-docs/slack_bolt/lazy_listener/thread_runner.html +++ b/docs/reference/lazy_listener/thread_runner.html @@ -3,7 +3,7 @@ - + slack_bolt.lazy_listener.thread_runner API documentation @@ -119,7 +119,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/listener/async_builtins.html b/docs/reference/listener/async_builtins.html similarity index 99% rename from docs/static/api-docs/slack_bolt/listener/async_builtins.html rename to docs/reference/listener/async_builtins.html index 61e6fcd9f..015dd94b3 100644 --- a/docs/static/api-docs/slack_bolt/listener/async_builtins.html +++ b/docs/reference/listener/async_builtins.html @@ -3,7 +3,7 @@ - + slack_bolt.listener.async_builtins API documentation @@ -168,7 +168,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/listener/async_listener.html b/docs/reference/listener/async_listener.html similarity index 95% rename from docs/static/api-docs/slack_bolt/listener/async_listener.html rename to docs/reference/listener/async_listener.html index 52da6d342..a3d1a7fef 100644 --- a/docs/static/api-docs/slack_bolt/listener/async_listener.html +++ b/docs/reference/listener/async_listener.html @@ -3,7 +3,7 @@ - + slack_bolt.listener.async_listener API documentation @@ -48,7 +48,7 @@

    Classes

    class AsyncCustomListener -(*,
    app_name: str,
    ack_function: Callable[..., Awaitable[BoltResponse | None]],
    lazy_functions: Sequence[Callable[..., Awaitable[None]]],
    matchers: Sequence[AsyncListenerMatcher],
    middleware: Sequence[AsyncMiddleware],
    auto_acknowledgement: bool = False,
    base_logger: logging.Logger | None = None)
    +(*,
    app_name: str,
    ack_function: Callable[..., Awaitable[BoltResponse | None]],
    lazy_functions: Sequence[Callable[..., Awaitable[None]]],
    matchers: Sequence[AsyncListenerMatcher],
    middleware: Sequence[AsyncMiddleware],
    auto_acknowledgement: bool = False,
    ack_timeout: int = 3,
    base_logger: logging.Logger | None = None)
    @@ -62,6 +62,7 @@

    Classes

    matchers: Sequence[AsyncListenerMatcher] middleware: Sequence[AsyncMiddleware] auto_acknowledgement: bool + ack_timeout: int arg_names: MutableSequence[str] logger: Logger @@ -74,6 +75,7 @@

    Classes

    matchers: Sequence[AsyncListenerMatcher], middleware: Sequence[AsyncMiddleware], auto_acknowledgement: bool = False, + ack_timeout: int = 3, base_logger: Optional[Logger] = None, ): self.app_name = app_name @@ -82,6 +84,7 @@

    Classes

    self.matchers = matchers self.middleware = middleware self.auto_acknowledgement = auto_acknowledgement + self.ack_timeout = ack_timeout self.arg_names = get_arg_names_of_callable(ack_function) self.logger = get_bolt_app_logger(app_name, self.ack_function, base_logger) @@ -112,6 +115,10 @@

    Class variables

    The type of the None singleton.

    +
    var ack_timeout : int
    +
    +

    The type of the None singleton.

    +
    var app_name : str

    The type of the None singleton.

    @@ -182,7 +189,7 @@

    Returns

    class cls -(*,
    app_name: str,
    ack_function: Callable[..., Awaitable[BoltResponse | None]],
    lazy_functions: Sequence[Callable[..., Awaitable[None]]],
    matchers: Sequence[AsyncListenerMatcher],
    middleware: Sequence[AsyncMiddleware],
    auto_acknowledgement: bool = False,
    base_logger: logging.Logger | None = None)
    +(*,
    app_name: str,
    ack_function: Callable[..., Awaitable[BoltResponse | None]],
    lazy_functions: Sequence[Callable[..., Awaitable[None]]],
    matchers: Sequence[AsyncListenerMatcher],
    middleware: Sequence[AsyncMiddleware],
    auto_acknowledgement: bool = False,
    ack_timeout: int = 3,
    base_logger: logging.Logger | None = None)
    @@ -196,6 +203,7 @@

    Returns

    matchers: Sequence[AsyncListenerMatcher] middleware: Sequence[AsyncMiddleware] auto_acknowledgement: bool + ack_timeout: int arg_names: MutableSequence[str] logger: Logger @@ -208,6 +216,7 @@

    Returns

    matchers: Sequence[AsyncListenerMatcher], middleware: Sequence[AsyncMiddleware], auto_acknowledgement: bool = False, + ack_timeout: int = 3, base_logger: Optional[Logger] = None, ): self.app_name = app_name @@ -216,6 +225,7 @@

    Returns

    self.matchers = matchers self.middleware = middleware self.auto_acknowledgement = auto_acknowledgement + self.ack_timeout = ack_timeout self.arg_names = get_arg_names_of_callable(ack_function) self.logger = get_bolt_app_logger(app_name, self.ack_function, base_logger) @@ -260,6 +270,7 @@

    Inherited members

  • AsyncListener:
    • ack_function
    • +
    • ack_timeout
    • auto_acknowledgement
    • lazy_functions
    • matchers
    • @@ -284,6 +295,7 @@

      Inherited members

      ack_function: Callable[..., Awaitable[BoltResponse]] lazy_functions: Sequence[Callable[..., Awaitable[None]]] auto_acknowledgement: bool + ack_timeout: int async def async_matches( self, @@ -349,6 +361,10 @@

      Class variables

      The type of the None singleton.

      +
      var ack_timeout : int
      +
      +

      The type of the None singleton.

      +
      var auto_acknowledgement : bool

      The type of the None singleton.

      @@ -490,6 +506,7 @@

      Returns

      AsyncCustomListener

      • ack_function
      • +
      • ack_timeout
      • app_name
      • arg_names
      • auto_acknowledgement
      • @@ -512,6 +529,7 @@

        AsyncListener

        • ack_function
        • +
        • ack_timeout
        • async_matches
        • auto_acknowledgement
        • lazy_functions
        • @@ -527,7 +545,7 @@

          -

          Generated by pdoc 0.11.5.

          +

          Generated by pdoc 0.11.6.

          diff --git a/docs/static/api-docs/slack_bolt/listener/async_listener_completion_handler.html b/docs/reference/listener/async_listener_completion_handler.html similarity index 99% rename from docs/static/api-docs/slack_bolt/listener/async_listener_completion_handler.html rename to docs/reference/listener/async_listener_completion_handler.html index 2a05e0213..6cde66b93 100644 --- a/docs/static/api-docs/slack_bolt/listener/async_listener_completion_handler.html +++ b/docs/reference/listener/async_listener_completion_handler.html @@ -3,7 +3,7 @@ - + slack_bolt.listener.async_listener_completion_handler API documentation @@ -220,7 +220,7 @@

          -

          Generated by pdoc 0.11.5.

          +

          Generated by pdoc 0.11.6.

          diff --git a/docs/static/api-docs/slack_bolt/listener/async_listener_error_handler.html b/docs/reference/listener/async_listener_error_handler.html similarity index 97% rename from docs/static/api-docs/slack_bolt/listener/async_listener_error_handler.html rename to docs/reference/listener/async_listener_error_handler.html index 9600a2cfd..ebee4441a 100644 --- a/docs/static/api-docs/slack_bolt/listener/async_listener_error_handler.html +++ b/docs/reference/listener/async_listener_error_handler.html @@ -3,7 +3,7 @@ - + slack_bolt.listener.async_listener_error_handler API documentation @@ -77,9 +77,10 @@

          Classes

          ) returned_response = await self.func(**kwargs) if returned_response is not None and isinstance(returned_response, BoltResponse): - response.status = returned_response.status # type: ignore[union-attr] - response.headers = returned_response.headers # type: ignore[union-attr] - response.body = returned_response.body # type: ignore[union-attr]
          + assert response is not None, "response must be provided when returning a BoltResponse from an error handler" + response.status = returned_response.status + response.headers = returned_response.headers + response.body = returned_response.body

  • Ancestors

    @@ -234,7 +235,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/listener/async_listener_start_handler.html b/docs/reference/listener/async_listener_start_handler.html similarity index 99% rename from docs/static/api-docs/slack_bolt/listener/async_listener_start_handler.html rename to docs/reference/listener/async_listener_start_handler.html index 23ada5e08..80b25eb29 100644 --- a/docs/static/api-docs/slack_bolt/listener/async_listener_start_handler.html +++ b/docs/reference/listener/async_listener_start_handler.html @@ -3,7 +3,7 @@ - + slack_bolt.listener.async_listener_start_handler API documentation @@ -220,7 +220,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/listener/asyncio_runner.html b/docs/reference/listener/asyncio_runner.html similarity index 99% rename from docs/static/api-docs/slack_bolt/listener/asyncio_runner.html rename to docs/reference/listener/asyncio_runner.html index 8262667f0..4d71a88a7 100644 --- a/docs/static/api-docs/slack_bolt/listener/asyncio_runner.html +++ b/docs/reference/listener/asyncio_runner.html @@ -3,7 +3,7 @@ - + slack_bolt.listener.asyncio_runner API documentation @@ -180,7 +180,7 @@

    Classes

    self._start_lazy_function(lazy_func, request) # await for the completion of ack() in the async listener execution - while ack.response is None and time.time() - starting_time <= 3: + while ack.response is None and time.time() - starting_time <= listener.ack_timeout: await asyncio.sleep(0.01) if response is None and ack.response is None: @@ -359,7 +359,7 @@

    Methods

    self._start_lazy_function(lazy_func, request) # await for the completion of ack() in the async listener execution - while ack.response is None and time.time() - starting_time <= 3: + while ack.response is None and time.time() - starting_time <= listener.ack_timeout: await asyncio.sleep(0.01) if response is None and ack.response is None: @@ -414,7 +414,7 @@

    diff --git a/docs/static/api-docs/slack_bolt/listener/builtins.html b/docs/reference/listener/builtins.html similarity index 99% rename from docs/static/api-docs/slack_bolt/listener/builtins.html rename to docs/reference/listener/builtins.html index 75f8ca620..5f3759658 100644 --- a/docs/static/api-docs/slack_bolt/listener/builtins.html +++ b/docs/reference/listener/builtins.html @@ -3,7 +3,7 @@ - + slack_bolt.listener.builtins API documentation @@ -168,7 +168,7 @@

    diff --git a/docs/static/api-docs/slack_bolt/listener/custom_listener.html b/docs/reference/listener/custom_listener.html similarity index 96% rename from docs/static/api-docs/slack_bolt/listener/custom_listener.html rename to docs/reference/listener/custom_listener.html index 1cd261379..1f18502f2 100644 --- a/docs/static/api-docs/slack_bolt/listener/custom_listener.html +++ b/docs/reference/listener/custom_listener.html @@ -3,7 +3,7 @@ - + slack_bolt.listener.custom_listener API documentation @@ -48,7 +48,7 @@

    Classes

    class CustomListener -(*,
    app_name: str,
    ack_function: Callable[..., BoltResponse | None],
    lazy_functions: Sequence[Callable[..., None]],
    matchers: Sequence[ListenerMatcher],
    middleware: Sequence[Middleware],
    auto_acknowledgement: bool = False,
    base_logger: logging.Logger | None = None)
    +(*,
    app_name: str,
    ack_function: Callable[..., BoltResponse | None],
    lazy_functions: Sequence[Callable[..., None]],
    matchers: Sequence[ListenerMatcher],
    middleware: Sequence[Middleware],
    auto_acknowledgement: bool = False,
    ack_timeout: int = 3,
    base_logger: logging.Logger | None = None)
    @@ -62,6 +62,7 @@

    Classes

    matchers: Sequence[ListenerMatcher] middleware: Sequence[Middleware] auto_acknowledgement: bool + ack_timeout: int = 3 arg_names: MutableSequence[str] logger: Logger @@ -74,6 +75,7 @@

    Classes

    matchers: Sequence[ListenerMatcher], middleware: Sequence[Middleware], auto_acknowledgement: bool = False, + ack_timeout: int = 3, base_logger: Optional[Logger] = None, ): self.app_name = app_name @@ -82,6 +84,7 @@

    Classes

    self.matchers = matchers self.middleware = middleware self.auto_acknowledgement = auto_acknowledgement + self.ack_timeout = ack_timeout self.arg_names = get_arg_names_of_callable(ack_function) self.logger = get_bolt_app_logger(app_name, self.ack_function, base_logger) @@ -126,6 +129,7 @@

    Inherited members

  • Listener:
    • ack_function
    • +
    • ack_timeout
    • auto_acknowledgement
    • lazy_functions
    • matchers
    • @@ -165,7 +169,7 @@

      -

      Generated by pdoc 0.11.5.

      +

      Generated by pdoc 0.11.6.

      diff --git a/docs/static/api-docs/slack_bolt/listener/index.html b/docs/reference/listener/index.html similarity index 96% rename from docs/static/api-docs/slack_bolt/listener/index.html rename to docs/reference/listener/index.html index 677147e21..f31264cac 100644 --- a/docs/static/api-docs/slack_bolt/listener/index.html +++ b/docs/reference/listener/index.html @@ -3,7 +3,7 @@ - + slack_bolt.listener API documentation @@ -107,7 +107,7 @@

      Classes

      class CustomListener -(*,
      app_name: str,
      ack_function: Callable[..., BoltResponse | None],
      lazy_functions: Sequence[Callable[..., None]],
      matchers: Sequence[ListenerMatcher],
      middleware: Sequence[Middleware],
      auto_acknowledgement: bool = False,
      base_logger: logging.Logger | None = None)
      +(*,
      app_name: str,
      ack_function: Callable[..., BoltResponse | None],
      lazy_functions: Sequence[Callable[..., None]],
      matchers: Sequence[ListenerMatcher],
      middleware: Sequence[Middleware],
      auto_acknowledgement: bool = False,
      ack_timeout: int = 3,
      base_logger: logging.Logger | None = None)
      @@ -121,6 +121,7 @@

      Classes

      matchers: Sequence[ListenerMatcher] middleware: Sequence[Middleware] auto_acknowledgement: bool + ack_timeout: int = 3 arg_names: MutableSequence[str] logger: Logger @@ -133,6 +134,7 @@

      Classes

      matchers: Sequence[ListenerMatcher], middleware: Sequence[Middleware], auto_acknowledgement: bool = False, + ack_timeout: int = 3, base_logger: Optional[Logger] = None, ): self.app_name = app_name @@ -141,6 +143,7 @@

      Classes

      self.matchers = matchers self.middleware = middleware self.auto_acknowledgement = auto_acknowledgement + self.ack_timeout = ack_timeout self.arg_names = get_arg_names_of_callable(ack_function) self.logger = get_bolt_app_logger(app_name, self.ack_function, base_logger) @@ -185,6 +188,7 @@

      Inherited members

    • Listener:
      • ack_function
      • +
      • ack_timeout
      • auto_acknowledgement
      • lazy_functions
      • matchers
      • @@ -209,6 +213,7 @@

        Inherited members

        ack_function: Callable[..., BoltResponse] lazy_functions: Sequence[Callable[..., None]] auto_acknowledgement: bool + ack_timeout: int = 3 def matches( self, @@ -274,6 +279,10 @@

        Class variables

        The type of the None singleton.

        +
        var ack_timeout : int
        +
        +

        The type of the None singleton.

        +
        var auto_acknowledgement : bool

        The type of the None singleton.

        @@ -440,6 +449,7 @@

        Listener

        • ack_function
        • +
        • ack_timeout
        • auto_acknowledgement
        • lazy_functions
        • matchers
        • @@ -455,7 +465,7 @@

          -

          Generated by pdoc 0.11.5.

          +

          Generated by pdoc 0.11.6.

          diff --git a/docs/static/api-docs/slack_bolt/listener/listener.html b/docs/reference/listener/listener.html similarity index 97% rename from docs/static/api-docs/slack_bolt/listener/listener.html rename to docs/reference/listener/listener.html index 743fb2ceb..034dbe67f 100644 --- a/docs/static/api-docs/slack_bolt/listener/listener.html +++ b/docs/reference/listener/listener.html @@ -3,7 +3,7 @@ - + slack_bolt.listener.listener API documentation @@ -60,6 +60,7 @@

          Classes

          ack_function: Callable[..., BoltResponse] lazy_functions: Sequence[Callable[..., None]] auto_acknowledgement: bool + ack_timeout: int = 3 def matches( self, @@ -125,6 +126,10 @@

          Class variables

          The type of the None singleton.

          +
          var ack_timeout : int
          +
          +

          The type of the None singleton.

          +
          var auto_acknowledgement : bool

          The type of the None singleton.

          @@ -266,6 +271,7 @@

          Returns

          Listener

          • ack_function
          • +
          • ack_timeout
          • auto_acknowledgement
          • lazy_functions
          • matchers
          • @@ -281,7 +287,7 @@

            -

            Generated by pdoc 0.11.5.

            +

            Generated by pdoc 0.11.6.

            diff --git a/docs/static/api-docs/slack_bolt/listener/listener_completion_handler.html b/docs/reference/listener/listener_completion_handler.html similarity index 99% rename from docs/static/api-docs/slack_bolt/listener/listener_completion_handler.html rename to docs/reference/listener/listener_completion_handler.html index 35b2fe8cd..42b1b5413 100644 --- a/docs/static/api-docs/slack_bolt/listener/listener_completion_handler.html +++ b/docs/reference/listener/listener_completion_handler.html @@ -3,7 +3,7 @@ - + slack_bolt.listener.listener_completion_handler API documentation @@ -221,7 +221,7 @@

            -

            Generated by pdoc 0.11.5.

            +

            Generated by pdoc 0.11.6.

            diff --git a/docs/static/api-docs/slack_bolt/listener/listener_error_handler.html b/docs/reference/listener/listener_error_handler.html similarity index 97% rename from docs/static/api-docs/slack_bolt/listener/listener_error_handler.html rename to docs/reference/listener/listener_error_handler.html index fc49894d2..e344b15cb 100644 --- a/docs/static/api-docs/slack_bolt/listener/listener_error_handler.html +++ b/docs/reference/listener/listener_error_handler.html @@ -3,7 +3,7 @@ - + slack_bolt.listener.listener_error_handler API documentation @@ -77,9 +77,10 @@

            Classes

            ) returned_response = self.func(**kwargs) if returned_response is not None and isinstance(returned_response, BoltResponse): - response.status = returned_response.status # type: ignore[union-attr] - response.headers = returned_response.headers # type: ignore[union-attr] - response.body = returned_response.body # type: ignore[union-attr]
            + assert response is not None, "response must be provided when returning a BoltResponse from an error handler" + response.status = returned_response.status + response.headers = returned_response.headers + response.body = returned_response.body

    • Ancestors

      @@ -234,7 +235,7 @@

      -

      Generated by pdoc 0.11.5.

      +

      Generated by pdoc 0.11.6.

      diff --git a/docs/static/api-docs/slack_bolt/listener/listener_start_handler.html b/docs/reference/listener/listener_start_handler.html similarity index 99% rename from docs/static/api-docs/slack_bolt/listener/listener_start_handler.html rename to docs/reference/listener/listener_start_handler.html index 63cb98b91..d60c1b9dc 100644 --- a/docs/static/api-docs/slack_bolt/listener/listener_start_handler.html +++ b/docs/reference/listener/listener_start_handler.html @@ -3,7 +3,7 @@ - + slack_bolt.listener.listener_start_handler API documentation @@ -232,7 +232,7 @@

      -

      Generated by pdoc 0.11.5.

      +

      Generated by pdoc 0.11.6.

      diff --git a/docs/static/api-docs/slack_bolt/listener/thread_runner.html b/docs/reference/listener/thread_runner.html similarity index 99% rename from docs/static/api-docs/slack_bolt/listener/thread_runner.html rename to docs/reference/listener/thread_runner.html index b6fafae99..5415f9ada 100644 --- a/docs/static/api-docs/slack_bolt/listener/thread_runner.html +++ b/docs/reference/listener/thread_runner.html @@ -3,7 +3,7 @@ - + slack_bolt.listener.thread_runner API documentation @@ -148,7 +148,7 @@

      Classes

      if not request.lazy_only: # start the listener function asynchronously def run_ack_function_asynchronously(): - nonlocal ack, request, response + nonlocal response try: self.listener_start_handler.handle( request=request, @@ -197,7 +197,7 @@

      Classes

      self._start_lazy_function(lazy_func, request) # await for the completion of ack() in the async listener execution - while ack.response is None and time.time() - starting_time <= 3: + while ack.response is None and time.time() - starting_time <= listener.ack_timeout: time.sleep(0.01) if response is None and ack.response is None: @@ -346,7 +346,7 @@

      Methods

      if not request.lazy_only: # start the listener function asynchronously def run_ack_function_asynchronously(): - nonlocal ack, request, response + nonlocal response try: self.listener_start_handler.handle( request=request, @@ -395,7 +395,7 @@

      Methods

      self._start_lazy_function(lazy_func, request) # await for the completion of ack() in the async listener execution - while ack.response is None and time.time() - starting_time <= 3: + while ack.response is None and time.time() - starting_time <= listener.ack_timeout: time.sleep(0.01) if response is None and ack.response is None: @@ -451,7 +451,7 @@

      diff --git a/docs/static/api-docs/slack_bolt/listener_matcher/async_builtins.html b/docs/reference/listener_matcher/async_builtins.html similarity index 98% rename from docs/static/api-docs/slack_bolt/listener_matcher/async_builtins.html rename to docs/reference/listener_matcher/async_builtins.html index b99d07c82..0df1215de 100644 --- a/docs/static/api-docs/slack_bolt/listener_matcher/async_builtins.html +++ b/docs/reference/listener_matcher/async_builtins.html @@ -3,7 +3,7 @@ - + slack_bolt.listener_matcher.async_builtins API documentation @@ -112,7 +112,7 @@

      -

      Generated by pdoc 0.11.5.

      +

      Generated by pdoc 0.11.6.

      diff --git a/docs/static/api-docs/slack_bolt/listener_matcher/async_listener_matcher.html b/docs/reference/listener_matcher/async_listener_matcher.html similarity index 99% rename from docs/static/api-docs/slack_bolt/listener_matcher/async_listener_matcher.html rename to docs/reference/listener_matcher/async_listener_matcher.html index bc8676302..1366da4e2 100644 --- a/docs/static/api-docs/slack_bolt/listener_matcher/async_listener_matcher.html +++ b/docs/reference/listener_matcher/async_listener_matcher.html @@ -3,7 +3,7 @@ - + slack_bolt.listener_matcher.async_listener_matcher API documentation @@ -311,7 +311,7 @@

      -

      Generated by pdoc 0.11.5.

      +

      Generated by pdoc 0.11.6.

      diff --git a/docs/static/api-docs/slack_bolt/listener_matcher/builtins.html b/docs/reference/listener_matcher/builtins.html similarity index 99% rename from docs/static/api-docs/slack_bolt/listener_matcher/builtins.html rename to docs/reference/listener_matcher/builtins.html index 1f9e3e677..a5aff3d0b 100644 --- a/docs/static/api-docs/slack_bolt/listener_matcher/builtins.html +++ b/docs/reference/listener_matcher/builtins.html @@ -3,7 +3,7 @@ - + slack_bolt.listener_matcher.builtins API documentation @@ -80,7 +80,7 @@

      Functions

      return dialog_submission(constraints["callback_id"], asyncio) if action_type == "dialog_cancellation": return dialog_cancellation(constraints["callback_id"], asyncio) - # https://api.slack.com/workflows/steps + # https://docs.slack.dev/legacy/legacy-steps-from-apps/ if action_type == "workflow_step_edit": return workflow_step_edit(constraints["callback_id"], asyncio) @@ -692,7 +692,7 @@

      diff --git a/docs/static/api-docs/slack_bolt/listener_matcher/custom_listener_matcher.html b/docs/reference/listener_matcher/custom_listener_matcher.html similarity index 99% rename from docs/static/api-docs/slack_bolt/listener_matcher/custom_listener_matcher.html rename to docs/reference/listener_matcher/custom_listener_matcher.html index 8009e84a1..087d36907 100644 --- a/docs/static/api-docs/slack_bolt/listener_matcher/custom_listener_matcher.html +++ b/docs/reference/listener_matcher/custom_listener_matcher.html @@ -3,7 +3,7 @@ - + slack_bolt.listener_matcher.custom_listener_matcher API documentation @@ -141,7 +141,7 @@

      -

      Generated by pdoc 0.11.5.

      +

      Generated by pdoc 0.11.6.

      diff --git a/docs/static/api-docs/slack_bolt/listener_matcher/index.html b/docs/reference/listener_matcher/index.html similarity index 99% rename from docs/static/api-docs/slack_bolt/listener_matcher/index.html rename to docs/reference/listener_matcher/index.html index 622a5e9d9..a93c86d98 100644 --- a/docs/static/api-docs/slack_bolt/listener_matcher/index.html +++ b/docs/reference/listener_matcher/index.html @@ -3,7 +3,7 @@ - + slack_bolt.listener_matcher API documentation @@ -247,7 +247,7 @@

      -

      Generated by pdoc 0.11.5.

      +

      Generated by pdoc 0.11.6.

      diff --git a/docs/static/api-docs/slack_bolt/listener_matcher/listener_matcher.html b/docs/reference/listener_matcher/listener_matcher.html similarity index 98% rename from docs/static/api-docs/slack_bolt/listener_matcher/listener_matcher.html rename to docs/reference/listener_matcher/listener_matcher.html index a20816088..0618f7e4e 100644 --- a/docs/static/api-docs/slack_bolt/listener_matcher/listener_matcher.html +++ b/docs/reference/listener_matcher/listener_matcher.html @@ -3,7 +3,7 @@ - + slack_bolt.listener_matcher.listener_matcher API documentation @@ -137,7 +137,7 @@

      -

      Generated by pdoc 0.11.5.

      +

      Generated by pdoc 0.11.6.

      diff --git a/docs/static/api-docs/slack_bolt/logger/index.html b/docs/reference/logger/index.html similarity index 98% rename from docs/static/api-docs/slack_bolt/logger/index.html rename to docs/reference/logger/index.html index c9defacdb..d0b2ef33f 100644 --- a/docs/static/api-docs/slack_bolt/logger/index.html +++ b/docs/reference/logger/index.html @@ -3,7 +3,7 @@ - + slack_bolt.logger API documentation @@ -121,7 +121,7 @@

      Functions

      diff --git a/docs/static/api-docs/slack_bolt/logger/messages.html b/docs/reference/logger/messages.html similarity index 95% rename from docs/static/api-docs/slack_bolt/logger/messages.html rename to docs/reference/logger/messages.html index 23ef874d3..e69b45fc9 100644 --- a/docs/static/api-docs/slack_bolt/logger/messages.html +++ b/docs/reference/logger/messages.html @@ -3,7 +3,7 @@ - + slack_bolt.logger.messages API documentation @@ -208,14 +208,14 @@

      Functions

      -def error_message_event_type(event_type: str) ‑> str +def error_message_event_type(event_type: str | re.Pattern) ‑> str
      Expand source code -
      def error_message_event_type(event_type: str) -> str:
      +
      def error_message_event_type(event_type: Union[str, Pattern]) -> str:
           return (
               f'Although the document mentions "{event_type}", '
               'it is not a valid event type. Use "message" instead. '
      @@ -303,11 +303,25 @@ 

      Functions

      "Bolt has enabled the file-based InstallationStore/OAuthStateStore for you. " "Note that these file-based stores are for local development. " "If you'd like to use a different data store, set the oauth_settings argument in the App constructor. " - "Please refer to https://slack.dev/bolt-python/concepts#authenticating-oauth for more details." + "Please refer to https://docs.slack.dev/tools/bolt-python/concepts/authenticating-oauth for more details." )
      +
      +def warning_ack_timeout_has_no_effect(identifier: str | re.Pattern, ack_timeout: int) ‑> str +
      +
      +
      + +Expand source code + +
      def warning_ack_timeout_has_no_effect(identifier: Union[str, Pattern], ack_timeout: int) -> str:
      +    handler_example = f'@app.function("{identifier}")' if isinstance(identifier, str) else f"@app.function({identifier})"
      +    return f"On {handler_example}, as `auto_acknowledge` is `True`, " f"`ack_timeout={ack_timeout}` you gave will be unused"
      +
      +
      +
      def warning_bot_only_conflicts() ‑> str
      @@ -395,7 +409,7 @@

      Functions

  • -def warning_unhandled_by_global_middleware(name: str,
    req: BoltRequest | ForwardRef('AsyncBoltRequest')) ‑> str
    +def warning_unhandled_by_global_middleware(name: str,
    req: BoltRequest | AsyncBoltRequest) ‑> str
    @@ -413,7 +427,7 @@

    Functions

    -def warning_unhandled_request(req: BoltRequest | ForwardRef('AsyncBoltRequest')) ‑> str +def warning_unhandled_request(req: BoltRequest | AsyncBoltRequest) ‑> str
    @@ -425,7 +439,7 @@

    Functions

    ) -> str: filtered_body = _build_filtered_body(req.body) default_message = f"Unhandled request ({filtered_body})" - is_async = type(req) != BoltRequest + is_async = not isinstance(req, BoltRequest) if is_workflow_step_edit(req.body) or is_workflow_step_save(req.body) or is_workflow_step_execute(req.body): # @app.step callback_id = ( @@ -591,6 +605,7 @@

    Functions

  • error_token_required
  • error_unexpected_listener_middleware
  • info_default_oauth_settings_loaded
  • +
  • warning_ack_timeout_has_no_effect
  • warning_bot_only_conflicts
  • warning_client_prioritized_and_token_skipped
  • warning_did_not_call_ack
  • @@ -605,7 +620,7 @@

    Functions

    diff --git a/docs/static/api-docs/slack_bolt/middleware/assistant/assistant.html b/docs/reference/middleware/assistant/assistant.html similarity index 94% rename from docs/static/api-docs/slack_bolt/middleware/assistant/assistant.html rename to docs/reference/middleware/assistant/assistant.html index 76e5dff76..946416d62 100644 --- a/docs/static/api-docs/slack_bolt/middleware/assistant/assistant.html +++ b/docs/reference/middleware/assistant/assistant.html @@ -3,7 +3,7 @@ - + slack_bolt.middleware.assistant.assistant API documentation @@ -96,7 +96,7 @@

    Classes

    self.build_listener( listener_or_functions=func, matchers=all_matchers, - middleware=middleware, # type:ignore[arg-type] + middleware=middleware, # type: ignore[arg-type] ) ) return func @@ -135,7 +135,7 @@

    Classes

    self.build_listener( listener_or_functions=func, matchers=all_matchers, - middleware=middleware, # type:ignore[arg-type] + middleware=middleware, # type: ignore[arg-type] ) ) return func @@ -174,7 +174,7 @@

    Classes

    self.build_listener( listener_or_functions=func, matchers=all_matchers, - middleware=middleware, # type:ignore[arg-type] + middleware=middleware, # type: ignore[arg-type] ) ) return func @@ -213,7 +213,7 @@

    Classes

    self.build_listener( listener_or_functions=func, matchers=all_matchers, - middleware=middleware, # type:ignore[arg-type] + middleware=middleware, # type: ignore[arg-type] ) ) return func @@ -243,13 +243,13 @@

    Classes

    ): return [CustomListenerMatcher(app_name=self.app_name, func=primary_matcher)] + ( custom_matchers or [] - ) # type:ignore[operator] + ) # type: ignore[operator] @staticmethod def default_thread_context_changed(save_thread_context: SaveThreadContext, payload: dict): save_thread_context(payload["assistant_thread"]["context"]) - def process( # type:ignore[return] + def process( # type: ignore[return] self, *, req: BoltRequest, resp: BoltResponse, next: Callable[[], BoltResponse] ) -> Optional[BoltResponse]: if self._thread_context_changed_listeners is None: @@ -265,6 +265,15 @@

    Classes

    if listeners is not None: for listener in listeners: if listener.matches(req=req, resp=resp): + middleware_resp, next_was_not_called = listener.run_middleware(req=req, resp=resp) + if next_was_not_called: + if middleware_resp is not None: + return middleware_resp + # The listener middleware didn't call next(). + # Skip this listener and try the next one. + continue + if middleware_resp is not None: + resp = middleware_resp return listener_runner.run( request=req, response=resp, @@ -284,13 +293,14 @@

    Classes

    middleware: Optional[List[Middleware]] = None, base_logger: Optional[Logger] = None, ) -> Listener: - if isinstance(listener_or_functions, Callable): # type:ignore[arg-type] - listener_or_functions = [listener_or_functions] # type:ignore[list-item] + if isinstance(listener_or_functions, Callable): # type: ignore[arg-type] + listener_or_functions = [listener_or_functions] # type: ignore[list-item] if isinstance(listener_or_functions, Listener): return listener_or_functions elif isinstance(listener_or_functions, list): middleware = middleware if middleware else [] + middleware.insert(0, AttachingConversationKwargs(self.thread_context_store)) functions = listener_or_functions ack_function = functions.pop(0) @@ -299,7 +309,7 @@

    Classes

    for matcher in matchers: if isinstance(matcher, ListenerMatcher): listener_matchers.append(matcher) - elif isinstance(matcher, Callable): # type:ignore[arg-type] + elif isinstance(matcher, Callable): # type: ignore[arg-type] listener_matchers.append( build_listener_matcher( func=matcher, @@ -378,7 +388,7 @@

    Methods

    self.build_listener( listener_or_functions=func, matchers=all_matchers, - middleware=middleware, # type:ignore[arg-type] + middleware=middleware, # type: ignore[arg-type] ) ) return func @@ -418,13 +428,14 @@

    Methods

    middleware: Optional[List[Middleware]] = None, base_logger: Optional[Logger] = None, ) -> Listener: - if isinstance(listener_or_functions, Callable): # type:ignore[arg-type] - listener_or_functions = [listener_or_functions] # type:ignore[list-item] + if isinstance(listener_or_functions, Callable): # type: ignore[arg-type] + listener_or_functions = [listener_or_functions] # type: ignore[list-item] if isinstance(listener_or_functions, Listener): return listener_or_functions elif isinstance(listener_or_functions, list): middleware = middleware if middleware else [] + middleware.insert(0, AttachingConversationKwargs(self.thread_context_store)) functions = listener_or_functions ack_function = functions.pop(0) @@ -433,7 +444,7 @@

    Methods

    for matcher in matchers: if isinstance(matcher, ListenerMatcher): listener_matchers.append(matcher) - elif isinstance(matcher, Callable): # type:ignore[arg-type] + elif isinstance(matcher, Callable): # type: ignore[arg-type] listener_matchers.append( build_listener_matcher( func=matcher, @@ -479,7 +490,7 @@

    Methods

    self.build_listener( listener_or_functions=func, matchers=all_matchers, - middleware=middleware, # type:ignore[arg-type] + middleware=middleware, # type: ignore[arg-type] ) ) return func @@ -528,7 +539,7 @@

    Methods

    self.build_listener( listener_or_functions=func, matchers=all_matchers, - middleware=middleware, # type:ignore[arg-type] + middleware=middleware, # type: ignore[arg-type] ) ) return func @@ -577,7 +588,7 @@

    Methods

    self.build_listener( listener_or_functions=func, matchers=all_matchers, - middleware=middleware, # type:ignore[arg-type] + middleware=middleware, # type: ignore[arg-type] ) ) return func @@ -647,7 +658,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/middleware/assistant/async_assistant.html b/docs/reference/middleware/assistant/async_assistant.html similarity index 94% rename from docs/static/api-docs/slack_bolt/middleware/assistant/async_assistant.html rename to docs/reference/middleware/assistant/async_assistant.html index 260d493ac..748be2cbf 100644 --- a/docs/static/api-docs/slack_bolt/middleware/assistant/async_assistant.html +++ b/docs/reference/middleware/assistant/async_assistant.html @@ -3,7 +3,7 @@ - + slack_bolt.middleware.assistant.async_assistant API documentation @@ -94,7 +94,7 @@

    Classes

    func=is_assistant_thread_started_event, asyncio=True, base_logger=self.base_logger, - ), # type:ignore[arg-type] + ), # type: ignore[arg-type] matchers, ) if is_used_without_argument(args): @@ -103,7 +103,7 @@

    Classes

    self.build_listener( listener_or_functions=func, matchers=all_matchers, - middleware=middleware, # type:ignore[arg-type] + middleware=middleware, # type: ignore[arg-type] ) ) return func @@ -140,7 +140,7 @@

    Classes

    func=is_user_message_event_in_assistant_thread, asyncio=True, base_logger=self.base_logger, - ), # type:ignore[arg-type] + ), # type: ignore[arg-type] matchers, ) if is_used_without_argument(args): @@ -149,7 +149,7 @@

    Classes

    self.build_listener( listener_or_functions=func, matchers=all_matchers, - middleware=middleware, # type:ignore[arg-type] + middleware=middleware, # type: ignore[arg-type] ) ) return func @@ -186,7 +186,7 @@

    Classes

    func=is_bot_message_event_in_assistant_thread, asyncio=True, base_logger=self.base_logger, - ), # type:ignore[arg-type] + ), # type: ignore[arg-type] matchers, ) if is_used_without_argument(args): @@ -195,7 +195,7 @@

    Classes

    self.build_listener( listener_or_functions=func, matchers=all_matchers, - middleware=middleware, # type:ignore[arg-type] + middleware=middleware, # type: ignore[arg-type] ) ) return func @@ -232,7 +232,7 @@

    Classes

    func=is_assistant_thread_context_changed_event, asyncio=True, base_logger=self.base_logger, - ), # type:ignore[arg-type] + ), # type: ignore[arg-type] matchers, ) if is_used_without_argument(args): @@ -241,7 +241,7 @@

    Classes

    self.build_listener( listener_or_functions=func, matchers=all_matchers, - middleware=middleware, # type:ignore[arg-type] + middleware=middleware, # type: ignore[arg-type] ) ) return func @@ -269,14 +269,14 @@

    Classes

    primary_matcher: Union[Callable[..., bool], AsyncListenerMatcher], custom_matchers: Optional[Union[Callable[..., bool], AsyncListenerMatcher]], ): - return [primary_matcher] + (custom_matchers or []) # type:ignore[operator] + return [primary_matcher] + (custom_matchers or []) # type: ignore[operator] @staticmethod async def default_thread_context_changed(save_thread_context: AsyncSaveThreadContext, payload: dict): new_context: dict = payload["assistant_thread"]["context"] await save_thread_context(new_context) - async def async_process( # type:ignore[return] + async def async_process( # type: ignore[return] self, *, req: AsyncBoltRequest, @@ -296,6 +296,15 @@

    Classes

    if listeners is not None: for listener in listeners: if listener is not None and await listener.async_matches(req=req, resp=resp): + middleware_resp, next_was_not_called = await listener.run_async_middleware(req=req, resp=resp) + if next_was_not_called: + if middleware_resp is not None: + return middleware_resp + # The listener middleware didn't call next(). + # Skip this listener and try the next one. + continue + if middleware_resp is not None: + resp = middleware_resp return await listener_runner.run( request=req, response=resp, @@ -315,13 +324,14 @@

    Classes

    middleware: Optional[List[AsyncMiddleware]] = None, base_logger: Optional[Logger] = None, ) -> AsyncListener: - if isinstance(listener_or_functions, Callable): # type:ignore[arg-type] - listener_or_functions = [listener_or_functions] # type:ignore[list-item] + if isinstance(listener_or_functions, Callable): # type: ignore[arg-type] + listener_or_functions = [listener_or_functions] # type: ignore[list-item] if isinstance(listener_or_functions, AsyncListener): return listener_or_functions elif isinstance(listener_or_functions, list): middleware = middleware if middleware else [] + middleware.insert(0, AsyncAttachingConversationKwargs(self.thread_context_store)) functions = listener_or_functions ack_function = functions.pop(0) @@ -333,7 +343,7 @@

    Classes

    else: listener_matchers.append( build_listener_matcher( - func=matcher, # type:ignore[arg-type] + func=matcher, # type: ignore[arg-type] asyncio=True, base_logger=base_logger, ) @@ -408,7 +418,7 @@

    Methods

    func=is_bot_message_event_in_assistant_thread, asyncio=True, base_logger=self.base_logger, - ), # type:ignore[arg-type] + ), # type: ignore[arg-type] matchers, ) if is_used_without_argument(args): @@ -417,7 +427,7 @@

    Methods

    self.build_listener( listener_or_functions=func, matchers=all_matchers, - middleware=middleware, # type:ignore[arg-type] + middleware=middleware, # type: ignore[arg-type] ) ) return func @@ -457,13 +467,14 @@

    Methods

    middleware: Optional[List[AsyncMiddleware]] = None, base_logger: Optional[Logger] = None, ) -> AsyncListener: - if isinstance(listener_or_functions, Callable): # type:ignore[arg-type] - listener_or_functions = [listener_or_functions] # type:ignore[list-item] + if isinstance(listener_or_functions, Callable): # type: ignore[arg-type] + listener_or_functions = [listener_or_functions] # type: ignore[list-item] if isinstance(listener_or_functions, AsyncListener): return listener_or_functions elif isinstance(listener_or_functions, list): middleware = middleware if middleware else [] + middleware.insert(0, AsyncAttachingConversationKwargs(self.thread_context_store)) functions = listener_or_functions ack_function = functions.pop(0) @@ -475,7 +486,7 @@

    Methods

    else: listener_matchers.append( build_listener_matcher( - func=matcher, # type:ignore[arg-type] + func=matcher, # type: ignore[arg-type] asyncio=True, base_logger=base_logger, ) @@ -516,7 +527,7 @@

    Methods

    func=is_assistant_thread_context_changed_event, asyncio=True, base_logger=self.base_logger, - ), # type:ignore[arg-type] + ), # type: ignore[arg-type] matchers, ) if is_used_without_argument(args): @@ -525,7 +536,7 @@

    Methods

    self.build_listener( listener_or_functions=func, matchers=all_matchers, - middleware=middleware, # type:ignore[arg-type] + middleware=middleware, # type: ignore[arg-type] ) ) return func @@ -572,7 +583,7 @@

    Methods

    func=is_assistant_thread_started_event, asyncio=True, base_logger=self.base_logger, - ), # type:ignore[arg-type] + ), # type: ignore[arg-type] matchers, ) if is_used_without_argument(args): @@ -581,7 +592,7 @@

    Methods

    self.build_listener( listener_or_functions=func, matchers=all_matchers, - middleware=middleware, # type:ignore[arg-type] + middleware=middleware, # type: ignore[arg-type] ) ) return func @@ -628,7 +639,7 @@

    Methods

    func=is_user_message_event_in_assistant_thread, asyncio=True, base_logger=self.base_logger, - ), # type:ignore[arg-type] + ), # type: ignore[arg-type] matchers, ) if is_used_without_argument(args): @@ -637,7 +648,7 @@

    Methods

    self.build_listener( listener_or_functions=func, matchers=all_matchers, - middleware=middleware, # type:ignore[arg-type] + middleware=middleware, # type: ignore[arg-type] ) ) return func @@ -707,7 +718,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/middleware/assistant/index.html b/docs/reference/middleware/assistant/index.html similarity index 94% rename from docs/static/api-docs/slack_bolt/middleware/assistant/index.html rename to docs/reference/middleware/assistant/index.html index 857240adb..e9fce8d64 100644 --- a/docs/static/api-docs/slack_bolt/middleware/assistant/index.html +++ b/docs/reference/middleware/assistant/index.html @@ -3,7 +3,7 @@ - + slack_bolt.middleware.assistant API documentation @@ -107,7 +107,7 @@

    Classes

    self.build_listener( listener_or_functions=func, matchers=all_matchers, - middleware=middleware, # type:ignore[arg-type] + middleware=middleware, # type: ignore[arg-type] ) ) return func @@ -146,7 +146,7 @@

    Classes

    self.build_listener( listener_or_functions=func, matchers=all_matchers, - middleware=middleware, # type:ignore[arg-type] + middleware=middleware, # type: ignore[arg-type] ) ) return func @@ -185,7 +185,7 @@

    Classes

    self.build_listener( listener_or_functions=func, matchers=all_matchers, - middleware=middleware, # type:ignore[arg-type] + middleware=middleware, # type: ignore[arg-type] ) ) return func @@ -224,7 +224,7 @@

    Classes

    self.build_listener( listener_or_functions=func, matchers=all_matchers, - middleware=middleware, # type:ignore[arg-type] + middleware=middleware, # type: ignore[arg-type] ) ) return func @@ -254,13 +254,13 @@

    Classes

    ): return [CustomListenerMatcher(app_name=self.app_name, func=primary_matcher)] + ( custom_matchers or [] - ) # type:ignore[operator] + ) # type: ignore[operator] @staticmethod def default_thread_context_changed(save_thread_context: SaveThreadContext, payload: dict): save_thread_context(payload["assistant_thread"]["context"]) - def process( # type:ignore[return] + def process( # type: ignore[return] self, *, req: BoltRequest, resp: BoltResponse, next: Callable[[], BoltResponse] ) -> Optional[BoltResponse]: if self._thread_context_changed_listeners is None: @@ -276,6 +276,15 @@

    Classes

    if listeners is not None: for listener in listeners: if listener.matches(req=req, resp=resp): + middleware_resp, next_was_not_called = listener.run_middleware(req=req, resp=resp) + if next_was_not_called: + if middleware_resp is not None: + return middleware_resp + # The listener middleware didn't call next(). + # Skip this listener and try the next one. + continue + if middleware_resp is not None: + resp = middleware_resp return listener_runner.run( request=req, response=resp, @@ -295,13 +304,14 @@

    Classes

    middleware: Optional[List[Middleware]] = None, base_logger: Optional[Logger] = None, ) -> Listener: - if isinstance(listener_or_functions, Callable): # type:ignore[arg-type] - listener_or_functions = [listener_or_functions] # type:ignore[list-item] + if isinstance(listener_or_functions, Callable): # type: ignore[arg-type] + listener_or_functions = [listener_or_functions] # type: ignore[list-item] if isinstance(listener_or_functions, Listener): return listener_or_functions elif isinstance(listener_or_functions, list): middleware = middleware if middleware else [] + middleware.insert(0, AttachingConversationKwargs(self.thread_context_store)) functions = listener_or_functions ack_function = functions.pop(0) @@ -310,7 +320,7 @@

    Classes

    for matcher in matchers: if isinstance(matcher, ListenerMatcher): listener_matchers.append(matcher) - elif isinstance(matcher, Callable): # type:ignore[arg-type] + elif isinstance(matcher, Callable): # type: ignore[arg-type] listener_matchers.append( build_listener_matcher( func=matcher, @@ -389,7 +399,7 @@

    Methods

    self.build_listener( listener_or_functions=func, matchers=all_matchers, - middleware=middleware, # type:ignore[arg-type] + middleware=middleware, # type: ignore[arg-type] ) ) return func @@ -429,13 +439,14 @@

    Methods

    middleware: Optional[List[Middleware]] = None, base_logger: Optional[Logger] = None, ) -> Listener: - if isinstance(listener_or_functions, Callable): # type:ignore[arg-type] - listener_or_functions = [listener_or_functions] # type:ignore[list-item] + if isinstance(listener_or_functions, Callable): # type: ignore[arg-type] + listener_or_functions = [listener_or_functions] # type: ignore[list-item] if isinstance(listener_or_functions, Listener): return listener_or_functions elif isinstance(listener_or_functions, list): middleware = middleware if middleware else [] + middleware.insert(0, AttachingConversationKwargs(self.thread_context_store)) functions = listener_or_functions ack_function = functions.pop(0) @@ -444,7 +455,7 @@

    Methods

    for matcher in matchers: if isinstance(matcher, ListenerMatcher): listener_matchers.append(matcher) - elif isinstance(matcher, Callable): # type:ignore[arg-type] + elif isinstance(matcher, Callable): # type: ignore[arg-type] listener_matchers.append( build_listener_matcher( func=matcher, @@ -490,7 +501,7 @@

    Methods

    self.build_listener( listener_or_functions=func, matchers=all_matchers, - middleware=middleware, # type:ignore[arg-type] + middleware=middleware, # type: ignore[arg-type] ) ) return func @@ -539,7 +550,7 @@

    Methods

    self.build_listener( listener_or_functions=func, matchers=all_matchers, - middleware=middleware, # type:ignore[arg-type] + middleware=middleware, # type: ignore[arg-type] ) ) return func @@ -588,7 +599,7 @@

    Methods

    self.build_listener( listener_or_functions=func, matchers=all_matchers, - middleware=middleware, # type:ignore[arg-type] + middleware=middleware, # type: ignore[arg-type] ) ) return func @@ -664,7 +675,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/middleware/async_builtins.html b/docs/reference/middleware/async_builtins.html similarity index 80% rename from docs/static/api-docs/slack_bolt/middleware/async_builtins.html rename to docs/reference/middleware/async_builtins.html index d25d27a92..8f7b1ba4f 100644 --- a/docs/static/api-docs/slack_bolt/middleware/async_builtins.html +++ b/docs/reference/middleware/async_builtins.html @@ -3,7 +3,7 @@ - + slack_bolt.middleware.async_builtins API documentation @@ -46,6 +46,98 @@

    Module slack_bolt.middleware.async_builtins

    Classes

    +
    +class AsyncAttachingConversationKwargs +(thread_context_store: AsyncAssistantThreadContextStore | None = None) +
    +
    +
    + +Expand source code + +
    class AsyncAttachingConversationKwargs(AsyncMiddleware):
    +
    +    thread_context_store: Optional[AsyncAssistantThreadContextStore]
    +
    +    def __init__(self, thread_context_store: Optional[AsyncAssistantThreadContextStore] = None):
    +        self.thread_context_store = thread_context_store
    +
    +    async def async_process(
    +        self,
    +        *,
    +        req: AsyncBoltRequest,
    +        resp: BoltResponse,
    +        next: Callable[[], Awaitable[BoltResponse]],
    +    ) -> Optional[BoltResponse]:
    +        event = to_event(req.body)
    +        if event is None:
    +            return await next()
    +        if req.context.channel_id is None:
    +            return await next()
    +
    +        if is_assistant_event(req.body):
    +            # TODO: eventually we might remove this assistant specific logic
    +            assistant = AsyncAssistantUtilities(
    +                payload=event,
    +                context=req.context,
    +                thread_context_store=self.thread_context_store,
    +            )
    +            req.context["say"] = assistant.say
    +            req.context["set_title"] = assistant.set_title
    +            req.context["get_thread_context"] = assistant.get_thread_context
    +            req.context["save_thread_context"] = assistant.save_thread_context
    +
    +        if (
    +            is_im_message_event(req.body)
    +            or is_assistant_thread_started_event(req.body)
    +            or is_assistant_thread_context_changed_event(req.body)
    +            or is_app_home_opened_event(req.body, tab="messages")
    +        ):
    +            req.context["set_suggested_prompts"] = AsyncSetSuggestedPrompts(
    +                client=req.context.client,
    +                channel_id=req.context.channel_id,
    +                thread_ts=req.context.thread_ts,
    +            )
    +
    +        # TODO: in the future we might want to introduce a "proper" extract_ts utility
    +        thread_ts_or_ts = req.context.thread_ts or event.get("ts")
    +        if thread_ts_or_ts:
    +            req.context["set_status"] = AsyncSetStatus(
    +                client=req.context.client,
    +                channel_id=req.context.channel_id,
    +                thread_ts=thread_ts_or_ts,
    +            )
    +            req.context["say_stream"] = AsyncSayStream(
    +                client=req.context.client,
    +                channel=req.context.channel_id,
    +                recipient_team_id=req.context.team_id or req.context.enterprise_id,
    +                recipient_user_id=req.context.user_id,
    +                thread_ts=thread_ts_or_ts,
    +            )
    +        return await next()
    +
    +

    A middleware can process request data before other middleware and listener functions.

    +

    Ancestors

    + +

    Class variables

    +
    +
    var thread_context_storeAsyncAssistantThreadContextStore | None
    +
    +

    The type of the None singleton.

    +
    +
    +

    Inherited members

    + +
    class AsyncAttachingFunctionToken
    @@ -205,7 +297,7 @@

    Inherited members

    """Verifies an incoming request by checking the validity of `x-slack-signature`, `x-slack-request-timestamp`, and its body data. - Refer to https://api.slack.com/authentication/verifying-requests-from-slack for details. + Refer to https://docs.slack.dev/authentication/verifying-requests-from-slack/ for details. """ async def async_process( @@ -232,10 +324,10 @@

    Inherited members

    Verifies an incoming request by checking the validity of x-slack-signature, x-slack-request-timestamp, and its body data.

    -

    Refer to https://api.slack.com/authentication/verifying-requests-from-slack for details.

    +

    Refer to https://docs.slack.dev/authentication/verifying-requests-from-slack/ for details.

    Verifies an incoming request by checking the validity of x-slack-signature, x-slack-request-timestamp, and its body data.

    -

    Refer to https://api.slack.com/authentication/verifying-requests-from-slack for details.

    +

    Refer to https://docs.slack.dev/authentication/verifying-requests-from-slack/ for details.

    Args

    signing_secret
    @@ -293,12 +385,12 @@

    Inherited members

    A middleware can process request data before other middleware and listener functions.

    Handles ssl_check requests. -Refer to https://api.slack.com/interactivity/slash-commands for details.

    +Refer to https://docs.slack.dev/interactivity/implementing-slash-commands/ for details.

    Args

    verification_token
    The verification token to check -(optional as it's already deprecated - https://api.slack.com/authentication/verifying-requests-from-slack#verification_token_deprecation)
    +(optional as it's already deprecated - https://docs.slack.dev/authentication/verifying-requests-from-slack/#deprecation)
    base_logger
    The base logger
    @@ -352,7 +444,7 @@

    Inherited members

    A middleware can process request data before other middleware and listener functions.

    Handles url_verification requests.

    -

    Refer to https://api.slack.com/events/url_verification for details.

    +

    Refer to https://docs.slack.dev/reference/events/url_verification/ for details.

    Args

    base_logger
    @@ -395,6 +487,12 @@

    Inherited members

  • Classes

    • +

      AsyncAttachingConversationKwargs

      + +
    • +
    • AsyncAttachingFunctionToken

    • @@ -418,7 +516,7 @@

      diff --git a/docs/static/api-docs/slack_bolt/middleware/async_custom_middleware.html b/docs/reference/middleware/async_custom_middleware.html similarity index 99% rename from docs/static/api-docs/slack_bolt/middleware/async_custom_middleware.html rename to docs/reference/middleware/async_custom_middleware.html index 6ef00baaf..d985458ed 100644 --- a/docs/static/api-docs/slack_bolt/middleware/async_custom_middleware.html +++ b/docs/reference/middleware/async_custom_middleware.html @@ -3,7 +3,7 @@ - + slack_bolt.middleware.async_custom_middleware API documentation @@ -166,7 +166,7 @@

      -

      Generated by pdoc 0.11.5.

      +

      Generated by pdoc 0.11.6.

      diff --git a/docs/static/api-docs/slack_bolt/middleware/async_middleware.html b/docs/reference/middleware/async_middleware.html similarity index 97% rename from docs/static/api-docs/slack_bolt/middleware/async_middleware.html rename to docs/reference/middleware/async_middleware.html index e0e38cf9c..f7713b881 100644 --- a/docs/static/api-docs/slack_bolt/middleware/async_middleware.html +++ b/docs/reference/middleware/async_middleware.html @@ -3,7 +3,7 @@ - + slack_bolt.middleware.async_middleware API documentation @@ -104,6 +104,7 @@

      Subclasses

      • AsyncAssistant
      • AsyncCustomMiddleware
      • +
      • AsyncAttachingConversationKwargs
      • AsyncAttachingFunctionToken
      • AsyncAuthorization
      • AsyncIgnoringSelfEvents
      • @@ -232,7 +233,7 @@

        diff --git a/docs/static/api-docs/slack_bolt/middleware/async_middleware_error_handler.html b/docs/reference/middleware/async_middleware_error_handler.html similarity index 97% rename from docs/static/api-docs/slack_bolt/middleware/async_middleware_error_handler.html rename to docs/reference/middleware/async_middleware_error_handler.html index 617e490c7..bf5b101f6 100644 --- a/docs/static/api-docs/slack_bolt/middleware/async_middleware_error_handler.html +++ b/docs/reference/middleware/async_middleware_error_handler.html @@ -3,7 +3,7 @@ - + slack_bolt.middleware.async_middleware_error_handler API documentation @@ -77,9 +77,10 @@

        Classes

        ) returned_response = await self.func(**kwargs) if returned_response is not None and isinstance(returned_response, BoltResponse): - response.status = returned_response.status # type: ignore[union-attr] - response.headers = returned_response.headers # type: ignore[union-attr] - response.body = returned_response.body # type: ignore[union-attr]
        + assert response is not None, "response must be provided when returning a BoltResponse from an error handler" + response.status = returned_response.status + response.headers = returned_response.headers + response.body = returned_response.body

        Ancestors

        @@ -234,7 +235,7 @@

        -

        Generated by pdoc 0.11.5.

        +

        Generated by pdoc 0.11.6.

        diff --git a/docs/reference/middleware/attaching_conversation_kwargs/async_attaching_conversation_kwargs.html b/docs/reference/middleware/attaching_conversation_kwargs/async_attaching_conversation_kwargs.html new file mode 100644 index 000000000..e2bbe7045 --- /dev/null +++ b/docs/reference/middleware/attaching_conversation_kwargs/async_attaching_conversation_kwargs.html @@ -0,0 +1,171 @@ + + + + + + +slack_bolt.middleware.attaching_conversation_kwargs.async_attaching_conversation_kwargs API documentation + + + + + + + + + + + +
        +
        +
        +

        Module slack_bolt.middleware.attaching_conversation_kwargs.async_attaching_conversation_kwargs

        +
        +
        +
        +
        +
        +
        +
        +
        +
        +
        +

        Classes

        +
        +
        +class AsyncAttachingConversationKwargs +(thread_context_store: AsyncAssistantThreadContextStore | None = None) +
        +
        +
        + +Expand source code + +
        class AsyncAttachingConversationKwargs(AsyncMiddleware):
        +
        +    thread_context_store: Optional[AsyncAssistantThreadContextStore]
        +
        +    def __init__(self, thread_context_store: Optional[AsyncAssistantThreadContextStore] = None):
        +        self.thread_context_store = thread_context_store
        +
        +    async def async_process(
        +        self,
        +        *,
        +        req: AsyncBoltRequest,
        +        resp: BoltResponse,
        +        next: Callable[[], Awaitable[BoltResponse]],
        +    ) -> Optional[BoltResponse]:
        +        event = to_event(req.body)
        +        if event is None:
        +            return await next()
        +        if req.context.channel_id is None:
        +            return await next()
        +
        +        if is_assistant_event(req.body):
        +            # TODO: eventually we might remove this assistant specific logic
        +            assistant = AsyncAssistantUtilities(
        +                payload=event,
        +                context=req.context,
        +                thread_context_store=self.thread_context_store,
        +            )
        +            req.context["say"] = assistant.say
        +            req.context["set_title"] = assistant.set_title
        +            req.context["get_thread_context"] = assistant.get_thread_context
        +            req.context["save_thread_context"] = assistant.save_thread_context
        +
        +        if (
        +            is_im_message_event(req.body)
        +            or is_assistant_thread_started_event(req.body)
        +            or is_assistant_thread_context_changed_event(req.body)
        +            or is_app_home_opened_event(req.body, tab="messages")
        +        ):
        +            req.context["set_suggested_prompts"] = AsyncSetSuggestedPrompts(
        +                client=req.context.client,
        +                channel_id=req.context.channel_id,
        +                thread_ts=req.context.thread_ts,
        +            )
        +
        +        # TODO: in the future we might want to introduce a "proper" extract_ts utility
        +        thread_ts_or_ts = req.context.thread_ts or event.get("ts")
        +        if thread_ts_or_ts:
        +            req.context["set_status"] = AsyncSetStatus(
        +                client=req.context.client,
        +                channel_id=req.context.channel_id,
        +                thread_ts=thread_ts_or_ts,
        +            )
        +            req.context["say_stream"] = AsyncSayStream(
        +                client=req.context.client,
        +                channel=req.context.channel_id,
        +                recipient_team_id=req.context.team_id or req.context.enterprise_id,
        +                recipient_user_id=req.context.user_id,
        +                thread_ts=thread_ts_or_ts,
        +            )
        +        return await next()
        +
        +

        A middleware can process request data before other middleware and listener functions.

        +

        Ancestors

        + +

        Class variables

        +
        +
        var thread_context_storeAsyncAssistantThreadContextStore | None
        +
        +

        The type of the None singleton.

        +
        +
        +

        Inherited members

        + +
        +
        +
        +
        + +
        + + + diff --git a/docs/reference/middleware/attaching_conversation_kwargs/attaching_conversation_kwargs.html b/docs/reference/middleware/attaching_conversation_kwargs/attaching_conversation_kwargs.html new file mode 100644 index 000000000..e9d558fec --- /dev/null +++ b/docs/reference/middleware/attaching_conversation_kwargs/attaching_conversation_kwargs.html @@ -0,0 +1,165 @@ + + + + + + +slack_bolt.middleware.attaching_conversation_kwargs.attaching_conversation_kwargs API documentation + + + + + + + + + + + +
        +
        +
        +

        Module slack_bolt.middleware.attaching_conversation_kwargs.attaching_conversation_kwargs

        +
        +
        +
        +
        +
        +
        +
        +
        +
        +
        +

        Classes

        +
        +
        +class AttachingConversationKwargs +(thread_context_store: AssistantThreadContextStore | None = None) +
        +
        +
        + +Expand source code + +
        class AttachingConversationKwargs(Middleware):
        +
        +    thread_context_store: Optional[AssistantThreadContextStore]
        +
        +    def __init__(self, thread_context_store: Optional[AssistantThreadContextStore] = None):
        +        self.thread_context_store = thread_context_store
        +
        +    def process(self, *, req: BoltRequest, resp: BoltResponse, next: Callable[[], BoltResponse]) -> Optional[BoltResponse]:
        +        event = to_event(req.body)
        +        if event is None:
        +            return next()
        +        if req.context.channel_id is None:
        +            return next()
        +
        +        if is_assistant_event(req.body):
        +            # TODO: eventually we might remove this assistant specific logic
        +            assistant = AssistantUtilities(
        +                payload=event,
        +                context=req.context,
        +                thread_context_store=self.thread_context_store,
        +            )
        +            req.context["say"] = assistant.say
        +            req.context["set_title"] = assistant.set_title
        +            req.context["get_thread_context"] = assistant.get_thread_context
        +            req.context["save_thread_context"] = assistant.save_thread_context
        +
        +        if (
        +            is_im_message_event(req.body)
        +            or is_assistant_thread_started_event(req.body)
        +            or is_assistant_thread_context_changed_event(req.body)
        +            or is_app_home_opened_event(req.body, tab="messages")
        +        ):
        +            req.context["set_suggested_prompts"] = SetSuggestedPrompts(
        +                client=req.context.client,
        +                channel_id=req.context.channel_id,
        +                thread_ts=req.context.thread_ts,
        +            )
        +
        +        # TODO: in the future we might want to introduce a "proper" extract_ts utility
        +        thread_ts_or_ts = req.context.thread_ts or event.get("ts")
        +        if thread_ts_or_ts:
        +            req.context["set_status"] = SetStatus(
        +                client=req.context.client,
        +                channel_id=req.context.channel_id,
        +                thread_ts=thread_ts_or_ts,
        +            )
        +            req.context["say_stream"] = SayStream(
        +                client=req.context.client,
        +                channel=req.context.channel_id,
        +                recipient_team_id=req.context.team_id or req.context.enterprise_id,
        +                recipient_user_id=req.context.user_id,
        +                thread_ts=thread_ts_or_ts,
        +            )
        +        return next()
        +
        +

        A middleware can process request data before other middleware and listener functions.

        +

        Ancestors

        + +

        Class variables

        +
        +
        var thread_context_storeAssistantThreadContextStore | None
        +
        +

        The type of the None singleton.

        +
        +
        +

        Inherited members

        + +
        +
        +
        +
        + +
        + + + diff --git a/docs/reference/middleware/attaching_conversation_kwargs/index.html b/docs/reference/middleware/attaching_conversation_kwargs/index.html new file mode 100644 index 000000000..38da4442e --- /dev/null +++ b/docs/reference/middleware/attaching_conversation_kwargs/index.html @@ -0,0 +1,182 @@ + + + + + + +slack_bolt.middleware.attaching_conversation_kwargs API documentation + + + + + + + + + + + +
        +
        +
        +

        Module slack_bolt.middleware.attaching_conversation_kwargs

        +
        +
        +
        +
        +

        Sub-modules

        +
        +
        slack_bolt.middleware.attaching_conversation_kwargs.async_attaching_conversation_kwargs
        +
        +
        +
        +
        slack_bolt.middleware.attaching_conversation_kwargs.attaching_conversation_kwargs
        +
        +
        +
        +
        +
        +
        +
        +
        +
        +
        +

        Classes

        +
        +
        +class AttachingConversationKwargs +(thread_context_store: AssistantThreadContextStore | None = None) +
        +
        +
        + +Expand source code + +
        class AttachingConversationKwargs(Middleware):
        +
        +    thread_context_store: Optional[AssistantThreadContextStore]
        +
        +    def __init__(self, thread_context_store: Optional[AssistantThreadContextStore] = None):
        +        self.thread_context_store = thread_context_store
        +
        +    def process(self, *, req: BoltRequest, resp: BoltResponse, next: Callable[[], BoltResponse]) -> Optional[BoltResponse]:
        +        event = to_event(req.body)
        +        if event is None:
        +            return next()
        +        if req.context.channel_id is None:
        +            return next()
        +
        +        if is_assistant_event(req.body):
        +            # TODO: eventually we might remove this assistant specific logic
        +            assistant = AssistantUtilities(
        +                payload=event,
        +                context=req.context,
        +                thread_context_store=self.thread_context_store,
        +            )
        +            req.context["say"] = assistant.say
        +            req.context["set_title"] = assistant.set_title
        +            req.context["get_thread_context"] = assistant.get_thread_context
        +            req.context["save_thread_context"] = assistant.save_thread_context
        +
        +        if (
        +            is_im_message_event(req.body)
        +            or is_assistant_thread_started_event(req.body)
        +            or is_assistant_thread_context_changed_event(req.body)
        +            or is_app_home_opened_event(req.body, tab="messages")
        +        ):
        +            req.context["set_suggested_prompts"] = SetSuggestedPrompts(
        +                client=req.context.client,
        +                channel_id=req.context.channel_id,
        +                thread_ts=req.context.thread_ts,
        +            )
        +
        +        # TODO: in the future we might want to introduce a "proper" extract_ts utility
        +        thread_ts_or_ts = req.context.thread_ts or event.get("ts")
        +        if thread_ts_or_ts:
        +            req.context["set_status"] = SetStatus(
        +                client=req.context.client,
        +                channel_id=req.context.channel_id,
        +                thread_ts=thread_ts_or_ts,
        +            )
        +            req.context["say_stream"] = SayStream(
        +                client=req.context.client,
        +                channel=req.context.channel_id,
        +                recipient_team_id=req.context.team_id or req.context.enterprise_id,
        +                recipient_user_id=req.context.user_id,
        +                thread_ts=thread_ts_or_ts,
        +            )
        +        return next()
        +
        +

        A middleware can process request data before other middleware and listener functions.

        +

        Ancestors

        + +

        Class variables

        +
        +
        var thread_context_storeAssistantThreadContextStore | None
        +
        +

        The type of the None singleton.

        +
        +
        +

        Inherited members

        + +
        +
        +
        +
        + +
        + + + diff --git a/docs/static/api-docs/slack_bolt/middleware/attaching_function_token/async_attaching_function_token.html b/docs/reference/middleware/attaching_function_token/async_attaching_function_token.html similarity index 98% rename from docs/static/api-docs/slack_bolt/middleware/attaching_function_token/async_attaching_function_token.html rename to docs/reference/middleware/attaching_function_token/async_attaching_function_token.html index ab8b609a2..1becac04e 100644 --- a/docs/static/api-docs/slack_bolt/middleware/attaching_function_token/async_attaching_function_token.html +++ b/docs/reference/middleware/attaching_function_token/async_attaching_function_token.html @@ -3,7 +3,7 @@ - + slack_bolt.middleware.attaching_function_token.async_attaching_function_token API documentation @@ -107,7 +107,7 @@

        -

        Generated by pdoc 0.11.5.

        +

        Generated by pdoc 0.11.6.

        diff --git a/docs/static/api-docs/slack_bolt/middleware/attaching_function_token/attaching_function_token.html b/docs/reference/middleware/attaching_function_token/attaching_function_token.html similarity index 98% rename from docs/static/api-docs/slack_bolt/middleware/attaching_function_token/attaching_function_token.html rename to docs/reference/middleware/attaching_function_token/attaching_function_token.html index f005e8ac1..8eea36647 100644 --- a/docs/static/api-docs/slack_bolt/middleware/attaching_function_token/attaching_function_token.html +++ b/docs/reference/middleware/attaching_function_token/attaching_function_token.html @@ -3,7 +3,7 @@ - + slack_bolt.middleware.attaching_function_token.attaching_function_token API documentation @@ -107,7 +107,7 @@

        -

        Generated by pdoc 0.11.5.

        +

        Generated by pdoc 0.11.6.

        diff --git a/docs/static/api-docs/slack_bolt/middleware/attaching_function_token/index.html b/docs/reference/middleware/attaching_function_token/index.html similarity index 98% rename from docs/static/api-docs/slack_bolt/middleware/attaching_function_token/index.html rename to docs/reference/middleware/attaching_function_token/index.html index 107ae09d6..44efd27a2 100644 --- a/docs/static/api-docs/slack_bolt/middleware/attaching_function_token/index.html +++ b/docs/reference/middleware/attaching_function_token/index.html @@ -3,7 +3,7 @@ - + slack_bolt.middleware.attaching_function_token API documentation @@ -124,7 +124,7 @@

        -

        Generated by pdoc 0.11.5.

        +

        Generated by pdoc 0.11.6.

        diff --git a/docs/static/api-docs/slack_bolt/middleware/authorization/async_authorization.html b/docs/reference/middleware/authorization/async_authorization.html similarity index 98% rename from docs/static/api-docs/slack_bolt/middleware/authorization/async_authorization.html rename to docs/reference/middleware/authorization/async_authorization.html index aef0b3cd7..9f38ea711 100644 --- a/docs/static/api-docs/slack_bolt/middleware/authorization/async_authorization.html +++ b/docs/reference/middleware/authorization/async_authorization.html @@ -3,7 +3,7 @@ - + slack_bolt.middleware.authorization.async_authorization API documentation @@ -102,7 +102,7 @@

        -

        Generated by pdoc 0.11.5.

        +

        Generated by pdoc 0.11.6.

        diff --git a/docs/static/api-docs/slack_bolt/middleware/authorization/async_internals.html b/docs/reference/middleware/authorization/async_internals.html similarity index 98% rename from docs/static/api-docs/slack_bolt/middleware/authorization/async_internals.html rename to docs/reference/middleware/authorization/async_internals.html index 483e8c81f..22b709799 100644 --- a/docs/static/api-docs/slack_bolt/middleware/authorization/async_internals.html +++ b/docs/reference/middleware/authorization/async_internals.html @@ -3,7 +3,7 @@ - + slack_bolt.middleware.authorization.async_internals API documentation @@ -60,7 +60,7 @@

        Module slack_bolt.middleware.authorization.async_interna diff --git a/docs/static/api-docs/slack_bolt/middleware/authorization/async_multi_teams_authorization.html b/docs/reference/middleware/authorization/async_multi_teams_authorization.html similarity index 99% rename from docs/static/api-docs/slack_bolt/middleware/authorization/async_multi_teams_authorization.html rename to docs/reference/middleware/authorization/async_multi_teams_authorization.html index b9bfa138f..50b529f33 100644 --- a/docs/static/api-docs/slack_bolt/middleware/authorization/async_multi_teams_authorization.html +++ b/docs/reference/middleware/authorization/async_multi_teams_authorization.html @@ -3,7 +3,7 @@ - + slack_bolt.middleware.authorization.async_multi_teams_authorization API documentation @@ -216,7 +216,7 @@

        -

        Generated by pdoc 0.11.5.

        +

        Generated by pdoc 0.11.6.

        diff --git a/docs/static/api-docs/slack_bolt/middleware/authorization/async_single_team_authorization.html b/docs/reference/middleware/authorization/async_single_team_authorization.html similarity index 99% rename from docs/static/api-docs/slack_bolt/middleware/authorization/async_single_team_authorization.html rename to docs/reference/middleware/authorization/async_single_team_authorization.html index 2b3c40369..a167d1c68 100644 --- a/docs/static/api-docs/slack_bolt/middleware/authorization/async_single_team_authorization.html +++ b/docs/reference/middleware/authorization/async_single_team_authorization.html @@ -3,7 +3,7 @@ - + slack_bolt.middleware.authorization.async_single_team_authorization API documentation @@ -157,7 +157,7 @@

        -

        Generated by pdoc 0.11.5.

        +

        Generated by pdoc 0.11.6.

        diff --git a/docs/static/api-docs/slack_bolt/middleware/authorization/authorization.html b/docs/reference/middleware/authorization/authorization.html similarity index 98% rename from docs/static/api-docs/slack_bolt/middleware/authorization/authorization.html rename to docs/reference/middleware/authorization/authorization.html index 6922ca354..7ddd4ce41 100644 --- a/docs/static/api-docs/slack_bolt/middleware/authorization/authorization.html +++ b/docs/reference/middleware/authorization/authorization.html @@ -3,7 +3,7 @@ - + slack_bolt.middleware.authorization.authorization API documentation @@ -101,7 +101,7 @@

        -

        Generated by pdoc 0.11.5.

        +

        Generated by pdoc 0.11.6.

        diff --git a/docs/static/api-docs/slack_bolt/middleware/authorization/index.html b/docs/reference/middleware/authorization/index.html similarity index 99% rename from docs/static/api-docs/slack_bolt/middleware/authorization/index.html rename to docs/reference/middleware/authorization/index.html index f97cc17bb..9f5c3f393 100644 --- a/docs/static/api-docs/slack_bolt/middleware/authorization/index.html +++ b/docs/reference/middleware/authorization/index.html @@ -3,7 +3,7 @@ - + slack_bolt.middleware.authorization API documentation @@ -398,7 +398,7 @@

        diff --git a/docs/static/api-docs/slack_bolt/middleware/authorization/internals.html b/docs/reference/middleware/authorization/internals.html similarity index 98% rename from docs/static/api-docs/slack_bolt/middleware/authorization/internals.html rename to docs/reference/middleware/authorization/internals.html index 62db9d31e..c64a7e0f3 100644 --- a/docs/static/api-docs/slack_bolt/middleware/authorization/internals.html +++ b/docs/reference/middleware/authorization/internals.html @@ -3,7 +3,7 @@ - + slack_bolt.middleware.authorization.internals API documentation @@ -60,7 +60,7 @@

        Module slack_bolt.middleware.authorization.internals diff --git a/docs/static/api-docs/slack_bolt/middleware/authorization/multi_teams_authorization.html b/docs/reference/middleware/authorization/multi_teams_authorization.html similarity index 99% rename from docs/static/api-docs/slack_bolt/middleware/authorization/multi_teams_authorization.html rename to docs/reference/middleware/authorization/multi_teams_authorization.html index 1414820c6..c2a6a7964 100644 --- a/docs/static/api-docs/slack_bolt/middleware/authorization/multi_teams_authorization.html +++ b/docs/reference/middleware/authorization/multi_teams_authorization.html @@ -3,7 +3,7 @@ - + slack_bolt.middleware.authorization.multi_teams_authorization API documentation @@ -213,7 +213,7 @@

        -

        Generated by pdoc 0.11.5.

        +

        Generated by pdoc 0.11.6.

        diff --git a/docs/static/api-docs/slack_bolt/middleware/authorization/single_team_authorization.html b/docs/reference/middleware/authorization/single_team_authorization.html similarity index 99% rename from docs/static/api-docs/slack_bolt/middleware/authorization/single_team_authorization.html rename to docs/reference/middleware/authorization/single_team_authorization.html index 535b18532..7687be155 100644 --- a/docs/static/api-docs/slack_bolt/middleware/authorization/single_team_authorization.html +++ b/docs/reference/middleware/authorization/single_team_authorization.html @@ -3,7 +3,7 @@ - + slack_bolt.middleware.authorization.single_team_authorization API documentation @@ -171,7 +171,7 @@

        -

        Generated by pdoc 0.11.5.

        +

        Generated by pdoc 0.11.6.

        diff --git a/docs/static/api-docs/slack_bolt/middleware/custom_middleware.html b/docs/reference/middleware/custom_middleware.html similarity index 99% rename from docs/static/api-docs/slack_bolt/middleware/custom_middleware.html rename to docs/reference/middleware/custom_middleware.html index 4364973a6..aba9dc14b 100644 --- a/docs/static/api-docs/slack_bolt/middleware/custom_middleware.html +++ b/docs/reference/middleware/custom_middleware.html @@ -3,7 +3,7 @@ - + slack_bolt.middleware.custom_middleware API documentation @@ -156,7 +156,7 @@

        diff --git a/docs/static/api-docs/slack_bolt/middleware/ignoring_self_events/async_ignoring_self_events.html b/docs/reference/middleware/ignoring_self_events/async_ignoring_self_events.html similarity index 99% rename from docs/static/api-docs/slack_bolt/middleware/ignoring_self_events/async_ignoring_self_events.html rename to docs/reference/middleware/ignoring_self_events/async_ignoring_self_events.html index 5b0f31653..4d48b16b9 100644 --- a/docs/static/api-docs/slack_bolt/middleware/ignoring_self_events/async_ignoring_self_events.html +++ b/docs/reference/middleware/ignoring_self_events/async_ignoring_self_events.html @@ -3,7 +3,7 @@ - + slack_bolt.middleware.ignoring_self_events.async_ignoring_self_events API documentation @@ -125,7 +125,7 @@

        -

        Generated by pdoc 0.11.5.

        +

        Generated by pdoc 0.11.6.

        diff --git a/docs/static/api-docs/slack_bolt/middleware/ignoring_self_events/ignoring_self_events.html b/docs/reference/middleware/ignoring_self_events/ignoring_self_events.html similarity index 99% rename from docs/static/api-docs/slack_bolt/middleware/ignoring_self_events/ignoring_self_events.html rename to docs/reference/middleware/ignoring_self_events/ignoring_self_events.html index 2c1a5eff3..111c096c4 100644 --- a/docs/static/api-docs/slack_bolt/middleware/ignoring_self_events/ignoring_self_events.html +++ b/docs/reference/middleware/ignoring_self_events/ignoring_self_events.html @@ -3,7 +3,7 @@ - + slack_bolt.middleware.ignoring_self_events.ignoring_self_events API documentation @@ -170,7 +170,7 @@

        -

        Generated by pdoc 0.11.5.

        +

        Generated by pdoc 0.11.6.

        diff --git a/docs/static/api-docs/slack_bolt/middleware/ignoring_self_events/index.html b/docs/reference/middleware/ignoring_self_events/index.html similarity index 99% rename from docs/static/api-docs/slack_bolt/middleware/ignoring_self_events/index.html rename to docs/reference/middleware/ignoring_self_events/index.html index 9c37fbb89..f81603f4a 100644 --- a/docs/static/api-docs/slack_bolt/middleware/ignoring_self_events/index.html +++ b/docs/reference/middleware/ignoring_self_events/index.html @@ -3,7 +3,7 @@ - + slack_bolt.middleware.ignoring_self_events API documentation @@ -187,7 +187,7 @@

        -

        Generated by pdoc 0.11.5.

        +

        Generated by pdoc 0.11.6.

        diff --git a/docs/static/api-docs/slack_bolt/middleware/index.html b/docs/reference/middleware/index.html similarity index 87% rename from docs/static/api-docs/slack_bolt/middleware/index.html rename to docs/reference/middleware/index.html index abbaa52df..153342bc1 100644 --- a/docs/static/api-docs/slack_bolt/middleware/index.html +++ b/docs/reference/middleware/index.html @@ -3,7 +3,7 @@ - + slack_bolt.middleware API documentation @@ -65,6 +65,10 @@

        Sub-modules

        +
        slack_bolt.middleware.attaching_conversation_kwargs
        +
        +
        +
        slack_bolt.middleware.attaching_function_token
        @@ -114,6 +118,92 @@

        Sub-modules

        Classes

        +
        +class AttachingConversationKwargs +(thread_context_store: AssistantThreadContextStore | None = None) +
        +
        +
        + +Expand source code + +
        class AttachingConversationKwargs(Middleware):
        +
        +    thread_context_store: Optional[AssistantThreadContextStore]
        +
        +    def __init__(self, thread_context_store: Optional[AssistantThreadContextStore] = None):
        +        self.thread_context_store = thread_context_store
        +
        +    def process(self, *, req: BoltRequest, resp: BoltResponse, next: Callable[[], BoltResponse]) -> Optional[BoltResponse]:
        +        event = to_event(req.body)
        +        if event is None:
        +            return next()
        +        if req.context.channel_id is None:
        +            return next()
        +
        +        if is_assistant_event(req.body):
        +            # TODO: eventually we might remove this assistant specific logic
        +            assistant = AssistantUtilities(
        +                payload=event,
        +                context=req.context,
        +                thread_context_store=self.thread_context_store,
        +            )
        +            req.context["say"] = assistant.say
        +            req.context["set_title"] = assistant.set_title
        +            req.context["get_thread_context"] = assistant.get_thread_context
        +            req.context["save_thread_context"] = assistant.save_thread_context
        +
        +        if (
        +            is_im_message_event(req.body)
        +            or is_assistant_thread_started_event(req.body)
        +            or is_assistant_thread_context_changed_event(req.body)
        +            or is_app_home_opened_event(req.body, tab="messages")
        +        ):
        +            req.context["set_suggested_prompts"] = SetSuggestedPrompts(
        +                client=req.context.client,
        +                channel_id=req.context.channel_id,
        +                thread_ts=req.context.thread_ts,
        +            )
        +
        +        # TODO: in the future we might want to introduce a "proper" extract_ts utility
        +        thread_ts_or_ts = req.context.thread_ts or event.get("ts")
        +        if thread_ts_or_ts:
        +            req.context["set_status"] = SetStatus(
        +                client=req.context.client,
        +                channel_id=req.context.channel_id,
        +                thread_ts=thread_ts_or_ts,
        +            )
        +            req.context["say_stream"] = SayStream(
        +                client=req.context.client,
        +                channel=req.context.channel_id,
        +                recipient_team_id=req.context.team_id or req.context.enterprise_id,
        +                recipient_user_id=req.context.user_id,
        +                thread_ts=thread_ts_or_ts,
        +            )
        +        return next()
        +
        +

        A middleware can process request data before other middleware and listener functions.

        +

        Ancestors

        + +

        Class variables

        +
        +
        var thread_context_storeAssistantThreadContextStore | None
        +
        +

        The type of the None singleton.

        +
        +
        +

        Inherited members

        + +
        class AttachingFunctionToken
        @@ -385,6 +475,7 @@

        Inherited members

        Subclasses

        • Assistant
        • +
        • AttachingConversationKwargs
        • AttachingFunctionToken
        • Authorization
        • CustomMiddleware
        • @@ -639,15 +730,23 @@

          Inherited members

          """Verifies an incoming request by checking the validity of `x-slack-signature`, `x-slack-request-timestamp`, and its body data. - Refer to https://api.slack.com/authentication/verifying-requests-from-slack for details. + Refer to https://docs.slack.dev/authentication/verifying-requests-from-slack/ for details. Args: signing_secret: The signing secret base_logger: The base logger """ - self.verifier = SignatureVerifier(signing_secret=signing_secret) + self._signing_secret = signing_secret + self._verifier: Optional[SignatureVerifier] = None self.logger = get_bolt_logger(RequestVerification, base_logger=base_logger) + @property + def verifier(self) -> SignatureVerifier: + # Defer initialization to avoid errors during start up + if self._verifier is None: + self._verifier = SignatureVerifier(signing_secret=self._signing_secret) + return self._verifier + def process( self, *, @@ -674,7 +773,7 @@

          Inherited members

          @staticmethod def _can_skip(mode: str, body: Dict[str, Any]) -> bool: - return mode == "socket_mode" or (body is not None and body.get("ssl_check") == "1") + return mode == "socket_mode" @staticmethod def _build_error_response() -> BoltResponse: @@ -688,7 +787,7 @@

          Inherited members

          A middleware can process request data before other middleware and listener functions.

          Verifies an incoming request by checking the validity of x-slack-signature, x-slack-request-timestamp, and its body data.

          -

          Refer to https://api.slack.com/authentication/verifying-requests-from-slack for details.

          +

          Refer to https://docs.slack.dev/authentication/verifying-requests-from-slack/ for details.

          Args

          signing_secret
          @@ -704,6 +803,24 @@

          Subclasses

          +

          Instance variables

          +
          +
          prop verifier : slack_sdk.signature.SignatureVerifier
          +
          +
          + +Expand source code + +
          @property
          +def verifier(self) -> SignatureVerifier:
          +    # Defer initialization to avoid errors during start up
          +    if self._verifier is None:
          +        self._verifier = SignatureVerifier(signing_secret=self._signing_secret)
          +    return self._verifier
          +
          +
          +
          +

          Inherited members

        base_logger
        The base logger

  • @@ -931,7 +1048,7 @@

    Inherited members

    def __init__(self, base_logger: Optional[Logger] = None): """Handles url_verification requests. - Refer to https://api.slack.com/events/url_verification for details. + Refer to https://docs.slack.dev/reference/events/url_verification/ for details. Args: base_logger: The base logger @@ -965,7 +1082,7 @@

    Inherited members

    A middleware can process request data before other middleware and listener functions.

    Handles url_verification requests.

    -

    Refer to https://api.slack.com/events/url_verification for details.

    +

    Refer to https://docs.slack.dev/reference/events/url_verification/ for details.

    Args

    base_logger
    @@ -1009,6 +1126,7 @@

    Inherited members

  • slack_bolt.middleware.async_custom_middleware
  • slack_bolt.middleware.async_middleware
  • slack_bolt.middleware.async_middleware_error_handler
  • +
  • slack_bolt.middleware.attaching_conversation_kwargs
  • slack_bolt.middleware.attaching_function_token
  • slack_bolt.middleware.authorization
  • slack_bolt.middleware.custom_middleware
  • @@ -1024,6 +1142,12 @@

    Inherited members

  • Classes

    • +

      AttachingConversationKwargs

      + +
    • +
    • AttachingFunctionToken

    • @@ -1057,6 +1181,9 @@

      RequestVerification

      +
    • SingleTeamAuthorization

      @@ -1077,7 +1204,7 @@

      -

      Generated by pdoc 0.11.5.

      +

      Generated by pdoc 0.11.6.

      diff --git a/docs/static/api-docs/slack_bolt/middleware/message_listener_matches/async_message_listener_matches.html b/docs/reference/middleware/message_listener_matches/async_message_listener_matches.html similarity index 98% rename from docs/static/api-docs/slack_bolt/middleware/message_listener_matches/async_message_listener_matches.html rename to docs/reference/middleware/message_listener_matches/async_message_listener_matches.html index 790669551..9cbee09ca 100644 --- a/docs/static/api-docs/slack_bolt/middleware/message_listener_matches/async_message_listener_matches.html +++ b/docs/reference/middleware/message_listener_matches/async_message_listener_matches.html @@ -3,7 +3,7 @@ - + slack_bolt.middleware.message_listener_matches.async_message_listener_matches API documentation @@ -124,7 +124,7 @@

      -

      Generated by pdoc 0.11.5.

      +

      Generated by pdoc 0.11.6.

      diff --git a/docs/static/api-docs/slack_bolt/middleware/message_listener_matches/index.html b/docs/reference/middleware/message_listener_matches/index.html similarity index 98% rename from docs/static/api-docs/slack_bolt/middleware/message_listener_matches/index.html rename to docs/reference/middleware/message_listener_matches/index.html index 340bfca8b..29dfbb861 100644 --- a/docs/static/api-docs/slack_bolt/middleware/message_listener_matches/index.html +++ b/docs/reference/middleware/message_listener_matches/index.html @@ -3,7 +3,7 @@ - + slack_bolt.middleware.message_listener_matches API documentation @@ -141,7 +141,7 @@

      -

      Generated by pdoc 0.11.5.

      +

      Generated by pdoc 0.11.6.

      diff --git a/docs/static/api-docs/slack_bolt/middleware/message_listener_matches/message_listener_matches.html b/docs/reference/middleware/message_listener_matches/message_listener_matches.html similarity index 98% rename from docs/static/api-docs/slack_bolt/middleware/message_listener_matches/message_listener_matches.html rename to docs/reference/middleware/message_listener_matches/message_listener_matches.html index 67481a683..35b5bfa7a 100644 --- a/docs/static/api-docs/slack_bolt/middleware/message_listener_matches/message_listener_matches.html +++ b/docs/reference/middleware/message_listener_matches/message_listener_matches.html @@ -3,7 +3,7 @@ - + slack_bolt.middleware.message_listener_matches.message_listener_matches API documentation @@ -124,7 +124,7 @@

      -

      Generated by pdoc 0.11.5.

      +

      Generated by pdoc 0.11.6.

      diff --git a/docs/static/api-docs/slack_bolt/middleware/middleware.html b/docs/reference/middleware/middleware.html similarity index 97% rename from docs/static/api-docs/slack_bolt/middleware/middleware.html rename to docs/reference/middleware/middleware.html index f2940d31b..efa8e6c30 100644 --- a/docs/static/api-docs/slack_bolt/middleware/middleware.html +++ b/docs/reference/middleware/middleware.html @@ -3,7 +3,7 @@ - + slack_bolt.middleware.middleware API documentation @@ -103,6 +103,7 @@

      Classes

      Subclasses

      • Assistant
      • +
      • AttachingConversationKwargs
      • AttachingFunctionToken
      • Authorization
      • CustomMiddleware
      • @@ -232,7 +233,7 @@

        -

        Generated by pdoc 0.11.5.

        +

        Generated by pdoc 0.11.6.

        diff --git a/docs/static/api-docs/slack_bolt/middleware/middleware_error_handler.html b/docs/reference/middleware/middleware_error_handler.html similarity index 97% rename from docs/static/api-docs/slack_bolt/middleware/middleware_error_handler.html rename to docs/reference/middleware/middleware_error_handler.html index 8dcce6026..1c5319feb 100644 --- a/docs/static/api-docs/slack_bolt/middleware/middleware_error_handler.html +++ b/docs/reference/middleware/middleware_error_handler.html @@ -3,7 +3,7 @@ - + slack_bolt.middleware.middleware_error_handler API documentation @@ -77,9 +77,10 @@

        Classes

        ) returned_response = self.func(**kwargs) if returned_response is not None and isinstance(returned_response, BoltResponse): - response.status = returned_response.status # type: ignore[union-attr] - response.headers = returned_response.headers # type: ignore[union-attr] - response.body = returned_response.body # type: ignore[union-attr]
        + assert response is not None, "response must be provided when returning a BoltResponse from an error handler" + response.status = returned_response.status + response.headers = returned_response.headers + response.body = returned_response.body

        Ancestors

        @@ -234,7 +235,7 @@

        -

        Generated by pdoc 0.11.5.

        +

        Generated by pdoc 0.11.6.

        diff --git a/docs/static/api-docs/slack_bolt/middleware/request_verification/async_request_verification.html b/docs/reference/middleware/request_verification/async_request_verification.html similarity index 95% rename from docs/static/api-docs/slack_bolt/middleware/request_verification/async_request_verification.html rename to docs/reference/middleware/request_verification/async_request_verification.html index e5d368b8b..192f77933 100644 --- a/docs/static/api-docs/slack_bolt/middleware/request_verification/async_request_verification.html +++ b/docs/reference/middleware/request_verification/async_request_verification.html @@ -3,7 +3,7 @@ - + slack_bolt.middleware.request_verification.async_request_verification API documentation @@ -59,7 +59,7 @@

        Classes

        """Verifies an incoming request by checking the validity of `x-slack-signature`, `x-slack-request-timestamp`, and its body data. - Refer to https://api.slack.com/authentication/verifying-requests-from-slack for details. + Refer to https://docs.slack.dev/authentication/verifying-requests-from-slack/ for details. """ async def async_process( @@ -86,10 +86,10 @@

        Classes

        Verifies an incoming request by checking the validity of x-slack-signature, x-slack-request-timestamp, and its body data.

        -

        Refer to https://api.slack.com/authentication/verifying-requests-from-slack for details.

        +

        Refer to https://docs.slack.dev/authentication/verifying-requests-from-slack/ for details.

        Verifies an incoming request by checking the validity of x-slack-signature, x-slack-request-timestamp, and its body data.

        -

        Refer to https://api.slack.com/authentication/verifying-requests-from-slack for details.

        +

        Refer to https://docs.slack.dev/authentication/verifying-requests-from-slack/ for details.

        Args

        signing_secret
        @@ -142,7 +142,7 @@

        -

        Generated by pdoc 0.11.5.

        +

        Generated by pdoc 0.11.6.

        diff --git a/docs/static/api-docs/slack_bolt/middleware/request_verification/index.html b/docs/reference/middleware/request_verification/index.html similarity index 87% rename from docs/static/api-docs/slack_bolt/middleware/request_verification/index.html rename to docs/reference/middleware/request_verification/index.html index 228076e0d..50a8676b5 100644 --- a/docs/static/api-docs/slack_bolt/middleware/request_verification/index.html +++ b/docs/reference/middleware/request_verification/index.html @@ -3,7 +3,7 @@ - + slack_bolt.middleware.request_verification API documentation @@ -71,15 +71,23 @@

        Classes

        """Verifies an incoming request by checking the validity of `x-slack-signature`, `x-slack-request-timestamp`, and its body data. - Refer to https://api.slack.com/authentication/verifying-requests-from-slack for details. + Refer to https://docs.slack.dev/authentication/verifying-requests-from-slack/ for details. Args: signing_secret: The signing secret base_logger: The base logger """ - self.verifier = SignatureVerifier(signing_secret=signing_secret) + self._signing_secret = signing_secret + self._verifier: Optional[SignatureVerifier] = None self.logger = get_bolt_logger(RequestVerification, base_logger=base_logger) + @property + def verifier(self) -> SignatureVerifier: + # Defer initialization to avoid errors during start up + if self._verifier is None: + self._verifier = SignatureVerifier(signing_secret=self._signing_secret) + return self._verifier + def process( self, *, @@ -106,7 +114,7 @@

        Classes

        @staticmethod def _can_skip(mode: str, body: Dict[str, Any]) -> bool: - return mode == "socket_mode" or (body is not None and body.get("ssl_check") == "1") + return mode == "socket_mode" @staticmethod def _build_error_response() -> BoltResponse: @@ -120,7 +128,7 @@

        Classes

        A middleware can process request data before other middleware and listener functions.

        Verifies an incoming request by checking the validity of x-slack-signature, x-slack-request-timestamp, and its body data.

        -

        Refer to https://api.slack.com/authentication/verifying-requests-from-slack for details.

        +

        Refer to https://docs.slack.dev/authentication/verifying-requests-from-slack/ for details.

        Args

        signing_secret
        @@ -136,6 +144,24 @@

        Subclasses

        +

        Instance variables

        +
        +
        prop verifier : slack_sdk.signature.SignatureVerifier
        +
        +
        + +Expand source code + +
        @property
        +def verifier(self) -> SignatureVerifier:
        +    # Defer initialization to avoid errors during start up
        +    if self._verifier is None:
        +        self._verifier = SignatureVerifier(signing_secret=self._signing_secret)
        +    return self._verifier
        +
        +
        +
        +

        Inherited members

        • Middleware: @@ -169,6 +195,9 @@

          Inherited members

        • @@ -176,7 +205,7 @@

          -

          Generated by pdoc 0.11.5.

          +

          Generated by pdoc 0.11.6.

          diff --git a/docs/static/api-docs/slack_bolt/middleware/request_verification/request_verification.html b/docs/reference/middleware/request_verification/request_verification.html similarity index 86% rename from docs/static/api-docs/slack_bolt/middleware/request_verification/request_verification.html rename to docs/reference/middleware/request_verification/request_verification.html index 675aa4cdd..4ee2ed1b1 100644 --- a/docs/static/api-docs/slack_bolt/middleware/request_verification/request_verification.html +++ b/docs/reference/middleware/request_verification/request_verification.html @@ -3,7 +3,7 @@ - + slack_bolt.middleware.request_verification.request_verification API documentation @@ -60,15 +60,23 @@

          Classes

          """Verifies an incoming request by checking the validity of `x-slack-signature`, `x-slack-request-timestamp`, and its body data. - Refer to https://api.slack.com/authentication/verifying-requests-from-slack for details. + Refer to https://docs.slack.dev/authentication/verifying-requests-from-slack/ for details. Args: signing_secret: The signing secret base_logger: The base logger """ - self.verifier = SignatureVerifier(signing_secret=signing_secret) + self._signing_secret = signing_secret + self._verifier: Optional[SignatureVerifier] = None self.logger = get_bolt_logger(RequestVerification, base_logger=base_logger) + @property + def verifier(self) -> SignatureVerifier: + # Defer initialization to avoid errors during start up + if self._verifier is None: + self._verifier = SignatureVerifier(signing_secret=self._signing_secret) + return self._verifier + def process( self, *, @@ -95,7 +103,7 @@

          Classes

          @staticmethod def _can_skip(mode: str, body: Dict[str, Any]) -> bool: - return mode == "socket_mode" or (body is not None and body.get("ssl_check") == "1") + return mode == "socket_mode" @staticmethod def _build_error_response() -> BoltResponse: @@ -109,7 +117,7 @@

          Classes

          A middleware can process request data before other middleware and listener functions.

          Verifies an incoming request by checking the validity of x-slack-signature, x-slack-request-timestamp, and its body data.

          -

          Refer to https://api.slack.com/authentication/verifying-requests-from-slack for details.

          +

          Refer to https://docs.slack.dev/authentication/verifying-requests-from-slack/ for details.

          Args

          signing_secret
          @@ -125,6 +133,24 @@

          Subclasses

          +

          Instance variables

          +
          +
          prop verifier : slack_sdk.signature.SignatureVerifier
          +
          +
          + +Expand source code + +
          @property
          +def verifier(self) -> SignatureVerifier:
          +    # Defer initialization to avoid errors during start up
          +    if self._verifier is None:
          +        self._verifier = SignatureVerifier(signing_secret=self._signing_secret)
          +    return self._verifier
          +
          +
          +
          +

          Inherited members

          • Middleware: @@ -152,6 +178,9 @@

            Inherited members

          • @@ -159,7 +188,7 @@

            -

            Generated by pdoc 0.11.5.

            +

            Generated by pdoc 0.11.6.

            diff --git a/docs/static/api-docs/slack_bolt/middleware/ssl_check/async_ssl_check.html b/docs/reference/middleware/ssl_check/async_ssl_check.html similarity index 95% rename from docs/static/api-docs/slack_bolt/middleware/ssl_check/async_ssl_check.html rename to docs/reference/middleware/ssl_check/async_ssl_check.html index d19f77eba..48c4bb599 100644 --- a/docs/static/api-docs/slack_bolt/middleware/ssl_check/async_ssl_check.html +++ b/docs/reference/middleware/ssl_check/async_ssl_check.html @@ -3,7 +3,7 @@ - + slack_bolt.middleware.ssl_check.async_ssl_check API documentation @@ -75,12 +75,12 @@

            Classes

            A middleware can process request data before other middleware and listener functions.

            Handles ssl_check requests. -Refer to https://api.slack.com/interactivity/slash-commands for details.

            +Refer to https://docs.slack.dev/interactivity/implementing-slash-commands/ for details.

            Args

            verification_token
            The verification token to check -(optional as it's already deprecated - https://api.slack.com/authentication/verifying-requests-from-slack#verification_token_deprecation)
            +(optional as it's already deprecated - https://docs.slack.dev/authentication/verifying-requests-from-slack/#deprecation)
            base_logger
            The base logger
            @@ -131,7 +131,7 @@

            -

            Generated by pdoc 0.11.5.

            +

            Generated by pdoc 0.11.6.

            diff --git a/docs/static/api-docs/slack_bolt/middleware/ssl_check/index.html b/docs/reference/middleware/ssl_check/index.html similarity index 94% rename from docs/static/api-docs/slack_bolt/middleware/ssl_check/index.html rename to docs/reference/middleware/ssl_check/index.html index e2d67d334..6c1e4725e 100644 --- a/docs/static/api-docs/slack_bolt/middleware/ssl_check/index.html +++ b/docs/reference/middleware/ssl_check/index.html @@ -3,7 +3,7 @@ - + slack_bolt.middleware.ssl_check API documentation @@ -76,11 +76,11 @@

            Classes

            base_logger: Optional[Logger] = None, ): """Handles `ssl_check` requests. - Refer to https://api.slack.com/interactivity/slash-commands for details. + Refer to https://docs.slack.dev/interactivity/implementing-slash-commands/ for details. Args: verification_token: The verification token to check - (optional as it's already deprecated - https://api.slack.com/authentication/verifying-requests-from-slack#verification_token_deprecation) + (optional as it's already deprecated - https://docs.slack.dev/authentication/verifying-requests-from-slack/#deprecation) base_logger: The base logger """ # noqa: E501 self.verification_token = verification_token @@ -122,12 +122,12 @@

            Classes

            A middleware can process request data before other middleware and listener functions.

            Handles slack_bolt.middleware.ssl_check.ssl_check requests. -Refer to https://api.slack.com/interactivity/slash-commands for details.

            +Refer to https://docs.slack.dev/interactivity/implementing-slash-commands/ for details.

            Args

            verification_token
            The verification token to check -(optional as it's already deprecated - https://api.slack.com/authentication/verifying-requests-from-slack#verification_token_deprecation)
            +(optional as it's already deprecated - https://docs.slack.dev/authentication/verifying-requests-from-slack/#deprecation)
            base_logger
            The base logger
            @@ -194,7 +194,7 @@

            -

            Generated by pdoc 0.11.5.

            +

            Generated by pdoc 0.11.6.

            diff --git a/docs/static/api-docs/slack_bolt/middleware/ssl_check/ssl_check.html b/docs/reference/middleware/ssl_check/ssl_check.html similarity index 94% rename from docs/static/api-docs/slack_bolt/middleware/ssl_check/ssl_check.html rename to docs/reference/middleware/ssl_check/ssl_check.html index d8985697f..f90ad4d87 100644 --- a/docs/static/api-docs/slack_bolt/middleware/ssl_check/ssl_check.html +++ b/docs/reference/middleware/ssl_check/ssl_check.html @@ -3,7 +3,7 @@ - + slack_bolt.middleware.ssl_check.ssl_check API documentation @@ -65,11 +65,11 @@

            Classes

            base_logger: Optional[Logger] = None, ): """Handles `ssl_check` requests. - Refer to https://api.slack.com/interactivity/slash-commands for details. + Refer to https://docs.slack.dev/interactivity/implementing-slash-commands/ for details. Args: verification_token: The verification token to check - (optional as it's already deprecated - https://api.slack.com/authentication/verifying-requests-from-slack#verification_token_deprecation) + (optional as it's already deprecated - https://docs.slack.dev/authentication/verifying-requests-from-slack/#deprecation) base_logger: The base logger """ # noqa: E501 self.verification_token = verification_token @@ -111,12 +111,12 @@

            Classes

            A middleware can process request data before other middleware and listener functions.

            Handles ssl_check requests. -Refer to https://api.slack.com/interactivity/slash-commands for details.

            +Refer to https://docs.slack.dev/interactivity/implementing-slash-commands/ for details.

            Args

            verification_token
            The verification token to check -(optional as it's already deprecated - https://api.slack.com/authentication/verifying-requests-from-slack#verification_token_deprecation)
            +(optional as it's already deprecated - https://docs.slack.dev/authentication/verifying-requests-from-slack/#deprecation)
            base_logger
            The base logger
            @@ -177,7 +177,7 @@

            -

            Generated by pdoc 0.11.5.

            +

            Generated by pdoc 0.11.6.

            diff --git a/docs/static/api-docs/slack_bolt/middleware/url_verification/async_url_verification.html b/docs/reference/middleware/url_verification/async_url_verification.html similarity index 97% rename from docs/static/api-docs/slack_bolt/middleware/url_verification/async_url_verification.html rename to docs/reference/middleware/url_verification/async_url_verification.html index 44bcaee1f..d1408052d 100644 --- a/docs/static/api-docs/slack_bolt/middleware/url_verification/async_url_verification.html +++ b/docs/reference/middleware/url_verification/async_url_verification.html @@ -3,7 +3,7 @@ - + slack_bolt.middleware.url_verification.async_url_verification API documentation @@ -73,7 +73,7 @@

            Classes

            A middleware can process request data before other middleware and listener functions.

            Handles url_verification requests.

            -

            Refer to https://api.slack.com/events/url_verification for details.

            +

            Refer to https://docs.slack.dev/reference/events/url_verification/ for details.

            Args

            base_logger
            @@ -124,7 +124,7 @@

            -

            Generated by pdoc 0.11.5.

            +

            Generated by pdoc 0.11.6.

            diff --git a/docs/static/api-docs/slack_bolt/middleware/url_verification/index.html b/docs/reference/middleware/url_verification/index.html similarity index 96% rename from docs/static/api-docs/slack_bolt/middleware/url_verification/index.html rename to docs/reference/middleware/url_verification/index.html index ff0720cf8..480c861d6 100644 --- a/docs/static/api-docs/slack_bolt/middleware/url_verification/index.html +++ b/docs/reference/middleware/url_verification/index.html @@ -3,7 +3,7 @@ - + slack_bolt.middleware.url_verification API documentation @@ -70,7 +70,7 @@

            Classes

            def __init__(self, base_logger: Optional[Logger] = None): """Handles url_verification requests. - Refer to https://api.slack.com/events/url_verification for details. + Refer to https://docs.slack.dev/reference/events/url_verification/ for details. Args: base_logger: The base logger @@ -104,7 +104,7 @@

            Classes

            A middleware can process request data before other middleware and listener functions.

            Handles url_verification requests.

            -

            Refer to https://api.slack.com/events/url_verification for details.

            +

            Refer to https://docs.slack.dev/reference/events/url_verification/ for details.

            Args

            base_logger
            @@ -158,7 +158,7 @@

            diff --git a/docs/static/api-docs/slack_bolt/middleware/url_verification/url_verification.html b/docs/reference/middleware/url_verification/url_verification.html similarity index 96% rename from docs/static/api-docs/slack_bolt/middleware/url_verification/url_verification.html rename to docs/reference/middleware/url_verification/url_verification.html index 04ad520e6..ff22c2986 100644 --- a/docs/static/api-docs/slack_bolt/middleware/url_verification/url_verification.html +++ b/docs/reference/middleware/url_verification/url_verification.html @@ -3,7 +3,7 @@ - + slack_bolt.middleware.url_verification.url_verification API documentation @@ -59,7 +59,7 @@

            Classes

            def __init__(self, base_logger: Optional[Logger] = None): """Handles url_verification requests. - Refer to https://api.slack.com/events/url_verification for details. + Refer to https://docs.slack.dev/reference/events/url_verification/ for details. Args: base_logger: The base logger @@ -93,7 +93,7 @@

            Classes

            A middleware can process request data before other middleware and listener functions.

            Handles url_verification requests.

            -

            Refer to https://api.slack.com/events/url_verification for details.

            +

            Refer to https://docs.slack.dev/reference/events/url_verification/ for details.

            Args

            base_logger
            @@ -141,7 +141,7 @@

            -

            Generated by pdoc 0.11.5.

            +

            Generated by pdoc 0.11.6.

            diff --git a/docs/static/api-docs/slack_bolt/oauth/async_callback_options.html b/docs/reference/oauth/async_callback_options.html similarity index 98% rename from docs/static/api-docs/slack_bolt/oauth/async_callback_options.html rename to docs/reference/oauth/async_callback_options.html index 40ae06c1f..d07f1aee5 100644 --- a/docs/static/api-docs/slack_bolt/oauth/async_callback_options.html +++ b/docs/reference/oauth/async_callback_options.html @@ -3,7 +3,7 @@ - + slack_bolt.oauth.async_callback_options API documentation @@ -101,7 +101,7 @@

            Class variables

            reason: str, error: Optional[Exception] = None, suggested_status_code: int, - settings: "AsyncOAuthSettings", # type: ignore[name-defined] + settings: "AsyncOAuthSettings", default: "AsyncCallbackOptions", ): """The arguments for a failure function. @@ -153,7 +153,7 @@

            Args

            *, request: AsyncBoltRequest, installation: Installation, - settings: "AsyncOAuthSettings", # type: ignore[name-defined] + settings: "AsyncOAuthSettings", default: "AsyncCallbackOptions", ): """The arguments for a success function. @@ -279,7 +279,7 @@

            -

            Generated by pdoc 0.11.5.

            +

            Generated by pdoc 0.11.6.

            diff --git a/docs/static/api-docs/slack_bolt/oauth/async_internals.html b/docs/reference/oauth/async_internals.html similarity index 98% rename from docs/static/api-docs/slack_bolt/oauth/async_internals.html rename to docs/reference/oauth/async_internals.html index ba6a31642..2b35a69c9 100644 --- a/docs/static/api-docs/slack_bolt/oauth/async_internals.html +++ b/docs/reference/oauth/async_internals.html @@ -3,7 +3,7 @@ - + slack_bolt.oauth.async_internals API documentation @@ -120,7 +120,7 @@

            Functions

            diff --git a/docs/static/api-docs/slack_bolt/oauth/async_oauth_flow.html b/docs/reference/oauth/async_oauth_flow.html similarity index 99% rename from docs/static/api-docs/slack_bolt/oauth/async_oauth_flow.html rename to docs/reference/oauth/async_oauth_flow.html index 182973db5..3ccdfd6f0 100644 --- a/docs/static/api-docs/slack_bolt/oauth/async_oauth_flow.html +++ b/docs/reference/oauth/async_oauth_flow.html @@ -3,7 +3,7 @@ - + slack_bolt.oauth.async_oauth_flow API documentation @@ -803,7 +803,7 @@

            -

            Generated by pdoc 0.11.5.

            +

            Generated by pdoc 0.11.6.

            diff --git a/docs/static/api-docs/slack_bolt/oauth/async_oauth_settings.html b/docs/reference/oauth/async_oauth_settings.html similarity index 99% rename from docs/static/api-docs/slack_bolt/oauth/async_oauth_settings.html rename to docs/reference/oauth/async_oauth_settings.html index 18b108491..3b8c04edb 100644 --- a/docs/static/api-docs/slack_bolt/oauth/async_oauth_settings.html +++ b/docs/reference/oauth/async_oauth_settings.html @@ -3,7 +3,7 @@ - + slack_bolt.oauth.async_oauth_settings API documentation @@ -48,7 +48,7 @@

            Classes

            class AsyncOAuthSettings -(*,
            client_id: str | None = None,
            client_secret: str | None = None,
            scopes: str | Sequence[str] | None = None,
            user_scopes: str | Sequence[str] | None = None,
            redirect_uri: str | None = None,
            install_path: str = '/slack/install',
            install_page_rendering_enabled: bool = True,
            redirect_uri_path: str = '/slack/oauth_redirect',
            callback_options: AsyncCallbackOptions | None = None,
            success_url: str | None = None,
            failure_url: str | None = None,
            authorization_url: str | None = None,
            installation_store: slack_sdk.oauth.installation_store.async_installation_store.AsyncInstallationStore | None = None,
            installation_store_bot_only: bool = False,
            token_rotation_expiration_minutes: int = 120,
            user_token_resolution: str = 'authed_user',
            state_validation_enabled: bool = True,
            state_store: slack_sdk.oauth.state_store.async_state_store.AsyncOAuthStateStore | None = None,
            state_cookie_name: str = 'slack-app-oauth-state',
            state_expiration_seconds: int = 600,
            logger: logging.Logger = <Logger slack_bolt.oauth.async_oauth_settings (WARNING)>)
            +(*,
            client_id: str | None = None,
            client_secret: str | None = None,
            scopes: Sequence[str] | str | None = None,
            user_scopes: Sequence[str] | str | None = None,
            redirect_uri: str | None = None,
            install_path: str = '/slack/install',
            install_page_rendering_enabled: bool = True,
            redirect_uri_path: str = '/slack/oauth_redirect',
            callback_options: AsyncCallbackOptions | None = None,
            success_url: str | None = None,
            failure_url: str | None = None,
            authorization_url: str | None = None,
            installation_store: slack_sdk.oauth.installation_store.async_installation_store.AsyncInstallationStore | None = None,
            installation_store_bot_only: bool = False,
            token_rotation_expiration_minutes: int = 120,
            user_token_resolution: str = 'authed_user',
            state_validation_enabled: bool = True,
            state_store: slack_sdk.oauth.state_store.async_state_store.AsyncOAuthStateStore | None = None,
            state_cookie_name: str = 'slack-app-oauth-state',
            state_expiration_seconds: int = 600,
            logger: logging.Logger = <Logger slack_bolt.oauth.async_oauth_settings (WARNING)>)
            @@ -417,7 +417,7 @@

            diff --git a/docs/static/api-docs/slack_bolt/oauth/callback_options.html b/docs/reference/oauth/callback_options.html similarity index 98% rename from docs/static/api-docs/slack_bolt/oauth/callback_options.html rename to docs/reference/oauth/callback_options.html index ac2a200b1..c6fc81286 100644 --- a/docs/static/api-docs/slack_bolt/oauth/callback_options.html +++ b/docs/reference/oauth/callback_options.html @@ -3,7 +3,7 @@ - + slack_bolt.oauth.callback_options API documentation @@ -181,7 +181,7 @@

            Inherited members

            reason: str, error: Optional[Exception] = None, suggested_status_code: int, - settings: "OAuthSettings", # type: ignore[name-defined] + settings: "OAuthSettings", default: "CallbackOptions", ): """The arguments for a failure function. @@ -233,7 +233,7 @@

            Args

            *, request: BoltRequest, installation: Installation, - settings: "OAuthSettings", # type: ignore[name-defined] + settings: "OAuthSettings", default: "CallbackOptions", ): """The arguments for a success function. @@ -299,7 +299,7 @@

            -

            Generated by pdoc 0.11.5.

            +

            Generated by pdoc 0.11.6.

            diff --git a/docs/static/api-docs/slack_bolt/oauth/index.html b/docs/reference/oauth/index.html similarity index 99% rename from docs/static/api-docs/slack_bolt/oauth/index.html rename to docs/reference/oauth/index.html index 6281fbcc1..d53dc6a41 100644 --- a/docs/static/api-docs/slack_bolt/oauth/index.html +++ b/docs/reference/oauth/index.html @@ -3,7 +3,7 @@ - + slack_bolt.oauth API documentation @@ -37,7 +37,7 @@

            Module slack_bolt.oauth

            Slack OAuth flow support for building an app that is installable in any workspaces.

            -

            Refer to https://slack.dev/bolt-python/concepts#authenticating-oauth for details.

            +

            Refer to https://docs.slack.dev/tools/bolt-python/concepts/authenticating-oauth for details.

            Sub-modules

            @@ -856,7 +856,7 @@

            -

            Generated by pdoc 0.11.5.

            +

            Generated by pdoc 0.11.6.

            diff --git a/docs/static/api-docs/slack_bolt/oauth/internals.html b/docs/reference/oauth/internals.html similarity index 99% rename from docs/static/api-docs/slack_bolt/oauth/internals.html rename to docs/reference/oauth/internals.html index e1b239782..3f1b43a7e 100644 --- a/docs/static/api-docs/slack_bolt/oauth/internals.html +++ b/docs/reference/oauth/internals.html @@ -3,7 +3,7 @@ - + slack_bolt.oauth.internals API documentation @@ -225,7 +225,7 @@

            -

            Generated by pdoc 0.11.5.

            +

            Generated by pdoc 0.11.6.

            diff --git a/docs/static/api-docs/slack_bolt/oauth/oauth_flow.html b/docs/reference/oauth/oauth_flow.html similarity index 99% rename from docs/static/api-docs/slack_bolt/oauth/oauth_flow.html rename to docs/reference/oauth/oauth_flow.html index abae18cb5..75aa3cb88 100644 --- a/docs/static/api-docs/slack_bolt/oauth/oauth_flow.html +++ b/docs/reference/oauth/oauth_flow.html @@ -3,7 +3,7 @@ - + slack_bolt.oauth.oauth_flow API documentation @@ -807,7 +807,7 @@

            -

            Generated by pdoc 0.11.5.

            +

            Generated by pdoc 0.11.6.

            diff --git a/docs/static/api-docs/slack_bolt/oauth/oauth_settings.html b/docs/reference/oauth/oauth_settings.html similarity index 99% rename from docs/static/api-docs/slack_bolt/oauth/oauth_settings.html rename to docs/reference/oauth/oauth_settings.html index 0555b761c..cd8def497 100644 --- a/docs/static/api-docs/slack_bolt/oauth/oauth_settings.html +++ b/docs/reference/oauth/oauth_settings.html @@ -3,7 +3,7 @@ - + slack_bolt.oauth.oauth_settings API documentation @@ -48,7 +48,7 @@

            Classes

            class OAuthSettings -(*,
            client_id: str | None = None,
            client_secret: str | None = None,
            scopes: str | Sequence[str] | None = None,
            user_scopes: str | Sequence[str] | None = None,
            redirect_uri: str | None = None,
            install_path: str = '/slack/install',
            install_page_rendering_enabled: bool = True,
            redirect_uri_path: str = '/slack/oauth_redirect',
            callback_options: CallbackOptions | None = None,
            success_url: str | None = None,
            failure_url: str | None = None,
            authorization_url: str | None = None,
            installation_store: slack_sdk.oauth.installation_store.installation_store.InstallationStore | None = None,
            installation_store_bot_only: bool = False,
            token_rotation_expiration_minutes: int = 120,
            user_token_resolution: str = 'authed_user',
            state_validation_enabled: bool = True,
            state_store: slack_sdk.oauth.state_store.state_store.OAuthStateStore | None = None,
            state_cookie_name: str = 'slack-app-oauth-state',
            state_expiration_seconds: int = 600,
            logger: logging.Logger = <Logger slack_bolt.oauth.oauth_settings (WARNING)>)
            +(*,
            client_id: str | None = None,
            client_secret: str | None = None,
            scopes: Sequence[str] | str | None = None,
            user_scopes: Sequence[str] | str | None = None,
            redirect_uri: str | None = None,
            install_path: str = '/slack/install',
            install_page_rendering_enabled: bool = True,
            redirect_uri_path: str = '/slack/oauth_redirect',
            callback_options: CallbackOptions | None = None,
            success_url: str | None = None,
            failure_url: str | None = None,
            authorization_url: str | None = None,
            installation_store: slack_sdk.oauth.installation_store.installation_store.InstallationStore | None = None,
            installation_store_bot_only: bool = False,
            token_rotation_expiration_minutes: int = 120,
            user_token_resolution: str = 'authed_user',
            state_validation_enabled: bool = True,
            state_store: slack_sdk.oauth.state_store.state_store.OAuthStateStore | None = None,
            state_cookie_name: str = 'slack-app-oauth-state',
            state_expiration_seconds: int = 600,
            logger: logging.Logger = <Logger slack_bolt.oauth.oauth_settings (WARNING)>)
            @@ -415,7 +415,7 @@

            -

            Generated by pdoc 0.11.5.

            +

            Generated by pdoc 0.11.6.

            diff --git a/docs/static/api-docs/slack_bolt/request/async_internals.html b/docs/reference/request/async_internals.html similarity index 98% rename from docs/static/api-docs/slack_bolt/request/async_internals.html rename to docs/reference/request/async_internals.html index 635bdf586..35a250c8d 100644 --- a/docs/static/api-docs/slack_bolt/request/async_internals.html +++ b/docs/reference/request/async_internals.html @@ -3,7 +3,7 @@ - + slack_bolt.request.async_internals API documentation @@ -129,7 +129,7 @@

            Functions

            diff --git a/docs/static/api-docs/slack_bolt/request/async_request.html b/docs/reference/request/async_request.html similarity index 99% rename from docs/static/api-docs/slack_bolt/request/async_request.html rename to docs/reference/request/async_request.html index c08c47ec9..a3658710a 100644 --- a/docs/static/api-docs/slack_bolt/request/async_request.html +++ b/docs/reference/request/async_request.html @@ -3,7 +3,7 @@ - + slack_bolt.request.async_request API documentation @@ -238,7 +238,7 @@

            -

            Generated by pdoc 0.11.5.

            +

            Generated by pdoc 0.11.6.

            diff --git a/docs/static/api-docs/slack_bolt/request/index.html b/docs/reference/request/index.html similarity index 98% rename from docs/static/api-docs/slack_bolt/request/index.html rename to docs/reference/request/index.html index 8e00109c1..84cd15050 100644 --- a/docs/static/api-docs/slack_bolt/request/index.html +++ b/docs/reference/request/index.html @@ -3,7 +3,7 @@ - + slack_bolt.request API documentation @@ -37,7 +37,7 @@

            Module slack_bolt.request

            Incoming request from Slack through either HTTP request or Socket Mode connection.

            -

            Refer to https://api.slack.com/apis/connections for the two types of connections. +

            Refer to https://docs.slack.dev/apis/events-api/ for the two types of connections. This interface encapsulates the difference between the two.

            @@ -272,7 +272,7 @@

            -

            Generated by pdoc 0.11.5.

            +

            Generated by pdoc 0.11.6.

            diff --git a/docs/static/api-docs/slack_bolt/request/internals.html b/docs/reference/request/internals.html similarity index 91% rename from docs/static/api-docs/slack_bolt/request/internals.html rename to docs/reference/request/internals.html index 68fa34dae..d25880135 100644 --- a/docs/static/api-docs/slack_bolt/request/internals.html +++ b/docs/reference/request/internals.html @@ -3,7 +3,7 @@ - + slack_bolt.request.internals API documentation @@ -268,12 +268,12 @@

            Functions

            return channel.get("id") if "channel_id" in payload: return payload.get("channel_id") - if payload.get("event") is not None: + if isinstance(payload.get("event"), dict): return extract_channel_id(payload["event"]) - if payload.get("item") is not None: + if isinstance(payload.get("item"), dict): # reaction_added: body["event"]["item"] return extract_channel_id(payload["item"]) - if payload.get("assistant_thread") is not None: + if isinstance(payload.get("assistant_thread"), dict): # assistant_thread_started return extract_channel_id(payload["assistant_thread"]) return None
            @@ -317,10 +317,10 @@

            Functions

            return extract_enterprise_id(payload["authorizations"][0]) if "enterprise_id" in payload: return payload.get("enterprise_id") - if payload.get("team") is not None and "enterprise_id" in payload["team"]: + if isinstance(payload.get("team"), dict) and "enterprise_id" in payload["team"]: # In the case where the type is view_submission return payload["team"].get("enterprise_id") - if payload.get("event") is not None: + if isinstance(payload.get("event"), dict): return extract_enterprise_id(payload["event"]) return None

            @@ -337,7 +337,7 @@

            Functions

            def extract_function_bot_access_token(payload: Dict[str, Any]) -> Optional[str]:
                 if payload.get("bot_access_token") is not None:
                     return payload.get("bot_access_token")
            -    if payload.get("event") is not None:
            +    if isinstance(payload.get("event"), dict):
                     return payload["event"].get("bot_access_token")
                 return None

            @@ -354,9 +354,9 @@

            Functions

            def extract_function_execution_id(payload: Dict[str, Any]) -> Optional[str]:
                 if payload.get("function_execution_id") is not None:
                     return payload.get("function_execution_id")
            -    if payload.get("event") is not None:
            +    if isinstance(payload.get("event"), dict):
                     return extract_function_execution_id(payload["event"])
            -    if payload.get("function_data") is not None:
            +    if isinstance(payload.get("function_data"), dict):
                     return payload["function_data"].get("execution_id")
                 return None
            @@ -371,9 +371,9 @@

            Functions

            Expand source code
            def extract_function_inputs(payload: Dict[str, Any]) -> Optional[Dict[str, Any]]:
            -    if payload.get("event") is not None:
            +    if isinstance(payload.get("event"), dict):
                     return payload["event"].get("inputs")
            -    if payload.get("function_data") is not None:
            +    if isinstance(payload.get("function_data"), dict):
                     return payload["function_data"].get("inputs")
                 return None
            @@ -408,13 +408,13 @@

            Functions

            Expand source code
            def extract_team_id(payload: Dict[str, Any]) -> Optional[str]:
            -    app_installed_team_id = payload.get("view", {}).get("app_installed_team_id")
            -    if app_installed_team_id is not None:
            +    view = payload.get("view")
            +    if isinstance(view, dict) and view.get("app_installed_team_id") is not None:
                     # view_submission payloads can have `view.app_installed_team_id` when a modal view that was opened
                     # in a different workspace via some operations inside a Slack Connect channel.
                     # Note that the same for enterprise_id does not exist. When you need to know the enterprise_id as well,
                     # you have to run some query toward your InstallationStore to know the org where the team_id belongs to.
            -        return app_installed_team_id
            +        return view["app_installed_team_id"]
                 if payload.get("team") is not None:
                     # With org-wide installations, payload.team in interactivity payloads can be None
                     # You need to extract either payload.user.team_id or payload.view.team_id as below
            @@ -429,12 +429,12 @@ 

            Functions

            return extract_team_id(payload["authorizations"][0]) if "team_id" in payload: return payload.get("team_id") - if payload.get("event") is not None: + if isinstance(payload.get("event"), dict): return extract_team_id(payload["event"]) - if payload.get("user") is not None: - return payload["user"]["team_id"] - if payload.get("view") is not None: - return payload.get("view", {})["team_id"] + if isinstance(payload.get("user"), dict): + return payload["user"].get("team_id") + if isinstance(payload.get("view"), dict): + return payload["view"].get("team_id") return None
            @@ -448,30 +448,17 @@

            Functions

            Expand source code
            def extract_thread_ts(payload: Dict[str, Any]) -> Optional[str]:
            -    # This utility initially supports only the use cases for AI assistants, but it may be fine to add more patterns.
            -    # That said, note that thread_ts is always required for assistant threads, but it's not for channels.
            -    # Thus, blindly setting this thread_ts to say utility can break existing apps' behaviors.
            -    if is_assistant_event(payload):
            -        event = payload["event"]
            -        if (
            -            event.get("assistant_thread") is not None
            -            and event["assistant_thread"].get("channel_id") is not None
            -            and event["assistant_thread"].get("thread_ts") is not None
            -        ):
            -            # assistant_thread_started, assistant_thread_context_changed
            -            # "assistant_thread" property can exist for message event without channel_id and thread_ts
            -            # Thus, the above if check verifies these properties exist
            -            return event["assistant_thread"]["thread_ts"]
            -        elif event.get("channel") is not None:
            -            if event.get("thread_ts") is not None:
            -                # message in an assistant thread
            -                return event["thread_ts"]
            -            elif event.get("message", {}).get("thread_ts") is not None:
            -                # message_changed
            -                return event["message"]["thread_ts"]
            -            elif event.get("previous_message", {}).get("thread_ts") is not None:
            -                # message_deleted
            -                return event["previous_message"]["thread_ts"]
            +    thread_ts = payload.get("thread_ts")
            +    if thread_ts is not None:
            +        return thread_ts
            +    if isinstance(payload.get("event"), dict):
            +        return extract_thread_ts(payload["event"])
            +    if isinstance(payload.get("assistant_thread"), dict):
            +        return extract_thread_ts(payload["assistant_thread"])
            +    if isinstance(payload.get("message"), dict):
            +        return extract_thread_ts(payload["message"])
            +    if isinstance(payload.get("previous_message"), dict):
            +        return extract_thread_ts(payload["previous_message"])
                 return None
            @@ -493,12 +480,12 @@

            Functions

            return user.get("id") if "user_id" in payload: return payload.get("user_id") - if payload.get("event") is not None: + if isinstance(payload.get("event"), dict): return extract_user_id(payload["event"]) - if payload.get("message") is not None: + if isinstance(payload.get("message"), dict): # message_changed: body["event"]["message"] return extract_user_id(payload["message"]) - if payload.get("previous_message") is not None: + if isinstance(payload.get("previous_message"), dict): # message_deleted: body["event"]["previous_message"] return extract_user_id(payload["previous_message"]) return None
            @@ -601,7 +588,7 @@

            Functions

            diff --git a/docs/static/api-docs/slack_bolt/request/payload_utils.html b/docs/reference/request/payload_utils.html similarity index 90% rename from docs/static/api-docs/slack_bolt/request/payload_utils.html rename to docs/reference/request/payload_utils.html index 21490bdc8..b583c3a51 100644 --- a/docs/static/api-docs/slack_bolt/request/payload_utils.html +++ b/docs/reference/request/payload_utils.html @@ -3,7 +3,7 @@ - + slack_bolt.request.payload_utils API documentation @@ -63,6 +63,39 @@

            Functions

            +
            +def is_any_im_message_event(body: Dict[str, Any]) ‑> bool +
            +
            +
            + +Expand source code + +
            def is_any_im_message_event(body: Dict[str, Any]) -> bool:
            +    if is_message_event(body):
            +        # Any message event with no subtype or any subtype (message_changed, message_deleted, etc.)
            +        return body["event"].get("channel_type") == "im"
            +    return False
            +
            +
            +
            +
            +def is_app_home_opened_event(body: Dict[str, Any], tab: str | None = None) ‑> bool +
            +
            +
            + +Expand source code + +
            def is_app_home_opened_event(body: Dict[str, Any], tab: Optional[str] = None) -> bool:
            +    if is_event(body) and body["event"]["type"] == "app_home_opened":
            +        if tab is not None:
            +            return body["event"].get("tab") == tab
            +        return True
            +    return False
            +
            +
            +
            def is_assistant_event(body: Dict[str, Any]) ‑> bool
            @@ -159,10 +192,9 @@

            Functions

            Expand source code
            def is_bot_message_event_in_assistant_thread(body: Dict[str, Any]) -> bool:
            -    if is_event(body):
            +    if is_any_im_message_event(body):
                     return (
            -            is_message_event_in_assistant_thread(body)
            -            and body["event"].get("subtype") is None
            +            body["event"].get("subtype") is None
                         and body["event"].get("thread_ts") is not None
                         and body["event"].get("bot_id") is not None
                     )
            @@ -248,17 +280,32 @@ 

            Functions

            -
            -def is_message_event_in_assistant_thread(body: Dict[str, Any]) ‑> bool +
            +def is_im_message_event(body: Dict[str, Any]) ‑> bool +
            +
            +
            + +Expand source code + +
            def is_im_message_event(body: Dict[str, Any]) -> bool:
            +    if is_any_im_message_event(body):
            +        return body["event"].get("subtype") in (None, "file_share")
            +    return False
            +
            +
            +
            +
            +def is_message_event(body: Dict[str, Any]) ‑> bool
            Expand source code -
            def is_message_event_in_assistant_thread(body: Dict[str, Any]) -> bool:
            +
            def is_message_event(body: Dict[str, Any]) -> bool:
                 if is_event(body):
            -        return body["event"]["type"] == "message" and body["event"].get("channel_type") == "im"
            +        return body["event"]["type"] == "message"
                 return False
            @@ -299,14 +346,10 @@

            Functions

            def is_other_message_sub_event_in_assistant_thread(body: Dict[str, Any]) -> bool:
                 # message_changed, message_deleted etc.
            -    if is_event(body):
            -        return (
            -            is_message_event_in_assistant_thread(body)
            -            and not is_user_message_event_in_assistant_thread(body)
            -            and (
            -                _is_other_message_sub_event(body["event"].get("message"))
            -                or _is_other_message_sub_event(body["event"].get("previous_message"))
            -            )
            +    if is_any_im_message_event(body):
            +        return not is_user_message_event_in_assistant_thread(body) and (
            +            _is_other_message_sub_event(body["event"].get("message"))
            +            or _is_other_message_sub_event(body["event"].get("previous_message"))
                     )
                 return False
            @@ -347,13 +390,8 @@

            Functions

            Expand source code
            def is_user_message_event_in_assistant_thread(body: Dict[str, Any]) -> bool:
            -    if is_event(body):
            -        return (
            -            is_message_event_in_assistant_thread(body)
            -            and body["event"].get("subtype") in (None, "file_share")
            -            and body["event"].get("thread_ts") is not None
            -            and body["event"].get("bot_id") is None
            -        )
            +    if is_im_message_event(body):
            +        return body["event"].get("thread_ts") is not None and body["event"].get("bot_id") is None
                 return False
            @@ -491,7 +529,7 @@

            Functions

            Expand source code
            def to_message(body: Dict[str, Any]) -> Optional[Dict[str, Any]]:
            -    if is_event(body) and body["event"]["type"] == "message":
            +    if is_message_event(body):
                     return to_event(body)
                 return None
            @@ -582,6 +620,8 @@

            Functions

          • Functions

  • Sub-modules

    @@ -227,7 +227,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/response/response.html b/docs/reference/response/response.html similarity index 99% rename from docs/static/api-docs/slack_bolt/response/response.html rename to docs/reference/response/response.html index 65151cf64..5044254e8 100644 --- a/docs/static/api-docs/slack_bolt/response/response.html +++ b/docs/reference/response/response.html @@ -3,7 +3,7 @@ - + slack_bolt.response.response API documentation @@ -211,7 +211,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/util/async_utils.html b/docs/reference/util/async_utils.html similarity index 98% rename from docs/static/api-docs/slack_bolt/util/async_utils.html rename to docs/reference/util/async_utils.html index f7ce77489..f74d8f0ac 100644 --- a/docs/static/api-docs/slack_bolt/util/async_utils.html +++ b/docs/reference/util/async_utils.html @@ -3,7 +3,7 @@ - + slack_bolt.util.async_utils API documentation @@ -85,7 +85,7 @@

    Functions

    diff --git a/docs/static/api-docs/slack_bolt/util/index.html b/docs/reference/util/index.html similarity index 98% rename from docs/static/api-docs/slack_bolt/util/index.html rename to docs/reference/util/index.html index 54f1dbd8d..6eadaacb9 100644 --- a/docs/static/api-docs/slack_bolt/util/index.html +++ b/docs/reference/util/index.html @@ -3,7 +3,7 @@ - + slack_bolt.util API documentation @@ -78,7 +78,7 @@

    Sub-modules

    diff --git a/docs/static/api-docs/slack_bolt/util/utils.html b/docs/reference/util/utils.html similarity index 94% rename from docs/static/api-docs/slack_bolt/util/utils.html rename to docs/reference/util/utils.html index d22c1f581..85d336513 100644 --- a/docs/static/api-docs/slack_bolt/util/utils.html +++ b/docs/reference/util/utils.html @@ -3,7 +3,7 @@ - + slack_bolt.util.utils API documentation @@ -83,19 +83,7 @@

    Functions

    Expand source code
    def create_copy(original: Any) -> Any:
    -    if sys.version_info.major == 3 and sys.version_info.minor <= 6:
    -        # NOTE: Unfortunately, copy.deepcopy doesn't work in Python 3.6.5.
    -        # --------------------
    -        # >     rv = reductor(4)
    -        # E     TypeError: can't pickle _thread.RLock objects
    -        # ../../.pyenv/versions/3.6.10/lib/python3.6/copy.py:169: TypeError
    -        # --------------------
    -        # As a workaround, this operation uses shallow copies in Python 3.6.
    -        # If your code modifies the shared data in threads / async functions, race conditions may arise.
    -        # Please consider upgrading Python major version to 3.7+ if you encounter some issues due to this.
    -        return copy.copy(original)
    -    else:
    -        return copy.deepcopy(original)
    + return copy.deepcopy(original)
    @@ -268,7 +256,7 @@

    Returns

    diff --git a/docs/static/api-docs/slack_bolt/version.html b/docs/reference/version.html similarity index 98% rename from docs/static/api-docs/slack_bolt/version.html rename to docs/reference/version.html index 404f38d79..c4a0f9b83 100644 --- a/docs/static/api-docs/slack_bolt/version.html +++ b/docs/reference/version.html @@ -3,7 +3,7 @@ - + slack_bolt.version API documentation @@ -61,7 +61,7 @@

    Module slack_bolt.version

    diff --git a/docs/static/api-docs/slack_bolt/workflows/index.html b/docs/reference/workflows/index.html similarity index 96% rename from docs/static/api-docs/slack_bolt/workflows/index.html rename to docs/reference/workflows/index.html index 5c2e3f91b..0dfe7457f 100644 --- a/docs/static/api-docs/slack_bolt/workflows/index.html +++ b/docs/reference/workflows/index.html @@ -3,7 +3,7 @@ - + slack_bolt.workflows API documentation @@ -43,7 +43,7 @@

    Module slack_bolt.workflows

  • slack_bolt.workflows.step.utilities
  • slack_bolt.workflows.step.async_step (if you use asyncio-based AsyncApp)
  • -

    Refer to https://api.slack.com/workflows/steps for details.

    +

    Refer to https://docs.slack.dev/legacy/legacy-steps-from-apps/ for details.

    Sub-modules

    @@ -80,7 +80,7 @@

    Sub-modules

    diff --git a/docs/static/api-docs/slack_bolt/workflows/step/async_step.html b/docs/reference/workflows/step/async_step.html similarity index 95% rename from docs/static/api-docs/slack_bolt/workflows/step/async_step.html rename to docs/reference/workflows/step/async_step.html index d0b91fb53..18fdd3ab9 100644 --- a/docs/static/api-docs/slack_bolt/workflows/step/async_step.html +++ b/docs/reference/workflows/step/async_step.html @@ -3,7 +3,7 @@ - + slack_bolt.workflows.step.async_step API documentation @@ -78,7 +78,7 @@

    Classes

    """ Deprecated: Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://api.slack.com/automation/functions/custom-bolt + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ Args: callback_id: The callback_id for this step from app @@ -124,7 +124,7 @@

    Classes

    """ Deprecated: Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://api.slack.com/automation/functions/custom-bolt + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ """ return AsyncWorkflowStepBuilder(callback_id, base_logger=base_logger) @@ -200,7 +200,7 @@

    Classes

    Deprecated

    Steps from apps for legacy workflows are now deprecated. -Use new custom steps: https://api.slack.com/automation/functions/custom-bolt

    +Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/

    Args

    callback_id
    @@ -252,7 +252,7 @@

    Static methods

    Deprecated

    Steps from apps for legacy workflows are now deprecated. -Use new custom steps: https://api.slack.com/automation/functions/custom-bolt

    +Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/

    @@ -267,7 +267,7 @@

    Static methods

    class AsyncWorkflowStepBuilder:
         """Steps from apps
    -    Refer to https://api.slack.com/workflows/steps for details.
    +    Refer to https://docs.slack.dev/legacy/legacy-steps-from-apps/ for details.
         """
     
         callback_id: Union[str, Pattern]
    @@ -285,7 +285,7 @@ 

    Static methods

    """ Deprecated: Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://api.slack.com/automation/functions/custom-bolt + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ This builder is supposed to be used as decorator. @@ -327,7 +327,7 @@

    Static methods

    """ Deprecated: Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://api.slack.com/automation/functions/custom-bolt + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ Registers a new edit listener with details. @@ -380,7 +380,7 @@

    Static methods

    """ Deprecated: Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://api.slack.com/automation/functions/custom-bolt + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ Registers a new save listener with details. @@ -433,7 +433,7 @@

    Static methods

    """ Deprecated: Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://api.slack.com/automation/functions/custom-bolt + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ Registers a new execute listener with details. @@ -480,7 +480,7 @@

    Static methods

    """ Deprecated: Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://api.slack.com/automation/functions/custom-bolt + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ Constructs a WorkflowStep object. This method may raise an exception if the builder doesn't have enough configurations to build the object. @@ -555,10 +555,10 @@

    Static methods

    return _middleware

    Steps from apps -Refer to https://api.slack.com/workflows/steps for details.

    +Refer to https://docs.slack.dev/legacy/legacy-steps-from-apps/ for details.

    Deprecated

    Steps from apps for legacy workflows are now deprecated. -Use new custom steps: https://api.slack.com/automation/functions/custom-bolt

    +Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/

    This builder is supposed to be used as decorator.

    my_step = AsyncWorkflowStep.builder("my_step")
     @my_step.edit
    @@ -659,7 +659,7 @@ 

    Methods

    """ Deprecated: Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://api.slack.com/automation/functions/custom-bolt + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ Constructs a WorkflowStep object. This method may raise an exception if the builder doesn't have enough configurations to build the object. @@ -685,7 +685,7 @@

    Methods

    Deprecated

    Steps from apps for legacy workflows are now deprecated. -Use new custom steps: https://api.slack.com/automation/functions/custom-bolt

    +Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/

    Constructs a WorkflowStep object. This method may raise an exception if the builder doesn't have enough configurations to build the object.

    Returns

    @@ -709,7 +709,7 @@

    Returns

    """ Deprecated: Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://api.slack.com/automation/functions/custom-bolt + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ Registers a new edit listener with details. @@ -754,7 +754,7 @@

    Returns

    Deprecated

    Steps from apps for legacy workflows are now deprecated. -Use new custom steps: https://api.slack.com/automation/functions/custom-bolt

    +Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/

    Registers a new edit listener with details.

    You can use this method as decorator as well.

    @my_step.edit
    @@ -799,7 +799,7 @@ 

    Args

    """ Deprecated: Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://api.slack.com/automation/functions/custom-bolt + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ Registers a new execute listener with details. @@ -844,7 +844,7 @@

    Args

    Deprecated

    Steps from apps for legacy workflows are now deprecated. -Use new custom steps: https://api.slack.com/automation/functions/custom-bolt

    +Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/

    Registers a new execute listener with details.

    You can use this method as decorator as well.

    @my_step.execute
    @@ -889,7 +889,7 @@ 

    Args

    """ Deprecated: Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://api.slack.com/automation/functions/custom-bolt + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ Registers a new save listener with details. @@ -934,7 +934,7 @@

    Args

    Deprecated

    Steps from apps for legacy workflows are now deprecated. -Use new custom steps: https://api.slack.com/automation/functions/custom-bolt

    +Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/

    Registers a new save listener with details.

    You can use this method as decorator as well.

    @my_step.save
    @@ -1007,7 +1007,7 @@ 

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/workflows/step/async_step_middleware.html b/docs/reference/workflows/step/async_step_middleware.html similarity index 98% rename from docs/static/api-docs/slack_bolt/workflows/step/async_step_middleware.html rename to docs/reference/workflows/step/async_step_middleware.html index fff1cda5c..a174b9c11 100644 --- a/docs/static/api-docs/slack_bolt/workflows/step/async_step_middleware.html +++ b/docs/reference/workflows/step/async_step_middleware.html @@ -3,7 +3,7 @@ - + slack_bolt.workflows.step.async_step_middleware API documentation @@ -140,7 +140,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/workflows/step/index.html b/docs/reference/workflows/step/index.html similarity index 97% rename from docs/static/api-docs/slack_bolt/workflows/step/index.html rename to docs/reference/workflows/step/index.html index bc15d5a1f..50b52906b 100644 --- a/docs/static/api-docs/slack_bolt/workflows/step/index.html +++ b/docs/reference/workflows/step/index.html @@ -3,7 +3,7 @@ - + slack_bolt.workflows.step API documentation @@ -174,7 +174,7 @@

    Classes

    ) app.step(ws) - Refer to https://api.slack.com/workflows/steps for details. + Refer to https://docs.slack.dev/legacy/legacy-steps-from-apps/ for details. """ def __init__(self, *, callback_id: str, client: WebClient, body: dict): @@ -219,7 +219,7 @@

    Classes

    ) app.step(ws)

    -

    Refer to https://api.slack.com/workflows/steps for details.

    +

    Refer to https://docs.slack.dev/legacy/legacy-steps-from-apps/ for details.

    class Fail @@ -411,7 +411,7 @@

    Classes

    """ Deprecated: Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://api.slack.com/automation/functions/custom-bolt + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ Args: callback_id: The callback_id for this step from app @@ -453,7 +453,7 @@

    Classes

    """ Deprecated: Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://api.slack.com/automation/functions/custom-bolt + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ """ return WorkflowStepBuilder( callback_id, @@ -546,7 +546,7 @@

    Classes

    Deprecated

    Steps from apps for legacy workflows are now deprecated. -Use new custom steps: https://api.slack.com/automation/functions/custom-bolt

    +Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/

    Args

    callback_id
    @@ -598,7 +598,7 @@

    Static methods

    Deprecated

    Steps from apps for legacy workflows are now deprecated. -Use new custom steps: https://api.slack.com/automation/functions/custom-bolt

    +Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/

    @@ -732,7 +732,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/workflows/step/internals.html b/docs/reference/workflows/step/internals.html similarity index 98% rename from docs/static/api-docs/slack_bolt/workflows/step/internals.html rename to docs/reference/workflows/step/internals.html index f2067677f..c5fda1012 100644 --- a/docs/static/api-docs/slack_bolt/workflows/step/internals.html +++ b/docs/reference/workflows/step/internals.html @@ -3,7 +3,7 @@ - + slack_bolt.workflows.step.internals API documentation @@ -60,7 +60,7 @@

    Module slack_bolt.workflows.step.internals

    diff --git a/docs/static/api-docs/slack_bolt/workflows/step/step.html b/docs/reference/workflows/step/step.html similarity index 95% rename from docs/static/api-docs/slack_bolt/workflows/step/step.html rename to docs/reference/workflows/step/step.html index c1500beaf..0309acd88 100644 --- a/docs/static/api-docs/slack_bolt/workflows/step/step.html +++ b/docs/reference/workflows/step/step.html @@ -3,7 +3,7 @@ - + slack_bolt.workflows.step.step API documentation @@ -78,7 +78,7 @@

    Classes

    """ Deprecated: Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://api.slack.com/automation/functions/custom-bolt + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ Args: callback_id: The callback_id for this step from app @@ -120,7 +120,7 @@

    Classes

    """ Deprecated: Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://api.slack.com/automation/functions/custom-bolt + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ """ return WorkflowStepBuilder( callback_id, @@ -213,7 +213,7 @@

    Classes

    Deprecated

    Steps from apps for legacy workflows are now deprecated. -Use new custom steps: https://api.slack.com/automation/functions/custom-bolt

    +Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/

    Args

    callback_id
    @@ -265,7 +265,7 @@

    Static methods

    Deprecated

    Steps from apps for legacy workflows are now deprecated. -Use new custom steps: https://api.slack.com/automation/functions/custom-bolt

    +Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/

    @@ -280,7 +280,7 @@

    Static methods

    class WorkflowStepBuilder:
         """Steps from apps
    -    Refer to https://api.slack.com/workflows/steps for details.
    +    Refer to https://docs.slack.dev/legacy/legacy-steps-from-apps/ for details.
         """
     
         callback_id: Union[str, Pattern]
    @@ -298,7 +298,7 @@ 

    Static methods

    """ Deprecated: Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://api.slack.com/automation/functions/custom-bolt + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ This builder is supposed to be used as decorator. @@ -340,7 +340,7 @@

    Static methods

    """ Deprecated: Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://api.slack.com/automation/functions/custom-bolt + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ Registers a new edit listener with details. @@ -394,7 +394,7 @@

    Static methods

    """ Deprecated: Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://api.slack.com/automation/functions/custom-bolt + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ Registers a new save listener with details. @@ -447,7 +447,7 @@

    Static methods

    """ Deprecated: Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://api.slack.com/automation/functions/custom-bolt + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ Registers a new execute listener with details. @@ -494,7 +494,7 @@

    Static methods

    """ Deprecated: Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://api.slack.com/automation/functions/custom-bolt + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ Constructs a WorkflowStep object. This method may raise an exception if the builder doesn't have enough configurations to build the object. @@ -584,10 +584,10 @@

    Static methods

    return _middleware

    Steps from apps -Refer to https://api.slack.com/workflows/steps for details.

    +Refer to https://docs.slack.dev/legacy/legacy-steps-from-apps/ for details.

    Deprecated

    Steps from apps for legacy workflows are now deprecated. -Use new custom steps: https://api.slack.com/automation/functions/custom-bolt

    +Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/

    This builder is supposed to be used as decorator.

    my_step = WorkflowStep.builder("my_step")
     @my_step.edit
    @@ -703,7 +703,7 @@ 

    Methods

    """ Deprecated: Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://api.slack.com/automation/functions/custom-bolt + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ Constructs a WorkflowStep object. This method may raise an exception if the builder doesn't have enough configurations to build the object. @@ -729,7 +729,7 @@

    Methods

    Deprecated

    Steps from apps for legacy workflows are now deprecated. -Use new custom steps: https://api.slack.com/automation/functions/custom-bolt

    +Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/

    Constructs a WorkflowStep object. This method may raise an exception if the builder doesn't have enough configurations to build the object.

    Returns

    @@ -753,7 +753,7 @@

    Returns

    """ Deprecated: Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://api.slack.com/automation/functions/custom-bolt + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ Registers a new edit listener with details. @@ -799,7 +799,7 @@

    Returns

    Deprecated

    Steps from apps for legacy workflows are now deprecated. -Use new custom steps: https://api.slack.com/automation/functions/custom-bolt

    +Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/

    Registers a new edit listener with details.

    You can use this method as decorator as well.

    @my_step.edit
    @@ -844,7 +844,7 @@ 

    Args

    """ Deprecated: Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://api.slack.com/automation/functions/custom-bolt + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ Registers a new execute listener with details. @@ -889,7 +889,7 @@

    Args

    Deprecated

    Steps from apps for legacy workflows are now deprecated. -Use new custom steps: https://api.slack.com/automation/functions/custom-bolt

    +Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/

    Registers a new execute listener with details.

    You can use this method as decorator as well.

    @my_step.execute
    @@ -934,7 +934,7 @@ 

    Args

    """ Deprecated: Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://api.slack.com/automation/functions/custom-bolt + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ Registers a new save listener with details. @@ -979,7 +979,7 @@

    Args

    Deprecated

    Steps from apps for legacy workflows are now deprecated. -Use new custom steps: https://api.slack.com/automation/functions/custom-bolt

    +Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/

    Registers a new save listener with details.

    You can use this method as decorator as well.

    @my_step.save
    @@ -1052,7 +1052,7 @@ 

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/workflows/step/step_middleware.html b/docs/reference/workflows/step/step_middleware.html similarity index 98% rename from docs/static/api-docs/slack_bolt/workflows/step/step_middleware.html rename to docs/reference/workflows/step/step_middleware.html index 58a1c6194..2ac62dd93 100644 --- a/docs/static/api-docs/slack_bolt/workflows/step/step_middleware.html +++ b/docs/reference/workflows/step/step_middleware.html @@ -3,7 +3,7 @@ - + slack_bolt.workflows.step.step_middleware API documentation @@ -143,7 +143,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/workflows/step/utilities/async_complete.html b/docs/reference/workflows/step/utilities/async_complete.html similarity index 98% rename from docs/static/api-docs/slack_bolt/workflows/step/utilities/async_complete.html rename to docs/reference/workflows/step/utilities/async_complete.html index 7dc7744a7..8e95cc267 100644 --- a/docs/static/api-docs/slack_bolt/workflows/step/utilities/async_complete.html +++ b/docs/reference/workflows/step/utilities/async_complete.html @@ -3,7 +3,7 @@ - + slack_bolt.workflows.step.utilities.async_complete API documentation @@ -134,7 +134,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/workflows/step/utilities/async_configure.html b/docs/reference/workflows/step/utilities/async_configure.html similarity index 96% rename from docs/static/api-docs/slack_bolt/workflows/step/utilities/async_configure.html rename to docs/reference/workflows/step/utilities/async_configure.html index 1e0395e92..10f236c47 100644 --- a/docs/static/api-docs/slack_bolt/workflows/step/utilities/async_configure.html +++ b/docs/reference/workflows/step/utilities/async_configure.html @@ -3,7 +3,7 @@ - + slack_bolt.workflows.step.utilities.async_configure API documentation @@ -83,7 +83,7 @@

    Classes

    ) app.step(ws) - Refer to https://api.slack.com/workflows/steps for details. + Refer to https://docs.slack.dev/legacy/legacy-steps-from-apps/ for details. """ def __init__(self, *, callback_id: str, client: AsyncWebClient, body: dict): @@ -131,7 +131,7 @@

    Classes

    ) app.step(ws)

    -

    Refer to https://api.slack.com/workflows/steps for details.

    +

    Refer to https://docs.slack.dev/legacy/legacy-steps-from-apps/ for details.

    @@ -157,7 +157,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/workflows/step/utilities/async_fail.html b/docs/reference/workflows/step/utilities/async_fail.html similarity index 98% rename from docs/static/api-docs/slack_bolt/workflows/step/utilities/async_fail.html rename to docs/reference/workflows/step/utilities/async_fail.html index 683174686..b27c36251 100644 --- a/docs/static/api-docs/slack_bolt/workflows/step/utilities/async_fail.html +++ b/docs/reference/workflows/step/utilities/async_fail.html @@ -3,7 +3,7 @@ - + slack_bolt.workflows.step.utilities.async_fail API documentation @@ -132,7 +132,7 @@

    diff --git a/docs/static/api-docs/slack_bolt/workflows/step/utilities/async_update.html b/docs/reference/workflows/step/utilities/async_update.html similarity index 98% rename from docs/static/api-docs/slack_bolt/workflows/step/utilities/async_update.html rename to docs/reference/workflows/step/utilities/async_update.html index 8b8282fae..bfb210fc3 100644 --- a/docs/static/api-docs/slack_bolt/workflows/step/utilities/async_update.html +++ b/docs/reference/workflows/step/utilities/async_update.html @@ -3,7 +3,7 @@ - + slack_bolt.workflows.step.utilities.async_update API documentation @@ -166,7 +166,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/workflows/step/utilities/complete.html b/docs/reference/workflows/step/utilities/complete.html similarity index 98% rename from docs/static/api-docs/slack_bolt/workflows/step/utilities/complete.html rename to docs/reference/workflows/step/utilities/complete.html index 685a3bd63..f1cf11f56 100644 --- a/docs/static/api-docs/slack_bolt/workflows/step/utilities/complete.html +++ b/docs/reference/workflows/step/utilities/complete.html @@ -3,7 +3,7 @@ - + slack_bolt.workflows.step.utilities.complete API documentation @@ -134,7 +134,7 @@

    diff --git a/docs/static/api-docs/slack_bolt/workflows/step/utilities/configure.html b/docs/reference/workflows/step/utilities/configure.html similarity index 96% rename from docs/static/api-docs/slack_bolt/workflows/step/utilities/configure.html rename to docs/reference/workflows/step/utilities/configure.html index 8199f9ea4..258bce312 100644 --- a/docs/static/api-docs/slack_bolt/workflows/step/utilities/configure.html +++ b/docs/reference/workflows/step/utilities/configure.html @@ -3,7 +3,7 @@ - + slack_bolt.workflows.step.utilities.configure API documentation @@ -83,7 +83,7 @@

    Classes

    ) app.step(ws) - Refer to https://api.slack.com/workflows/steps for details. + Refer to https://docs.slack.dev/legacy/legacy-steps-from-apps/ for details. """ def __init__(self, *, callback_id: str, client: WebClient, body: dict): @@ -128,7 +128,7 @@

    Classes

    ) app.step(ws)
    -

    Refer to https://api.slack.com/workflows/steps for details.

    +

    Refer to https://docs.slack.dev/legacy/legacy-steps-from-apps/ for details.

    @@ -154,7 +154,7 @@

    diff --git a/docs/static/api-docs/slack_bolt/workflows/step/utilities/fail.html b/docs/reference/workflows/step/utilities/fail.html similarity index 98% rename from docs/static/api-docs/slack_bolt/workflows/step/utilities/fail.html rename to docs/reference/workflows/step/utilities/fail.html index 071bbbfed..00d0be83d 100644 --- a/docs/static/api-docs/slack_bolt/workflows/step/utilities/fail.html +++ b/docs/reference/workflows/step/utilities/fail.html @@ -3,7 +3,7 @@ - + slack_bolt.workflows.step.utilities.fail API documentation @@ -132,7 +132,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/static/api-docs/slack_bolt/workflows/step/utilities/index.html b/docs/reference/workflows/step/utilities/index.html similarity index 99% rename from docs/static/api-docs/slack_bolt/workflows/step/utilities/index.html rename to docs/reference/workflows/step/utilities/index.html index 1594bacb6..54261ea96 100644 --- a/docs/static/api-docs/slack_bolt/workflows/step/utilities/index.html +++ b/docs/reference/workflows/step/utilities/index.html @@ -3,7 +3,7 @@ - + slack_bolt.workflows.step.utilities API documentation @@ -127,7 +127,7 @@

    Sub-modules

    diff --git a/docs/static/api-docs/slack_bolt/workflows/step/utilities/update.html b/docs/reference/workflows/step/utilities/update.html similarity index 98% rename from docs/static/api-docs/slack_bolt/workflows/step/utilities/update.html rename to docs/reference/workflows/step/utilities/update.html index f24c07836..9899448f9 100644 --- a/docs/static/api-docs/slack_bolt/workflows/step/utilities/update.html +++ b/docs/reference/workflows/step/utilities/update.html @@ -3,7 +3,7 @@ - + slack_bolt.workflows.step.utilities.update API documentation @@ -166,7 +166,7 @@

    -

    Generated by pdoc 0.11.5.

    +

    Generated by pdoc 0.11.6.

    diff --git a/docs/sidebars.js b/docs/sidebars.js deleted file mode 100644 index 1a294e33b..000000000 --- a/docs/sidebars.js +++ /dev/null @@ -1,106 +0,0 @@ -/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ -const sidebars = { - sidebarBoltPy: [ - { - type: "doc", - id: "index", - label: "Bolt for Python", - className: "sidebar-title", - }, - { type: "html", value: "
    " }, - { - type: "category", - label: "Guides", - collapsed: false, - items: [ - "getting-started", - { - type: "category", - label: "Basic concepts", - items: [ - "basic/assistant", - "basic/message-listening", - "basic/message-sending", - "basic/event-listening", - "basic/web-api", - "basic/action-listening", - "basic/action-respond", - "basic/acknowledge", - "basic/shortcuts", - "basic/commands", - "basic/opening-modals", - "basic/updating-pushing-views", - "basic/view_submissions", - "basic/app-home", - "basic/options", - "basic/custom-steps", - "basic/authenticating-oauth", - "basic/socket-mode", - ], - }, - { - type: "category", - label: "Advanced concepts", - items: [ - "advanced/adapters", - "advanced/custom-adapters", - "advanced/async", - "advanced/errors", - "advanced/logging", - "advanced/authorization", - "advanced/token-rotation", - "advanced/listener-middleware", - "advanced/global-middleware", - "advanced/context", - "advanced/lazy-listeners", - ], - }, - { - type: "category", - label: "Steps from apps (Deprecated)", - items: [ - "steps/steps", - "steps/executing-steps", - "steps/creating-steps", - "steps/adding-editing-steps", - "steps/saving-steps", - ], - }, - ], - }, - { type: "html", value: "
    " }, - { - type: "category", - label: "Tutorials", - items: [ - "tutorial/getting-started-http", - "tutorial/ai-chatbot", - "tutorial/custom-steps-for-jira", - ], - }, - { type: "html", value: "
    " }, - { - type: "link", - label: "Reference", - href: "https://tools.slack.dev/bolt-python/api-docs/slack_bolt/", - }, - { type: "html", value: "
    " }, - { - type: "link", - label: "Release notes", - href: "https://github.com/slackapi/bolt-python/releases", - }, - { - type: "link", - label: "Code on GitHub", - href: "https://github.com/SlackAPI/bolt-python", - }, - { - type: "link", - label: "Contributors Guide", - href: "https://github.com/SlackAPI/bolt-python/blob/main/.github/contributing.md", - }, - ], -}; - -export default sidebars; diff --git a/docs/src/css/custom.css b/docs/src/css/custom.css deleted file mode 100644 index ceb43f769..000000000 --- a/docs/src/css/custom.css +++ /dev/null @@ -1,388 +0,0 @@ -:root { - --ifm-font-size-base: 15px; - - /* set hex colors here pls */ - --dim: #eef2f6; - - --aubergine: #4a154b; - --aubergine-background: #552555; - --aubergine-dark: #2c0134; - - --aubergine-active: #7c3085; - --aubergine-active-70: #7c308570; - --aubergine-active-50: #7c308550; - --aubergine-active-30: #7c308530; - - --horchata: #f4ede4; - - --slack-red: #e3066a; - --slack-red-70: #e3066a70; - --slack-red-50: #e3066a50; - --slack-red-30: #e3066a30; - --slack-red-20: #e3066a20; - - --slack-yellow: #fcc003; - --slack-yellow-70: #fcc00370; - --slack-yellow-50: #fcc00350; - --slack-yellow-30: #fcc00330; - --slack-yellow-20: #fcc00320; - - --slack-green: #41b658; - --slack-green-70: #41b65870; - --slack-green-50: #41b65850; - --slack-green-30: #41b65830; - --slack-green-20: #41b65820; - - --slack-blue: #1ab9ff; - --slack-blue-70: #1ab9ff70; - --slack-blue-50: #1ab9ff70; - --slack-blue-30: #1ab9ff30; - --slack-blue-20: #1ab9ff20; - - /* used for dark-mode links */ - --slack-cloud-blue: #1ab9ff; - /* slack marketing color used for light-mode links */ - --slack-dark-blue: #1264a3; - - /* used for functions */ - --unofficial-orange: #e36606; - --unofficial-orange-70: #e3660670; - --unofficial-orange-50: #e3660650; - --unofficial-orange-30: #e3660630; - - /* turns opacity into flat colors for bubbles on top of things */ - --slack-yellow-70-flat: #fcc00370; - - --slack-yellow-30-on-white: #feecb3; - --slack-green-30-on-white: #c6e9cc; - --slack-red-30-on-white: #f6b4d2; - --slack-blue-30-on-white: #baeaff; - --unofficial-orange-30-on-white: #f6d1b4; - --aubergine-active-30-on-white: #d7c0da; -} - -/* resets striped tables that hurt me eyes */ -table tr:nth-child(even) { - background-color: inherit; -} - -h1 { - font-size: 2.5rem; -} - -/* changing the links to blue for accessibility */ -p a, -.markdown a { - color: var(--slack-cloud-blue); - text-decoration: none; -} - -p a, -.markdown a:hover { - text-decoration: underline; -} - -a:hover { - color: var(--slack-cloud-blue); -} - -/* adjusting for light and dark modes */ -[data-theme="light"] { - --docusaurus-highlighted-code-line-bg: var(--dim); - --ifm-color-primary: var(--aubergine-active); - --ifm-footer-background-color: white; - --slack-cloud-blue: var(--slack-dark-blue); - --reference-section-color: var(--horchata); -} - -[data-theme="dark"] { - --docusaurus-highlighted-code-line-bg: rgb(0 0 0 / 30%); - --ifm-color-primary: var(--slack-cloud-blue); - --ifm-navbar-background-color: var(--aubergine) !important; - --ifm-footer-background-color: var(--aubergine) !important; - --ifm-footer-color: white; -} - -.alert--warning { - --ifm-alert-background-color: var(--slack-yellow-30); - --ifm-alert-border-color: var(--slack-yellow); - --ifm-alert-background-color-highlight: var(--slack-yellow-30); -} - -.alert--info { - --ifm-alert-background-color: var(--slack-blue-30); - --ifm-alert-border-color: var(--slack-blue); - /* --ifm-alert-background-color-highlight: var(--slack-blue-30); */ -} - -.alert--danger { - --ifm-alert-background-color: var(--slack-red-30); - --ifm-alert-border-color: var(--slack-red); -} - -.alert--success { - --ifm-alert-background-color: var(--slack-green-30); - --ifm-alert-border-color: var(--slack-green); -} - -.footer { - font-size: 80%; - padding-bottom: 0rem; -} - -.footer__items a { - color: inherit; -} - -.footer .container { - margin: 0; -} - -/* adjusting size */ -.table-of-contents__link { - font-size: 1rem; -} - -/* bolding ToC for contrast */ -.table-of-contents__link--active { - font-weight: bold; -} - -/* removing ToC line */ -.table-of-contents__left-border { - border-left: none !important; -} - -/* increasing name of site in sidebar */ -.sidebar-title { - /* padding-bottom: 0.5rem; - font-size: 1.25em; */ - font-weight: bold; -} - -.theme-doc-sidebar-item-link hr { - margin: 1rem; -} - -.sidebar-sdk-title { - /* margin: 0.5rem 0; */ - padding: 0.5rem; - /* border-radius: 4px; */ - border-bottom: .5px solid grey; -} - -.theme-doc-sidebar-item-category-level-1 .menu__link { - font-weight: bold; -} - -.theme-doc-sidebar-item-category-level-1 .menu__list-item .menu__link { - font-weight: normal; -} - -/* removing sidebar line and adding space to match ToC */ -.theme-doc-sidebar-container { - border-right: none !important; - margin-right: 2rem; -} - -/* announcement bar up top */ -div[class^="announcementBar_"] { - font-size: 20px; - height: 50px; - background: var(--horchata); -} - -/* navbar github link */ -.navbar-github-link { - width: 32px; - height: 32px; - padding: 6px; - margin-right: 6px; - margin-left: 6px; - border-radius: 50%; - transition: background var(--ifm-transition-fast); -} - -.navbar-github-link:hover { - background: var(--ifm-color-emphasis-200); -} - -.navbar-github-link::before { - content: ""; - height: 100%; - display: block; - background: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E") - no-repeat; -} - -html[data-theme="dark"] .navbar-github-link::before { - background: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='white' d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E") - no-repeat; -} - -/* Delineate tab blocks */ -.tabs-container { - border: 1px solid var(--ifm-color-primary); - border-radius: 5px; - padding: 0.5em; -} - -/* Docs code bubbles */ -[data-theme="light"] { - --contrast-color: black; - --code-link-background: var(--slack-blue-30); - --code-link-text: rgb(21, 50, 59); - - --method-link-background: var(--slack-green-30-on-white); - --method-link-text: rgb(0, 41, 0); - - --scope-link-background: var(--slack-yellow-30-on-white); - --scope-link-text: rgb(63, 46, 0); - - --event-link-background: var(--slack-red-30-on-white); - --event-link-text: rgb(63, 0, 24); - - --function-link-background: var(--unofficial-orange-30-on-white); - --function-link-text: rgb(75, 35, 0); - - --command-link-background: var(--aubergine-active-30-on-white); - --command-link-text: rgb(75, 0, 75); -} - -[data-theme="dark"] { - --contrast-color: white; - --code-link-text: white; - --method-link-text: white; - --scope-link-text: white; - --event-link-text: white; - --function-link-text: white; - --command-link-text: white; - - --code-link-background: var(--slack-blue-70); - --method-link-background: var(--slack-green-70); - --scope-link-background: var(--slack-yellow-70); - --event-link-background: var(--slack-red-70); - --command-link-background: var(--aubergine-active); - --function-link-background: var(--unofficial-orange-70); -} - -a code { - background-color: var(--code-link-background); - color: var(--code-link-text); -} - -a[href^="https://api.slack.com/methods"] > code { - background-color: var(--method-link-background); - color: var(--method-link-text); -} - -a[href^="https://api.slack.com/scopes"] > code { - background-color: var(--scope-link-background); - color: var(--scope-link-text); -} - -a[href^="https://api.slack.com/events"] > code { - background-color: var(--event-link-background); - color: var(--event-link-text); -} - -a[href^="/deno-slack-sdk/reference/slack-functions/"] > code { - background-color: var(--function-link-background); - color: var(--function-link-text); -} - -a[href^="/deno-slack-sdk/reference/connector-functions/"] > code { - background-color: var(--function-link-background); - color: var(--function-link-text); -} - -a[href^="/slack-cli/reference/commands"] > code { - background-color: var(--command-link-background); - color: var(--command-link-text); -} - -.facts-section { - background-color: var(--slack-green-20) !important; -} - -.auth-section { - background-color: var(--slack-red-20) !important; -} - -.inputs-section { - background-color: var(--slack-blue-20) !important; -} - -.functions-section { - border-radius: 6px; - padding: 1rem; - margin-bottom: 2rem; -} - -.scope-list { - display: flex; - flex-wrap: wrap; - gap: 1rem; -} - -.scope-item { - display: inline-block; -} - -.functions-section .type { - text-align: right; -} - -.param-required-section { - padding-top: 1rem; - margin-bottom: 1rem; -} - -.param-container { - border-top: 0.5px solid; - padding-top: 1rem; - padding-bottom: 1rem; -} - -.param-container:last-child { - padding-bottom: 0; -} - -.param-top-row { - display: flex; - align-items: center; - margin-bottom: 1rem; -} - -/* left-align param name */ -.param-top-row .name { - flex: 1; -} - -/* right-align Required and Type */ -.param-top-row .required, -.param-top-row .type { - margin-left: auto; - text-align: right; -} - -/* add space between Required and Type */ -.param-top-row .required { - margin-left: 10px; -} - -.info-row { - display: flex; - align-items: center; - margin-bottom: 1rem; -} - -.info-key { - flex: 0 0 10rem; - align-items: center; -} - -/* hides next and previous */ -.pagination-nav__link { - display: none; -} diff --git a/docs/src/theme/NotFound/Content/index.js b/docs/src/theme/NotFound/Content/index.js deleted file mode 100644 index c122bc039..000000000 --- a/docs/src/theme/NotFound/Content/index.js +++ /dev/null @@ -1,36 +0,0 @@ -import React from 'react'; -import clsx from 'clsx'; -import Translate from '@docusaurus/Translate'; -import Heading from '@theme/Heading'; -export default function NotFoundContent({className}) { - return ( -
    -
    -
    - - - Oh no! There's nothing here. - - -

    - - If we've led you astray, please let us know. We'll do our best to get things in order. - - -

    -

    - - For now, we suggest heading back to the beginning to get your bearings. May your next journey have clear skies to guide you true. - -

    -
    -
    -
    - ); -} diff --git a/docs/src/theme/NotFound/index.js b/docs/src/theme/NotFound/index.js deleted file mode 100644 index 3b551f9e4..000000000 --- a/docs/src/theme/NotFound/index.js +++ /dev/null @@ -1,19 +0,0 @@ -import React from 'react'; -import {translate} from '@docusaurus/Translate'; -import {PageMetadata} from '@docusaurus/theme-common'; -import Layout from '@theme/Layout'; -import NotFoundContent from '@theme/NotFound/Content'; -export default function Index() { - const title = translate({ - id: 'theme.NotFound.title', - message: 'Page Not Found', - }); - return ( - <> - - - - - - ); -} diff --git a/docs/static/img/bolt-logo.svg b/docs/static/img/bolt-logo.svg deleted file mode 100644 index 5077600d5..000000000 --- a/docs/static/img/bolt-logo.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/docs/static/img/bolt-py-logo.svg b/docs/static/img/bolt-py-logo.svg deleted file mode 100644 index 1dcab5261..000000000 --- a/docs/static/img/bolt-py-logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/static/img/boltpy/bolt-favicon.png b/docs/static/img/boltpy/bolt-favicon.png deleted file mode 100644 index bfe5456c1..000000000 Binary files a/docs/static/img/boltpy/bolt-favicon.png and /dev/null differ diff --git a/docs/static/img/boltpy/ngrok.gif b/docs/static/img/boltpy/ngrok.gif deleted file mode 100644 index c7c94d51a..000000000 Binary files a/docs/static/img/boltpy/ngrok.gif and /dev/null differ diff --git a/docs/static/img/boltpy/request-url-config.png b/docs/static/img/boltpy/request-url-config.png deleted file mode 100644 index 5315298a8..000000000 Binary files a/docs/static/img/boltpy/request-url-config.png and /dev/null differ diff --git a/docs/static/img/boltpy/signing-secret.png b/docs/static/img/boltpy/signing-secret.png deleted file mode 100644 index d32afa03e..000000000 Binary files a/docs/static/img/boltpy/signing-secret.png and /dev/null differ diff --git a/docs/static/img/favicon.ico b/docs/static/img/favicon.ico deleted file mode 100644 index e6e9a4aa7..000000000 Binary files a/docs/static/img/favicon.ico and /dev/null differ diff --git a/docs/static/img/slack-logo.svg b/docs/static/img/slack-logo.svg deleted file mode 100644 index fb55f7245..000000000 --- a/docs/static/img/slack-logo.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/examples/aws_lambda/README.md b/examples/aws_lambda/README.md index 454f080a7..2fc34b7cf 100644 --- a/examples/aws_lambda/README.md +++ b/examples/aws_lambda/README.md @@ -32,16 +32,16 @@ Instructions on how to set up and deploy each example are provided below. `lazy_aws_lambda_config.yaml` - Optionally enter a description for the role, such as "Bolt Python basic role" -3. Ensure you have created an app on api.slack.com/apps as per the [Getting - Started Guide](https://slack.dev/bolt-python/tutorial/getting-started). +3. Ensure you have created an app on api.slack.com/apps as per the + [Building an App](https://docs.slack.dev/tools/bolt-python/creating-an-app) guide. Ensure you have installed it to a workspace. 4. Ensure you have exported your Slack Bot Token and Slack Signing Secret for your apps as the environment variables `SLACK_BOT_TOKEN` and - `SLACK_SIGNING_SECRET`, respectively, as per the [Getting - Started Guide](https://slack.dev/bolt-python/tutorial/getting-started). + `SLACK_SIGNING_SECRET`, respectively, as per the + [Building an App](https://docs.slack.dev/tools/bolt-python/creating-an-app) guide. 5. You may want to create a dedicated virtual environment for this example app, as - per the "Setting up your project" section of the [Getting - Started Guide](https://slack.dev/bolt-python/tutorial/getting-started). + per the "Setting up your project" section of the + [Building an App](https://docs.slack.dev/tools/bolt-python/creating-an-app) guide. 6. Let's deploy the Lambda! Run `./deploy_lazy.sh`. By default it deploys to the us-east-1 region in AWS - you can change this at the top of `lazy_aws_lambda_config.yaml` if you wish. 7. Load up AWS Lambda inside the AWS Console - make sure you are in the correct @@ -150,7 +150,7 @@ Let’s create a user role that will use the custom policy we created as well as 3. "Create Role" ### Create Slack App and Load your Lambda to AWS -Ensure you have created an app on [api.slack.com/apps](https://api.slack.com/apps) as per the [Getting Started Guide](https://slack.dev/bolt-python/tutorial/getting-started). You do not need to ensure you have installed it to a workspace, as the OAuth flow will provide your app the ability to be installed by anyone. +Ensure you have created an app on [api.slack.com/apps](https://api.slack.com/apps) as per the [Building an App](https://docs.slack.dev/tools/bolt-python/creating-an-app) guide. You do not need to ensure you have installed it to a workspace, as the OAuth flow will provide your app the ability to be installed by anyone. 1. Remember those S3 buckets we made? You will need the names of these buckets again in the next step. 2. You need many environment variables exported! Specifically the following from api.slack.com/apps diff --git a/examples/django/README.md b/examples/django/README.md index c50681c5b..cb29a822b 100644 --- a/examples/django/README.md +++ b/examples/django/README.md @@ -4,7 +4,7 @@ This example demonstrates how you can use Bolt for Python in your Django applica ### `simple_app` - Single-workspace App Example -If you want to run a simple app like the one you've tried in the [Getting Started Guide](https://slack.dev/bolt-python/tutorial/getting-started), this is the right one for you. By default, this Django project runs this application. If you want to switch to OAuth flow supported one, modify `myslackapp/urls.py`. +If you want to run a simple app like the one you've tried in the [Building an App](https://docs.slack.dev/tools/bolt-python/creating-an-app) guide, this is the right one for you. By default, this Django project runs this application. If you want to switch to OAuth flow supported one, modify `myslackapp/urls.py`. To run this app, all you need to do are: @@ -31,7 +31,7 @@ python manage.py migrate python manage.py runserver 0.0.0.0:3000 ``` -As you did at [Getting Started Guide](https://slack.dev/bolt-python/tutorial/getting-started), configure ngrok or something similar to serve a public endpoint. Lastly, +As you did at [Building an App](https://docs.slack.dev/tools/bolt-python/creating-an-app) guide, configure ngrok or something similar to serve a public endpoint. Lastly, * Go back to the Slack app configuration page * Go to "Event Subscriptions" @@ -54,7 +54,7 @@ To run this app, all you need to do are: * Create a new Slack app configuration at https://api.slack.com/apps?new_app=1 * Go to "OAuth & Permissions" * Add `app_mentions:read`, `chat:write` in Scopes > Bot Token Scopes -* Follow the instructions [here](https://slack.dev/bolt-python/concepts#authenticating-oauth) for configuring OAuth flow supported Slack apps +* Follow the instructions [here](https://docs.slack.dev/tools/bolt-python/concepts/authenticating-oauth) for configuring OAuth flow supported Slack apps You can start your Django application this way: @@ -73,7 +73,7 @@ python manage.py migrate python manage.py runserver 0.0.0.0:3000 ``` -As you did at [Getting Started Guide](https://slack.dev/bolt-python/tutorial/getting-started), configure ngrok or something similar to serve a public endpoint. Lastly, +As you did at [Building an App](https://docs.slack.dev/tools/bolt-python/creating-an-app) guide, configure ngrok or something similar to serve a public endpoint. Lastly, * Go back to the Slack app configuration page * Go to "Event Subscriptions" diff --git a/examples/getting_started/README.md b/examples/getting_started/README.md index 63875a4dd..48f9d4095 100644 --- a/examples/getting_started/README.md +++ b/examples/getting_started/README.md @@ -1,5 +1,5 @@ # Getting Started with ⚡️ Bolt for Python -> Slack app example from 📚 [Getting started with Bolt for Python][1] +> Slack app example from 📚 [Building an App with Bolt for Python][1] ## Overview @@ -42,6 +42,6 @@ ngrok http 3000 python3 app.py ``` -[1]: https://slack.dev/bolt-python/tutorial/getting-started -[2]: https://slack.dev/bolt-python/ -[3]: https://slack.dev/bolt-python/tutorial/getting-started#setting-up-events +[1]: https://docs.slack.dev/tools/bolt-python/creating-an-app +[2]: https://docs.slack.dev/tools/bolt-python/ +[3]: https://docs.slack.dev/tools/bolt-python/creating-an-app#setting-up-events diff --git a/examples/getting_started/app.py b/examples/getting_started/app.py index 22cdf5f31..aa5223d51 100644 --- a/examples/getting_started/app.py +++ b/examples/getting_started/app.py @@ -10,7 +10,7 @@ # Listens to incoming messages that contain "hello" # To learn available listener method arguments, -# visit https://slack.dev/bolt-python/api-docs/slack_bolt/kwargs_injection/args.html +# visit https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html @app.message("hello") def message_hello(message, say): # say() sends a message to the channel where the event was triggered diff --git a/examples/message_events.py b/examples/message_events.py index 7658be276..3fd424060 100644 --- a/examples/message_events.py +++ b/examples/message_events.py @@ -32,7 +32,7 @@ def extract_subtype(body: dict, context: BoltContext, next: Callable): next() -# https://api.slack.com/events/message +# https://docs.slack.dev/reference/events/message/ # Newly posted messages only # or @app.event("message") @app.event({"type": "message", "subtype": None}) @@ -55,8 +55,8 @@ def detect_deletion(say: Say, body: dict): say(f"You've deleted a message: {text}") -# https://api.slack.com/events/message/file_share -# https://api.slack.com/events/message/bot_message +# https://docs.slack.dev/reference/events/message/file_share +# https://docs.slack.dev/reference/events/message/bot_message @app.event( event={"type": "message", "subtype": re.compile("(me_message)|(file_share)")}, middleware=[extract_subtype], diff --git a/examples/readme_app.py b/examples/readme_app.py index 963938658..fe81a0904 100644 --- a/examples/readme_app.py +++ b/examples/readme_app.py @@ -16,13 +16,13 @@ def log_request(logger, body, next): return next() -# Events API: https://api.slack.com/events-api +# Events API: https://docs.slack.dev/apis/events-api/ @app.event("app_mention") def event_test(say): say("What's up?") -# Interactivity: https://api.slack.com/interactivity +# Interactivity: https://docs.slack.dev/interactivity/ @app.shortcut("callback-id-here") # @app.command("/hello-bolt-python") def open_modal(ack, client, logger, body): diff --git a/examples/readme_async_app.py b/examples/readme_async_app.py index c43d3af32..f11d308a0 100644 --- a/examples/readme_async_app.py +++ b/examples/readme_async_app.py @@ -28,13 +28,13 @@ async def log_request(logger, body, next): return await next() -# Events API: https://api.slack.com/events-api +# Events API: https://docs.slack.dev/apis/events-api/ @app.event("app_mention") async def event_test(say): await say("What's up?") -# Interactivity: https://api.slack.com/interactivity +# Interactivity: https://docs.slack.dev/interactivity/ @app.shortcut("callback-id-here") # @app.command("/hello-bolt-python") async def open_modal(ack, client, logger, body): diff --git a/examples/workflow_steps/async_steps_from_apps.py b/examples/workflow_steps/async_steps_from_apps.py index 11566de6c..ed108cf5e 100644 --- a/examples/workflow_steps/async_steps_from_apps.py +++ b/examples/workflow_steps/async_steps_from_apps.py @@ -11,7 +11,7 @@ ################################################################################ # Steps from apps for legacy workflows are now deprecated. # -# Use new custom steps: https://api.slack.com/automation/functions/custom-bolt # +# Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ # ################################################################################ logging.basicConfig(level=logging.DEBUG) diff --git a/examples/workflow_steps/async_steps_from_apps_decorator.py b/examples/workflow_steps/async_steps_from_apps_decorator.py index 423048a47..e04884723 100644 --- a/examples/workflow_steps/async_steps_from_apps_decorator.py +++ b/examples/workflow_steps/async_steps_from_apps_decorator.py @@ -13,7 +13,7 @@ ################################################################################ # Steps from apps for legacy workflows are now deprecated. # -# Use new custom steps: https://api.slack.com/automation/functions/custom-bolt # +# Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ # ################################################################################ logging.basicConfig(level=logging.DEBUG) diff --git a/examples/workflow_steps/async_steps_from_apps_primitive.py b/examples/workflow_steps/async_steps_from_apps_primitive.py index 2e636e600..06a2956db 100644 --- a/examples/workflow_steps/async_steps_from_apps_primitive.py +++ b/examples/workflow_steps/async_steps_from_apps_primitive.py @@ -5,7 +5,7 @@ ################################################################################ # Steps from apps for legacy workflows are now deprecated. # -# Use new custom steps: https://api.slack.com/automation/functions/custom-bolt # +# Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ # ################################################################################ logging.basicConfig(level=logging.DEBUG) diff --git a/examples/workflow_steps/steps_from_apps.py b/examples/workflow_steps/steps_from_apps.py index efbb2ce65..b5f591700 100644 --- a/examples/workflow_steps/steps_from_apps.py +++ b/examples/workflow_steps/steps_from_apps.py @@ -8,7 +8,7 @@ ################################################################################ # Steps from apps for legacy workflows are now deprecated. # -# Use new custom steps: https://api.slack.com/automation/functions/custom-bolt # +# Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ # ################################################################################ logging.basicConfig(level=logging.DEBUG) diff --git a/examples/workflow_steps/steps_from_apps_decorator.py b/examples/workflow_steps/steps_from_apps_decorator.py index 64ddfcc20..1558e825a 100644 --- a/examples/workflow_steps/steps_from_apps_decorator.py +++ b/examples/workflow_steps/steps_from_apps_decorator.py @@ -9,7 +9,7 @@ ################################################################################ # Steps from apps for legacy workflows are now deprecated. # -# Use new custom steps: https://api.slack.com/automation/functions/custom-bolt # +# Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ # ################################################################################ logging.basicConfig(level=logging.DEBUG) diff --git a/examples/workflow_steps/steps_from_apps_primitive.py b/examples/workflow_steps/steps_from_apps_primitive.py index 6aa5a98bb..dd4231ba6 100644 --- a/examples/workflow_steps/steps_from_apps_primitive.py +++ b/examples/workflow_steps/steps_from_apps_primitive.py @@ -7,7 +7,7 @@ ################################################################################ # Steps from apps for legacy workflows are now deprecated. # -# Use new custom steps: https://api.slack.com/automation/functions/custom-bolt # +# Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ # ################################################################################ logging.basicConfig(level=logging.DEBUG) diff --git a/pyproject.toml b/pyproject.toml index ef5440fed..ac197c4f6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,14 +1,13 @@ [build-system] -requires = ["setuptools", "pytest-runner==6.0.1", "wheel"] +requires = ["setuptools", "wheel"] build-backend = "setuptools.build_meta" [project] name = "slack_bolt" -dynamic = ["version", "readme", "dependencies", "authors"] +dynamic = ["version", "readme", "authors"] description = "The Bolt Framework for Python" license = { text = "MIT" } classifiers = [ - "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", @@ -16,15 +15,18 @@ classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Programming Language :: Python :: Implementation :: CPython", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", ] -requires-python = ">=3.6" +requires-python = ">=3.7" +dependencies = ["slack_sdk>=3.38.0,<4"] [project.urls] -Documentation = "https://slack.dev/bolt-python" +Documentation = "https://docs.slack.dev/tools/bolt-python/" +Source = "https://github.com/slackapi/bolt-python" [tool.setuptools.packages.find] include = ["slack_bolt*"] @@ -32,7 +34,6 @@ include = ["slack_bolt*"] [tool.setuptools.dynamic] version = { attr = "slack_bolt.version.__version__" } readme = { file = ["README.md"], content-type = "text/markdown" } -dependencies = { file = ["requirements.txt"] } [tool.distutils.bdist_wheel] universal = true @@ -46,10 +47,7 @@ log_file = "logs/pytest.log" log_file_level = "DEBUG" log_format = "%(asctime)s %(levelname)s %(message)s" log_date_format = "%Y-%m-%d %H:%M:%S" -filterwarnings = [ - "ignore:\"@coroutine\" decorator is deprecated since Python 3.8, use \"async def\" instead:DeprecationWarning", - "ignore:The loop argument is deprecated since Python 3.8, and scheduled for removal in Python 3.10.:DeprecationWarning", -] +filterwarnings = [] asyncio_mode = "auto" [tool.mypy] diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index f0b0cf987..000000000 --- a/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -slack_sdk>=3.33.5,<4 diff --git a/requirements/adapter.txt b/requirements/adapter.txt deleted file mode 100644 index 7a09fae46..000000000 --- a/requirements/adapter.txt +++ /dev/null @@ -1,23 +0,0 @@ -# pip install -r requirements/adapter.txt -# NOTE: any of async ones requires pip install -r requirements/async.txt too -# used only under slack_bolt/adapter -boto3<=2 -bottle>=0.12,<1 -chalice<=1.27.3; python_version=="3.6" -chalice>=1.28,<2; python_version>"3.6" -CherryPy>=18,<19 -Django>=3,<6 -falcon>=2,<4; python_version<"3.11" -falcon>=3.1.1,<5; python_version>="3.11" -fastapi>=0.70.0,<1 -Flask>=1,<4 -Werkzeug>=2,<4 -pyramid>=1,<3 -sanic>=20,<21; python_version=="3.6" -sanic>=21,<24; python_version>"3.6" and python_version<="3.8" -sanic>=21,<25; python_version>"3.8" -starlette>=0.14,<1 -tornado>=6,<7 -uvicorn<1 # The oldest version can vary among Python runtime versions -gunicorn>=20,<24 -websocket_client>=1.2.3,<2 # Socket Mode 3rd party implementation diff --git a/requirements/adapter_dev.txt b/requirements/adapter_dev.txt new file mode 100644 index 000000000..34a96a911 --- /dev/null +++ b/requirements/adapter_dev.txt @@ -0,0 +1,79 @@ +# pip install -r requirements/adapter_dev.txt +# Note: any of the async ones requires pip install -r requirements/async_dev.txt too +# Note: used only under slack_bolt/adapter + +# boto3 +boto3<=2 + +# bottle +bottle>=0.13.4,<1 + +# chalice +chalice>=1.28,<1.31; python_version < "3.9" +chalice>=1.32.0,<1.33; python_version >= "3.9" and python_version < "3.10" +chalice>=1.33.0,<2; python_version >= "3.10" + +# cheroot +cheroot<12 + +# CherryPy +CherryPy>=18.10.0,<19 + +# Django +# Note: Django 5.x requires Python >=3.10 and 4.x requires >=3.8, so 3.8/3.9 stay on the 4.2 LTS line and 3.7 stays on the 3.2 line. +Django>=3.2,<4; python_version < "3.8" +Django>=4.2.30,<5; python_version >= "3.8" and python_version < "3.10" +Django>=5.2.15,<6; python_version >= "3.10" + +# falcon +# Note: falcon 4.2.0 requires Python >=3.9; 3.7/3.8 stay on the older pinned release. +falcon>=2,<4; python_version < "3.9" +falcon>=4.2.0,<5; python_version >= "3.9" + +# fastapi +fastapi>=0.70.0,<1; python_version < "3.9" +fastapi>=0.128.8,<0.129.0; python_version >= "3.9" and python_version < "3.10" +fastapi>=0.141.1,<1; python_version >= "3.10" + +# Flask +# Note: Flask 3.1.3 requires Python >=3.9; 3.7/3.8 stay on the older pinned release. +Flask>=1,<4; python_version < "3.9" +Flask>=3.1.3,<4; python_version >= "3.9" + +# Werkzeug +# Note: Werkzeug 3.1.8 requires Python >=3.9; 3.7/3.8 stay on the older pinned release. +Werkzeug>=2,<3; python_version < "3.9" +Werkzeug>=3.1.8,<4; python_version >= "3.9" + +# pyramid +# Note: pyramid 2.1 requires Python >=3.10; 3.7/3.8/3.9 stay on the last 2.0.x release. +pyramid>=1,<2.1; python_version < "3.10" +pyramid>=2.1,<3; python_version >= "3.10" + +# setuptools +# Note: Pyramid depends on pkg_resources (deprecated in setuptools 67.5.0, removed in 82+). See: https://github.com/Pylons/pyramid/issues/3731 +setuptools<82 + +# sanic +# Note: Sanic pulls in tracerite via a wildcard version, so tracerite is co-pinned here. +# Note: tracerite > 1.1.2 is incompatible with Python <= 3.8 and ships no requires_python, so an explicit ceiling is required. +# Note: sanic 25.12 requires Python >=3.10; 3.9 stays on the older 25.x line. +tracerite<1.1.2; python_version < "3.9" +sanic>=21,<24; python_version < "3.9" +sanic>=25.3.0,<25.12.0; python_version >= "3.9" and python_version < "3.10" +sanic>=25.12.1,<26; python_version >= "3.10" + +# starlette +# Note: starlette 0.49.3 requires Python >=3.9; 3.7/3.8 stay on the older pinned release. +starlette>=0.19.1,<0.45; python_version < "3.9" +starlette>=0.49.3,<1; python_version >= "3.9" + +# tornado +# Note: tornado 6.5.6 requires Python >=3.9; 3.7/3.8 stay on the older pinned release. +tornado>=6.2,<7; python_version < "3.9" +tornado>=6.5.6,<7; python_version >= "3.9" + +# websocket_client +# Note: Socket Mode 3rd party implementation +websocket_client>=1.2.3,<1.9; python_version < "3.9" +websocket_client>=1.9.0,<2; python_version >= "3.9" diff --git a/requirements/adapter_testing.txt b/requirements/adapter_testing.txt deleted file mode 100644 index 3d829ee15..000000000 --- a/requirements/adapter_testing.txt +++ /dev/null @@ -1,5 +0,0 @@ -# pip install -r requirements/adapter_testing.txt -moto>=3,<6 # For AWS tests -docker>=5,<8 # Used by moto -boddle>=0.2,<0.3 # For Bottle app tests -sanic-testing>=0.7; python_version>"3.6" diff --git a/requirements/async.txt b/requirements/async.txt deleted file mode 100644 index cd9c368f6..000000000 --- a/requirements/async.txt +++ /dev/null @@ -1,3 +0,0 @@ -# pip install -r requirements/async.txt -aiohttp>=3,<4 -websockets<15 diff --git a/requirements/async_dev.txt b/requirements/async_dev.txt new file mode 100644 index 000000000..32dff305b --- /dev/null +++ b/requirements/async_dev.txt @@ -0,0 +1,9 @@ +# pip install -r requirements/async_dev.txt + +# aiohttp +# Note: aiohttp 3.13.5 requires Python >=3.9; 3.7/3.8 stay on the older pinned release. +aiohttp>=3,<4; python_version < "3.9" +aiohttp>=3.13.5,<4; python_version >= "3.9" + +# websockets +websockets<17 diff --git a/requirements/dev_tools.txt b/requirements/dev_tools.txt new file mode 100644 index 000000000..59e4cdd21 --- /dev/null +++ b/requirements/dev_tools.txt @@ -0,0 +1,10 @@ +# pip install -r requirements/dev_tools.txt + +# mypy +mypy==2.3.0 + +# flake8 +flake8==7.3.0 + +# black +black==26.3.1 diff --git a/requirements/test.txt b/requirements/test.txt new file mode 100644 index 000000000..af895692b --- /dev/null +++ b/requirements/test.txt @@ -0,0 +1,8 @@ +# pip install -r requirements/test.txt + +# pytest +pytest<9.2 + +# pytest-cov +# Note: only needed to evaluate coverage on the latest supported python version +pytest-cov>=7.1.0,<8; python_version >= "3.14" diff --git a/requirements/test_adapter.txt b/requirements/test_adapter.txt new file mode 100644 index 000000000..702856aac --- /dev/null +++ b/requirements/test_adapter.txt @@ -0,0 +1,16 @@ +# pip install -r requirements/test_adapter.txt + +# moto +# Note: for AWS tests +# Note: moto drops old Pythons across the 5.x line — 5.0.0 requires >=3.8, 5.1.0 >=3.9, 5.2.0 >=3.10; older interpreters stay on the last compatible release. +moto>=3,<5; python_version < "3.8" +moto>=3,<5.1; python_version >= "3.8" and python_version < "3.9" +moto>=3,<5.2; python_version >= "3.9" and python_version < "3.10" +moto>=5.2.2,<6; python_version >= "3.10" + +# boddle +# Note: for Bottle app tests +boddle>=0.2.9,<0.3 + +# sanic-testing +sanic-testing>=24.6.0 diff --git a/requirements/test_async.txt b/requirements/test_async.txt new file mode 100644 index 000000000..5dc51a201 --- /dev/null +++ b/requirements/test_async.txt @@ -0,0 +1,11 @@ +# pip install -r requirements/test_async.txt +-r test.txt +-r async_dev.txt + +# asgiref +# Note: asgiref 3.11.1 requires Python >=3.9; 3.7/3.8 stay on the older pinned release. +asgiref>=3.7.2,<3.8; python_version < "3.9" +asgiref>=3.11.1,<4; python_version >= "3.9" + +# pytest-asyncio +pytest-asyncio<2 diff --git a/requirements/testing.txt b/requirements/testing.txt deleted file mode 100644 index 7cd7d353a..000000000 --- a/requirements/testing.txt +++ /dev/null @@ -1,4 +0,0 @@ -# pip install -r requirements/testing.txt --r testing_without_asyncio.txt --r async.txt -pytest-asyncio<1; diff --git a/requirements/testing_without_asyncio.txt b/requirements/testing_without_asyncio.txt deleted file mode 100644 index 754889faf..000000000 --- a/requirements/testing_without_asyncio.txt +++ /dev/null @@ -1,3 +0,0 @@ -# pip install -r requirements/testing_without_asyncio.txt -pytest>=6.2.5,<8.4 # https://github.com/tornadoweb/tornado/issues/3375 -pytest-cov>=3,<7 diff --git a/requirements/tools.txt b/requirements/tools.txt deleted file mode 100644 index 91fc76a31..000000000 --- a/requirements/tools.txt +++ /dev/null @@ -1,3 +0,0 @@ -mypy==1.14.1 -flake8==6.0.0 -black==24.8.0 # Until we drop Python 3.6 support, we have to stay with this version diff --git a/scripts/build_pypi_package.sh b/scripts/build_pypi_package.sh index 79c6db9f2..5806262a6 100755 --- a/scripts/build_pypi_package.sh +++ b/scripts/build_pypi_package.sh @@ -5,7 +5,7 @@ cd ${script_dir}/.. rm -rf ./slack_bolt.egg-info pip install -U pip && \ - pip install twine build && \ + pip install -U twine build && \ rm -rf dist/ build/ slack_bolt.egg-info/ && \ python -m build --sdist --wheel && \ twine check dist/* diff --git a/scripts/deploy_to_pypi_org.sh b/scripts/deploy_to_pypi_org.sh deleted file mode 100755 index a3cf431fa..000000000 --- a/scripts/deploy_to_pypi_org.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -script_dir=`dirname $0` -cd ${script_dir}/.. -rm -rf ./slack_bolt.egg-info - -pip install -U pip && \ - pip install twine build && \ - rm -rf dist/ build/ slack_bolt.egg-info/ && \ - python -m build --sdist --wheel && \ - twine check dist/* && \ - twine upload dist/* diff --git a/scripts/deploy_to_test_pypi_org.sh b/scripts/deploy_to_test_pypi.sh similarity index 88% rename from scripts/deploy_to_test_pypi_org.sh rename to scripts/deploy_to_test_pypi.sh index b2cc65a12..a6b9c352d 100644 --- a/scripts/deploy_to_test_pypi_org.sh +++ b/scripts/deploy_to_test_pypi.sh @@ -5,7 +5,7 @@ cd ${script_dir}/.. rm -rf ./slack_bolt.egg-info pip install -U pip && \ - pip install twine build && \ + pip install -U twine build && \ rm -rf dist/ build/ slack_bolt.egg-info/ && \ python -m build --sdist --wheel && \ twine check dist/* && \ diff --git a/scripts/format.sh b/scripts/format.sh new file mode 100755 index 000000000..771cbb413 --- /dev/null +++ b/scripts/format.sh @@ -0,0 +1,13 @@ +#!/bin/bash +# ./scripts/format.sh + +script_dir=`dirname $0` +cd ${script_dir}/.. + +if [[ "$1" != "--no-install" ]]; then + export PIP_REQUIRE_VIRTUALENV=1 + pip install -U pip + pip install -U -r requirements/dev_tools.txt +fi + +black slack_bolt/ tests/ diff --git a/scripts/generate_api_docs.sh b/scripts/generate_api_docs.sh index 68988f428..275aa0fe1 100755 --- a/scripts/generate_api_docs.sh +++ b/scripts/generate_api_docs.sh @@ -1,10 +1,19 @@ #!/bin/bash # Generate API documents from the latest source code -script_dir=`dirname $0` -cd ${script_dir}/.. +set -e +script_dir=$(dirname "$0") +cd "${script_dir}/.." +pip install -U pip +pip install -U -r requirements/adapter_dev.txt +pip install -U -r requirements/async_dev.txt pip install -U pdoc3 -rm -rf docs/static/api-docs -pdoc slack_bolt --html -o docs/static/api-docs -open docs/static/api-docs/slack_bolt/index.html +pip install . +rm -rf docs/reference + +pdoc slack_bolt --html -o docs/reference +cp -R docs/reference/slack_bolt/* docs/reference/ +rm -rf docs/reference/slack_bolt + +open docs/reference/index.html diff --git a/scripts/install.sh b/scripts/install.sh new file mode 100755 index 000000000..64cdf1561 --- /dev/null +++ b/scripts/install.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# Installs all dependencies of the project +# ./scripts/install.sh + +script_dir=`dirname $0` +cd ${script_dir}/.. +rm -rf ./slack_bolt.egg-info + +# Update pip to prevent warnings +pip install -U pip + +# The package causes a conflict with moto +pip uninstall python-lambda + +pip install -U -e . +pip install -U -r requirements/test_async.txt +pip install -U -r requirements/adapter_dev.txt +pip install -U -r requirements/test_adapter.txt +pip install -U -r requirements/dev_tools.txt + +# To avoid errors due to the old versions of click forced by Chalice +pip install -U pip click diff --git a/scripts/install_all_and_run_tests.sh b/scripts/install_all_and_run_tests.sh index 21660dca5..939e71ffd 100755 --- a/scripts/install_all_and_run_tests.sh +++ b/scripts/install_all_and_run_tests.sh @@ -5,42 +5,19 @@ script_dir=`dirname $0` cd ${script_dir}/.. -rm -rf ./slack_bolt.egg-info -# Update pip to prevent warnings -pip install -U pip +test_target="${1:-tests/}" -# The package causes a conflict with moto -pip uninstall python-lambda +# keep in sync with LATEST_SUPPORTED_PY in .github/workflows/ci-build.yml +LATEST_SUPPORTED_PY="3.14" +current_py=$(python --version | sed -E 's/Python ([0-9]+\.[0-9]+).*/\1/') -test_target="$1" -python_version=`python --version | awk '{print $2}'` +./scripts/install.sh -if [ ${python_version:0:3} == "3.6" ] -then - pip install -U -r requirements.txt -else - pip install -e . -fi +./scripts/format.sh --no-install +./scripts/lint.sh --no-install +pytest $test_target -if [[ $test_target != "" ]] -then - pip install -U -r requirements/testing.txt && \ - pip install -U -r requirements/adapter.txt && \ - pip install -U -r requirements/adapter_testing.txt && \ - # To avoid errors due to the old versions of click forced by Chalice - pip install -U pip click && \ - black slack_bolt/ tests/ && \ - pytest $1 -else - pip install -U -r requirements/testing.txt && \ - pip install -U -r requirements/adapter.txt && \ - pip install -U -r requirements/adapter_testing.txt && \ - pip install -r requirements/tools.txt && \ - # To avoid errors due to the old versions of click forced by Chalice - pip install -U pip click && \ - black slack_bolt/ tests/ && \ - flake8 slack_bolt/ && flake8 examples/ - pytest && \ - mypy --config-file pyproject.toml +if [[ "$current_py" == "$LATEST_SUPPORTED_PY" ]]; then + ./scripts/run_mypy.sh --no-install fi diff --git a/scripts/lint.sh b/scripts/lint.sh new file mode 100755 index 000000000..3a3037419 --- /dev/null +++ b/scripts/lint.sh @@ -0,0 +1,12 @@ +#!/bin/bash +# ./scripts/lint.sh + +script_dir=$(dirname $0) +cd ${script_dir}/.. + +if [[ "$1" != "--no-install" ]]; then + pip install -U pip + pip install -U -r requirements/dev_tools.txt +fi + +flake8 slack_bolt/ && flake8 examples/ diff --git a/scripts/run_flake8.sh b/scripts/run_flake8.sh deleted file mode 100755 index 73562da29..000000000 --- a/scripts/run_flake8.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -# ./scripts/run_flake8.sh - -script_dir=$(dirname $0) -cd ${script_dir}/.. && \ - pip install -r requirements/tools.txt && \ - flake8 slack_bolt/ && flake8 examples/ diff --git a/scripts/run_mypy.sh b/scripts/run_mypy.sh index 77a2bacb7..c9234f87a 100755 --- a/scripts/run_mypy.sh +++ b/scripts/run_mypy.sh @@ -2,9 +2,14 @@ # ./scripts/run_mypy.sh script_dir=$(dirname $0) -cd ${script_dir}/.. && \ - pip install . - pip install -r requirements/async.txt && \ - pip install -r requirements/adapter.txt && \ - pip install -r requirements/tools.txt && \ - mypy --config-file pyproject.toml +cd ${script_dir}/.. + +if [[ "$1" != "--no-install" ]]; then + pip install -U pip + pip install -U . + pip install -U -r requirements/async_dev.txt + pip install -U -r requirements/adapter_dev.txt + pip install -U -r requirements/dev_tools.txt +fi + +mypy --config-file pyproject.toml diff --git a/scripts/run_tests.sh b/scripts/run_tests.sh index e4cc99709..d4dc767e3 100755 --- a/scripts/run_tests.sh +++ b/scripts/run_tests.sh @@ -6,13 +6,7 @@ script_dir=`dirname $0` cd ${script_dir}/.. -test_target="$1" -python_version=`python --version | awk '{print $2}'` +test_target="${1:-tests/}" -if [[ $test_target != "" ]] -then - black slack_bolt/ tests/ && \ - pytest -vv $1 -else - black slack_bolt/ tests/ && pytest -fi +./scripts/format.sh --no-install +pytest -vv $test_target diff --git a/slack_bolt/__init__.py b/slack_bolt/__init__.py index 32ab76721..e3664814b 100644 --- a/slack_bolt/__init__.py +++ b/slack_bolt/__init__.py @@ -1,7 +1,7 @@ """ -A Python framework to build Slack apps in a flash with the latest platform features.Read the [getting started guide](https://slack.dev/bolt-python/tutorial/getting-started) and look at our [code examples](https://github.com/slackapi/bolt-python/tree/main/examples) to learn how to build apps using Bolt. +A Python framework to build Slack apps in a flash with the latest platform features.Read the [getting started guide](https://docs.slack.dev/tools/bolt-python/creating-an-app) and look at our [code examples](https://github.com/slackapi/bolt-python/tree/main/examples) to learn how to build apps using Bolt. -* Website: https://slack.dev/bolt-python/ +* Website: https://docs.slack.dev/tools/bolt-python/ * GitHub repository: https://github.com/slackapi/bolt-python * The class representing a Bolt app: `slack_bolt.app.app` """ # noqa: E501 @@ -14,6 +14,7 @@ from .context.fail import Fail from .context.respond import Respond from .context.say import Say +from .context.say_stream import SayStream from .kwargs_injection import Args from .listener import Listener from .listener_matcher import CustomListenerMatcher @@ -41,6 +42,7 @@ "Fail", "Respond", "Say", + "SayStream", "Args", "Listener", "CustomListenerMatcher", diff --git a/slack_bolt/adapter/__init__.py b/slack_bolt/adapter/__init__.py index f339226bc..9ca556e52 100644 --- a/slack_bolt/adapter/__init__.py +++ b/slack_bolt/adapter/__init__.py @@ -1,2 +1 @@ -"""Adapter modules for running Bolt apps along with Web frameworks or Socket Mode. -""" +"""Adapter modules for running Bolt apps along with Web frameworks or Socket Mode.""" diff --git a/slack_bolt/adapter/asgi/base_handler.py b/slack_bolt/adapter/asgi/base_handler.py index 5e68c51f4..adfa060c1 100644 --- a/slack_bolt/adapter/asgi/base_handler.py +++ b/slack_bolt/adapter/asgi/base_handler.py @@ -1,4 +1,4 @@ -from typing import Callable, Dict, Union +from typing import Callable, Union from .http_request import AsgiHttpRequest from .http_response import AsgiHttpResponse @@ -47,15 +47,13 @@ async def _get_http_response(self, method: str, path: str, request: AsgiHttpRequ return AsgiHttpResponse(status=bolt_response.status, headers=bolt_response.headers, body=bolt_response.body) return AsgiHttpResponse(status=404, headers={"content-type": ["text/plain;charset=utf-8"]}, body="Not Found") - async def _handle_lifespan(self, receive: Callable) -> Dict[str, str]: - while True: - lifespan = await receive() - if lifespan["type"] == "lifespan.startup": - """Do something before startup""" - return {"type": "lifespan.startup.complete"} - if lifespan["type"] == "lifespan.shutdown": - """Do something before shutdown""" - return {"type": "lifespan.shutdown.complete"} + async def _handle_lifespan(self, receive: Callable, send: Callable) -> None: + message = await receive() + if message["type"] == "lifespan.startup": + await send({"type": "lifespan.startup.complete"}) + message = await receive() + if message["type"] == "lifespan.shutdown": + await send({"type": "lifespan.shutdown.complete"}) async def __call__(self, scope: scope_type, receive: Callable, send: Callable) -> None: if scope["type"] == "http": @@ -66,6 +64,6 @@ async def __call__(self, scope: scope_type, receive: Callable, send: Callable) - await send(response.get_response_body()) return if scope["type"] == "lifespan": - await send(await self._handle_lifespan(receive)) + await self._handle_lifespan(receive, send) return raise TypeError(f"Unsupported scope type: {scope['type']!r}") diff --git a/slack_bolt/adapter/asgi/http_response.py b/slack_bolt/adapter/asgi/http_response.py index c8178b8f5..58969f137 100644 --- a/slack_bolt/adapter/asgi/http_response.py +++ b/slack_bolt/adapter/asgi/http_response.py @@ -8,11 +8,14 @@ class AsgiHttpResponse: def __init__(self, status: int, headers: Dict[str, Sequence[str]] = {}, body: str = ""): self.status: int = status - self.raw_headers: List[Tuple[bytes, bytes]] = [ - (bytes(key, ENCODING), bytes(value[0], ENCODING)) for key, value in headers.items() - ] - self.raw_headers.append((b"content-length", bytes(str(len(body)), ENCODING))) self.body: bytes = bytes(body, ENCODING) + self.raw_headers: List[Tuple[bytes, bytes]] = [] + for key, values in headers.items(): + if key.lower() == "content-length": + continue + for v in values: + self.raw_headers.append((bytes(key, ENCODING), bytes(v, ENCODING))) + self.raw_headers.append((b"content-length", bytes(str(len(self.body)), ENCODING))) def get_response_start(self) -> Dict[str, Union[str, int, Iterable[Tuple[bytes, bytes]]]]: return { diff --git a/slack_bolt/adapter/falcon/async_resource.py b/slack_bolt/adapter/falcon/async_resource.py index eece0a323..fdb2d975f 100644 --- a/slack_bolt/adapter/falcon/async_resource.py +++ b/slack_bolt/adapter/falcon/async_resource.py @@ -1,6 +1,7 @@ from datetime import datetime from http import HTTPStatus +from falcon import MEDIA_TEXT from falcon import version as falcon_version from falcon.asgi import Request, Response from slack_bolt import BoltResponse @@ -40,9 +41,9 @@ async def on_get(self, req: Request, resp: Response): await self._write_response(bolt_resp, resp) return - resp.status = "404" - # Falcon 4.x w/ mypy fails to correctly infer the str type here - resp.body = "The page is not found..." # type: ignore[assignment] + resp.status = HTTPStatus.NOT_FOUND + resp.content_type = MEDIA_TEXT + resp.text = "The page is not found..." async def on_post(self, req: Request, resp: Response): bolt_req = await self._to_bolt_request(req) diff --git a/slack_bolt/adapter/falcon/resource.py b/slack_bolt/adapter/falcon/resource.py index baf0f9745..5d162ad23 100644 --- a/slack_bolt/adapter/falcon/resource.py +++ b/slack_bolt/adapter/falcon/resource.py @@ -1,6 +1,7 @@ from datetime import datetime from http import HTTPStatus +from falcon import MEDIA_TEXT from falcon import Request, Response, version as falcon_version from slack_bolt import BoltResponse @@ -34,9 +35,9 @@ def on_get(self, req: Request, resp: Response): self._write_response(bolt_resp, resp) return - resp.status = "404" - # Falcon 4.x w/ mypy fails to correctly infer the str type here - resp.body = "The page is not found..." # type: ignore[assignment] + resp.status = HTTPStatus.NOT_FOUND + resp.content_type = MEDIA_TEXT + resp.text = "The page is not found..." def on_post(self, req: Request, resp: Response): bolt_req = self._to_bolt_request(req) @@ -53,7 +54,7 @@ def _to_bolt_request(self, req: Request) -> BoltRequest: def _write_response(self, bolt_resp: BoltResponse, resp: Response): if falcon_version.__version__.startswith("2."): # Falcon 4.x w/ mypy fails to correctly infer the str type here - resp.body = bolt_resp.body # type: ignore[assignment] + resp.body = bolt_resp.body else: resp.text = bolt_resp.body diff --git a/slack_bolt/adapter/socket_mode/async_internals.py b/slack_bolt/adapter/socket_mode/async_internals.py index c2965f766..428ab437c 100644 --- a/slack_bolt/adapter/socket_mode/async_internals.py +++ b/slack_bolt/adapter/socket_mode/async_internals.py @@ -8,13 +8,14 @@ from slack_sdk.socket_mode.request import SocketModeRequest from slack_sdk.socket_mode.response import SocketModeResponse +from slack_bolt.adapter.socket_mode.internals import build_headers from slack_bolt.app.async_app import AsyncApp from slack_bolt.request.async_request import AsyncBoltRequest from slack_bolt.response import BoltResponse async def run_async_bolt_app(app: AsyncApp, req: SocketModeRequest): - bolt_req: AsyncBoltRequest = AsyncBoltRequest(mode="socket_mode", body=req.payload) + bolt_req: AsyncBoltRequest = AsyncBoltRequest(mode="socket_mode", body=req.payload, headers=build_headers(req)) bolt_resp: BoltResponse = await app.async_dispatch(bolt_req) return bolt_resp diff --git a/slack_bolt/adapter/socket_mode/internals.py b/slack_bolt/adapter/socket_mode/internals.py index 8eb751b4d..6289f28f5 100644 --- a/slack_bolt/adapter/socket_mode/internals.py +++ b/slack_bolt/adapter/socket_mode/internals.py @@ -3,6 +3,7 @@ import json import logging from time import time +from typing import Dict, Optional, Sequence, Union from slack_sdk.socket_mode.client import BaseSocketModeClient from slack_sdk.socket_mode.request import SocketModeRequest @@ -13,8 +14,18 @@ from slack_bolt.response import BoltResponse +def build_headers(req: SocketModeRequest) -> Optional[Dict[str, Union[str, Sequence[str]]]]: + # Mirror the HTTP mode retry headers so middleware/listeners can detect Events API retries + headers: Dict[str, Union[str, Sequence[str]]] = {} + if req.retry_attempt is not None: + headers["x-slack-retry-num"] = str(req.retry_attempt) + if req.retry_reason is not None: + headers["x-slack-retry-reason"] = req.retry_reason + return headers or None + + def run_bolt_app(app: App, req: SocketModeRequest): - bolt_req: BoltRequest = BoltRequest(mode="socket_mode", body=req.payload) + bolt_req: BoltRequest = BoltRequest(mode="socket_mode", body=req.payload, headers=build_headers(req)) bolt_resp: BoltResponse = app.dispatch(bolt_req) return bolt_resp diff --git a/slack_bolt/adapter/wsgi/handler.py b/slack_bolt/adapter/wsgi/handler.py index 2861b4425..fef54f73e 100644 --- a/slack_bolt/adapter/wsgi/handler.py +++ b/slack_bolt/adapter/wsgi/handler.py @@ -1,6 +1,10 @@ -from typing import Any, Callable, Dict, Iterable, List, Tuple +from typing import TYPE_CHECKING, Iterable from slack_bolt import App + +if TYPE_CHECKING: + from wsgiref.types import StartResponse, WSGIEnvironment + from slack_bolt.adapter.wsgi.http_request import WsgiHttpRequest from slack_bolt.adapter.wsgi.http_response import WsgiHttpResponse from slack_bolt.request import BoltRequest @@ -69,14 +73,17 @@ def _get_http_response(self, request: WsgiHttpRequest) -> WsgiHttpResponse: def __call__( self, - environ: Dict[str, Any], - start_response: Callable[[str, List[Tuple[str, str]]], None], + environ: "WSGIEnvironment", + start_response: "StartResponse", ) -> Iterable[bytes]: request = WsgiHttpRequest(environ) - if "HTTP" in request.protocol: + if request.protocol.startswith("HTTP"): response: WsgiHttpResponse = self._get_http_response( request=request, ) - start_response(response.status, response.get_headers()) - return response.get_body() - raise TypeError(f"Unsupported SERVER_PROTOCOL: {request.protocol}") + else: + response = WsgiHttpResponse( + status=400, headers={"content-type": ["text/plain;charset=utf-8"]}, body="Bad Request" + ) + start_response(response.status, response.get_headers()) + return response.get_body() diff --git a/slack_bolt/adapter/wsgi/http_request.py b/slack_bolt/adapter/wsgi/http_request.py index 460d8f531..644d0e333 100644 --- a/slack_bolt/adapter/wsgi/http_request.py +++ b/slack_bolt/adapter/wsgi/http_request.py @@ -1,4 +1,7 @@ -from typing import Any, Dict, Sequence, Union +from typing import TYPE_CHECKING, Dict, Sequence, Union + +if TYPE_CHECKING: + from wsgiref.types import WSGIEnvironment from .internals import ENCODING @@ -12,7 +15,7 @@ class WsgiHttpRequest: __slots__ = ("method", "path", "query_string", "protocol", "environ") - def __init__(self, environ: Dict[str, Any]): + def __init__(self, environ: "WSGIEnvironment"): self.method: str = environ.get("REQUEST_METHOD", "GET") self.path: str = environ.get("PATH_INFO", "") self.query_string: str = environ.get("QUERY_STRING", "") @@ -33,5 +36,5 @@ def get_headers(self) -> Dict[str, Union[str, Sequence[str]]]: def get_body(self) -> str: if "wsgi.input" not in self.environ: return "" - content_length = int(self.environ.get("CONTENT_LENGTH", 0)) + content_length = int(self.environ.get("CONTENT_LENGTH") or 0) return self.environ["wsgi.input"].read(content_length).decode(ENCODING) diff --git a/slack_bolt/adapter/wsgi/http_response.py b/slack_bolt/adapter/wsgi/http_response.py index 1ad32e672..32956d276 100644 --- a/slack_bolt/adapter/wsgi/http_response.py +++ b/slack_bolt/adapter/wsgi/http_response.py @@ -1,5 +1,5 @@ from http import HTTPStatus -from typing import Dict, Iterable, List, Sequence, Tuple +from typing import Dict, Iterable, List, Optional, Sequence, Tuple from .internals import ENCODING @@ -13,18 +13,19 @@ class WsgiHttpResponse: __slots__ = ("status", "_headers", "_body") - def __init__(self, status: int, headers: Dict[str, Sequence[str]] = {}, body: str = ""): + def __init__(self, status: int, headers: Optional[Dict[str, Sequence[str]]] = None, body: str = ""): _status = HTTPStatus(status) self.status = f"{_status.value} {_status.phrase}" - self._headers = headers + self._headers = headers or {} self._body = bytes(body, ENCODING) def get_headers(self) -> List[Tuple[str, str]]: headers: List[Tuple[str, str]] = [] - for key, value in self._headers.items(): + for key, values in self._headers.items(): if key.lower() == "content-length": continue - headers.append((key, value[0])) + for v in values: + headers.append((key, v)) headers.append(("content-length", str(len(self._body)))) return headers diff --git a/slack_bolt/app/app.py b/slack_bolt/app/app.py index 3d5532b7b..e20649902 100644 --- a/slack_bolt/app/app.py +++ b/slack_bolt/app/app.py @@ -22,7 +22,6 @@ from slack_bolt.context.assistant.thread_context_store.store import AssistantThreadContextStore -from slack_bolt.context.assistant.assistant_utilities import AssistantUtilities from slack_bolt.error import BoltError, BoltUnhandledRequestError from slack_bolt.lazy_listener.thread_runner import ThreadLazyListenerRunner from slack_bolt.listener.builtins import TokenRevocationListeners @@ -59,6 +58,7 @@ info_default_oauth_settings_loaded, error_installation_store_required_for_builtin_listeners, warning_unhandled_by_global_middleware, + warning_ack_timeout_has_no_effect, ) from slack_bolt.middleware import ( Middleware, @@ -69,6 +69,7 @@ IgnoringSelfEvents, CustomMiddleware, AttachingFunctionToken, + AttachingConversationKwargs, ) from slack_bolt.middleware.assistant import Assistant from slack_bolt.middleware.message_listener_matches import MessageListenerMatches @@ -82,10 +83,6 @@ from slack_bolt.oauth.internals import select_consistent_installation_store from slack_bolt.oauth.oauth_settings import OAuthSettings from slack_bolt.request import BoltRequest -from slack_bolt.request.payload_utils import ( - is_assistant_event, - to_event, -) from slack_bolt.response import BoltResponse from slack_bolt.util.utils import ( create_web_client, @@ -136,6 +133,7 @@ def __init__( listener_executor: Optional[Executor] = None, # for AI Agents & Assistants assistant_thread_context_store: Optional[AssistantThreadContextStore] = None, + attaching_conversation_kwargs_enabled: bool = True, ): """Bolt App that provides functionalities to register middleware/listeners. @@ -158,10 +156,10 @@ def message_hello(message, say): if __name__ == "__main__": app.start(port=int(os.environ.get("PORT", 3000))) - Refer to https://slack.dev/bolt-python/tutorial/getting-started for details. + Refer to https://docs.slack.dev/tools/bolt-python/creating-an-app for details. If you would like to build an OAuth app for enabling the app to run with multiple workspaces, - refer to https://slack.dev/bolt-python/concepts#authenticating-oauth to learn how to configure the app. + refer to https://docs.slack.dev/tools/bolt-python/concepts/authenticating-oauth to learn how to configure the app. Args: logger: The custom logger that can be used in this app. @@ -356,6 +354,7 @@ def message_hello(message, say): listener_executor = ThreadPoolExecutor(max_workers=5) self._assistant_thread_context_store = assistant_thread_context_store + self._attaching_conversation_kwargs_enabled = attaching_conversation_kwargs_enabled self._process_before_response = process_before_response self._listener_runner = ThreadListenerRunner( @@ -670,7 +669,7 @@ def middleware_func(logger, body, next): # Pass a function to this method app.middleware(middleware_func) - Refer to https://slack.dev/bolt-python/concepts#global-middleware for details. + Refer to https://docs.slack.dev/tools/bolt-python/concepts/global-middleware for details. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -716,7 +715,7 @@ def step( """ Deprecated: Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://api.slack.com/automation/functions/custom-bolt + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ Registers a new step from app listener. @@ -734,7 +733,7 @@ def step( # Pass Step to set up listeners app.step(ws) - Refer to https://api.slack.com/workflows/steps for details of steps from apps. + Refer to https://docs.slack.dev/legacy/legacy-steps-from-apps/ for details of steps from apps. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -751,7 +750,7 @@ def step( warnings.warn( ( "Steps from apps for legacy workflows are now deprecated. " - "Use new custom steps: https://api.slack.com/automation/functions/custom-bolt" + "Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/" ), category=DeprecationWarning, ) @@ -828,7 +827,7 @@ def ask_for_introduction(event, say): # Pass a function to this method app.event("team_join")(ask_for_introduction) - Refer to https://api.slack.com/apis/connections/events-api for details of Events API. + Refer to https://docs.slack.dev/apis/events-api/ for details of Events API. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -840,10 +839,13 @@ def ask_for_introduction(event, say): middleware: A list of lister middleware functions. Only when all the middleware call `next()` method, the listener function can be invoked. """ + middleware = list(middleware) if middleware else [] def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) primary_matcher = builtin_matchers.event(event, base_logger=self._base_logger) + if self._attaching_conversation_kwargs_enabled: + middleware.insert(0, AttachingConversationKwargs(self._assistant_thread_context_store)) return self._register_listener(list(functions), primary_matcher, matchers, middleware, True) return __call__ @@ -866,7 +868,7 @@ def say_hello(message, say): # Pass a function to this method app.message(":wave:")(say_hello) - Refer to https://api.slack.com/events/message for details of `message` events. + Refer to https://docs.slack.dev/reference/events/message/ for details of `message` events. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -901,6 +903,8 @@ def __call__(*args, **kwargs): primary_matcher = builtin_matchers.message_event( keyword=keyword, constraints=constraints, base_logger=self._base_logger ) + if self._attaching_conversation_kwargs_enabled: + middleware.insert(0, AttachingConversationKwargs(self._assistant_thread_context_store)) middleware.insert(0, MessageListenerMatches(keyword)) return self._register_listener(list(functions), primary_matcher, matchers, middleware, True) @@ -912,6 +916,7 @@ def function( matchers: Optional[Sequence[Callable[..., bool]]] = None, middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, auto_acknowledge: bool = True, + ack_timeout: int = 3, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new Function listener. This method can be used as either a decorator or a method. @@ -940,13 +945,17 @@ def reverse_string(ack: Ack, inputs: dict, complete: Complete, fail: Fail): Only when all the middleware call `next()` method, the listener function can be invoked. """ + if auto_acknowledge is True: + if ack_timeout != 3: + self._framework_logger.warning(warning_ack_timeout_has_no_effect(callback_id, ack_timeout)) + matchers = list(matchers) if matchers else [] middleware = list(middleware) if middleware else [] def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) primary_matcher = builtin_matchers.function_executed(callback_id=callback_id, base_logger=self._base_logger) - return self._register_listener(functions, primary_matcher, matchers, middleware, auto_acknowledge) + return self._register_listener(functions, primary_matcher, matchers, middleware, auto_acknowledge, ack_timeout) return __call__ @@ -972,7 +981,7 @@ def repeat_text(ack, say, command): # Pass a function to this method app.command("/echo")(repeat_text) - Refer to https://api.slack.com/interactivity/slash-commands for details of Slash Commands. + Refer to https://docs.slack.dev/interactivity/implementing-slash-commands/ for details of Slash Commands. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -1019,7 +1028,7 @@ def open_modal(ack, body, client): # Pass a function to this method app.shortcut("open_modal")(open_modal) - Refer to https://api.slack.com/interactivity/shortcuts for details about Shortcuts. + Refer to https://docs.slack.dev/interactivity/implementing-shortcuts/ for details about Shortcuts. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -1087,9 +1096,9 @@ def update_message(ack): # Pass a function to this method app.action("approve_button")(update_message) - * Refer to https://api.slack.com/reference/interaction-payloads/block-actions for actions in `blocks`. - * Refer to https://api.slack.com/legacy/message-buttons for actions in `attachments`. - * Refer to https://api.slack.com/dialogs for actions in dialogs. + * Refer to https://docs.slack.dev/reference/interaction-payloads/block_actions-payload/ for actions in `blocks`. + * Refer to https://docs.slack.dev/legacy/legacy-messaging/legacy-message-buttons/ for actions in `attachments`. + * Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for actions in dialogs. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -1115,7 +1124,7 @@ def block_action( middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new `block_actions` action listener. - Refer to https://api.slack.com/reference/interaction-payloads/block-actions for details. + Refer to https://docs.slack.dev/reference/interaction-payloads/block_actions-payload/ for details. """ def __call__(*args, **kwargs): @@ -1132,7 +1141,7 @@ def attachment_action( middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new `interactive_message` action listener. - Refer to https://api.slack.com/legacy/message-buttons for details.""" + Refer to https://docs.slack.dev/legacy/legacy-messaging/legacy-message-buttons/ for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1148,7 +1157,7 @@ def dialog_submission( middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new `dialog_submission` listener. - Refer to https://api.slack.com/dialogs for details.""" + Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1164,7 +1173,7 @@ def dialog_cancellation( middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new `dialog_cancellation` listener. - Refer to https://api.slack.com/dialogs for details.""" + Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1205,7 +1214,7 @@ def handle_submission(ack, body, client, view): # Pass a function to this method app.view("view_1")(handle_submission) - Refer to https://api.slack.com/reference/interaction-payloads/views for details of payloads. + Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload for details of payloads. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -1231,7 +1240,9 @@ def view_submission( middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new `view_submission` listener. - Refer to https://api.slack.com/reference/interaction-payloads/views#view_submission for details.""" + Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for + details. + """ def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1247,7 +1258,7 @@ def view_closed( middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new `view_closed` listener. - Refer to https://api.slack.com/reference/interaction-payloads/views#view_closed for details.""" + Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_closed for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1288,8 +1299,8 @@ def show_menu_options(ack): Refer to the following documents for details: - * https://api.slack.com/reference/block-kit/block-elements#external_select - * https://api.slack.com/reference/block-kit/block-elements#external_multi_select + * https://docs.slack.dev/reference/block-kit/block-elements/select-menu-element#external_select + * https://docs.slack.dev/reference/block-kit/block-elements/multi-select-menu-element#external_multi_select To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.args`'s API document. @@ -1329,7 +1340,7 @@ def dialog_suggestion( middleware: Optional[Sequence[Union[Callable, Middleware]]] = None, ) -> Callable[..., Optional[Callable[..., Optional[BoltResponse]]]]: """Registers a new `dialog_suggestion` listener. - Refer to https://api.slack.com/dialogs for details.""" + Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1381,6 +1392,7 @@ def _init_context(self, req: BoltRequest): proxy=self._client.proxy, headers=self._client.headers, team_id=req.context.team_id, + logger=self._client.logger, retry_handlers=self._client.retry_handlers.copy() if self._client.retry_handlers is not None else None, ) req.context["client"] = client_per_request @@ -1389,20 +1401,6 @@ def _init_context(self, req: BoltRequest): # It is intended for apps that start lazy listeners from their custom global middleware. req.context["listener_runner"] = self.listener_runner - # For AI Agents & Assistants - if is_assistant_event(req.body): - assistant = AssistantUtilities( - payload=to_event(req.body), # type:ignore[arg-type] - context=req.context, - thread_context_store=self._assistant_thread_context_store, - ) - req.context["say"] = assistant.say - req.context["set_status"] = assistant.set_status - req.context["set_title"] = assistant.set_title - req.context["set_suggested_prompts"] = assistant.set_suggested_prompts - req.context["get_thread_context"] = assistant.get_thread_context - req.context["save_thread_context"] = assistant.save_thread_context - @staticmethod def _to_listener_functions( kwargs: dict, @@ -1421,6 +1419,7 @@ def _register_listener( matchers: Optional[Sequence[Callable[..., bool]]], middleware: Optional[Sequence[Union[Callable, Middleware]]], auto_acknowledgement: bool = False, + ack_timeout: int = 3, ) -> Optional[Callable[..., Optional[BoltResponse]]]: value_to_return = None if not isinstance(functions, list): @@ -1447,10 +1446,11 @@ def _register_listener( CustomListener( app_name=self.name, ack_function=functions.pop(0), - lazy_functions=functions, + lazy_functions=functions, # type: ignore[arg-type] matchers=listener_matchers, middleware=listener_middleware, auto_acknowledgement=auto_acknowledgement, + ack_timeout=ack_timeout, base_logger=self._base_logger, ) ) diff --git a/slack_bolt/app/async_app.py b/slack_bolt/app/async_app.py index 50a36e5dd..cc94f9e15 100644 --- a/slack_bolt/app/async_app.py +++ b/slack_bolt/app/async_app.py @@ -8,7 +8,6 @@ from aiohttp import web from slack_bolt.app.async_server import AsyncSlackAppServer -from slack_bolt.context.assistant.async_assistant_utilities import AsyncAssistantUtilities from slack_bolt.context.assistant.thread_context_store.async_store import ( AsyncAssistantThreadContextStore, ) @@ -30,7 +29,6 @@ AsyncMessageListenerMatches, ) from slack_bolt.oauth.async_internals import select_consistent_installation_store -from slack_bolt.request.payload_utils import is_assistant_event, to_event from slack_bolt.util.utils import get_name_for_callable, is_callable_coroutine from slack_bolt.workflows.step.async_step import ( AsyncWorkflowStep, @@ -68,6 +66,7 @@ info_default_oauth_settings_loaded, error_installation_store_required_for_builtin_listeners, warning_unhandled_by_global_middleware, + warning_ack_timeout_has_no_effect, ) from slack_bolt.lazy_listener.asyncio_runner import AsyncioLazyListenerRunner from slack_bolt.listener.async_listener import AsyncListener, AsyncCustomListener @@ -87,6 +86,7 @@ AsyncIgnoringSelfEvents, AsyncUrlVerification, AsyncAttachingFunctionToken, + AsyncAttachingConversationKwargs, ) from slack_bolt.middleware.async_custom_middleware import ( AsyncMiddleware, @@ -142,6 +142,7 @@ def __init__( verification_token: Optional[str] = None, # for AI Agents & Assistants assistant_thread_context_store: Optional[AsyncAssistantThreadContextStore] = None, + attaching_conversation_kwargs_enabled: bool = True, ): """Bolt App that provides functionalities to register middleware/listeners. @@ -164,10 +165,10 @@ async def message_hello(message, say): # async function if __name__ == "__main__": app.start(port=int(os.environ.get("PORT", 3000))) - Refer to https://slack.dev/bolt-python/concepts#async for details. + Refer to https://docs.slack.dev/tools/bolt-python/concepts/async for details. If you would like to build an OAuth app for enabling the app to run with multiple workspaces, - refer to https://slack.dev/bolt-python/concepts#authenticating-oauth to learn how to configure the app. + refer to https://docs.slack.dev/tools/bolt-python/concepts/authenticating-oauth to learn how to configure the app. Args: logger: The custom logger that can be used in this app. @@ -362,6 +363,7 @@ async def message_hello(message, say): # async function self._async_listeners: List[AsyncListener] = [] self._assistant_thread_context_store = assistant_thread_context_store + self._attaching_conversation_kwargs_enabled = attaching_conversation_kwargs_enabled self._process_before_response = process_before_response self._async_listener_runner = AsyncioListenerRunner( @@ -615,7 +617,7 @@ async def async_middleware_next(): self._framework_logger.debug(debug_checking_listener(listener_name)) if await listener.async_matches(req=req, resp=resp): # type: ignore[arg-type] # run all the middleware attached to this listener first - (middleware_resp, next_was_not_called) = await listener.run_async_middleware( + middleware_resp, next_was_not_called = await listener.run_async_middleware( req=req, resp=resp # type: ignore[arg-type] ) if next_was_not_called: @@ -737,7 +739,7 @@ def step( """ Deprecated: Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://api.slack.com/automation/functions/custom-bolt + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ Registers a new step from app listener. @@ -755,7 +757,7 @@ def step( # Pass Step to set up listeners app.step(ws) - Refer to https://api.slack.com/workflows/steps for details of steps from apps. + Refer to https://docs.slack.dev/legacy/legacy-steps-from-apps/ for details of steps from apps. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.async_args`'s API document. For further information about AsyncWorkflowStep specific function arguments @@ -771,7 +773,7 @@ def step( warnings.warn( ( "Steps from apps for legacy workflows are now deprecated. " - "Use new custom steps: https://api.slack.com/automation/functions/custom-bolt" + "Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/" ), category=DeprecationWarning, ) @@ -853,7 +855,7 @@ async def ask_for_introduction(event, say): # Pass a function to this method app.event("team_join")(ask_for_introduction) - Refer to https://api.slack.com/apis/connections/events-api for details of Events API. + Refer to https://docs.slack.dev/apis/events-api/ for details of Events API. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.async_args`'s API document. @@ -865,10 +867,13 @@ async def ask_for_introduction(event, say): middleware: A list of lister middleware functions. Only when all the middleware call `next()` method, the listener function can be invoked. """ + middleware = list(middleware) if middleware else [] def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) primary_matcher = builtin_matchers.event(event, True, base_logger=self._base_logger) + if self._attaching_conversation_kwargs_enabled: + middleware.insert(0, AsyncAttachingConversationKwargs(self._assistant_thread_context_store)) return self._register_listener(list(functions), primary_matcher, matchers, middleware, True) return __call__ @@ -891,7 +896,7 @@ async def say_hello(message, say): # Pass a function to this method app.message(":wave:")(say_hello) - Refer to https://api.slack.com/events/message for details of `message` events. + Refer to https://docs.slack.dev/reference/events/message/ for details of `message` events. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.async_args`'s API document. @@ -929,6 +934,8 @@ def __call__(*args, **kwargs): asyncio=True, base_logger=self._base_logger, ) + if self._attaching_conversation_kwargs_enabled: + middleware.insert(0, AsyncAttachingConversationKwargs(self._assistant_thread_context_store)) middleware.insert(0, AsyncMessageListenerMatches(keyword)) return self._register_listener(list(functions), primary_matcher, matchers, middleware, True) @@ -940,6 +947,7 @@ def function( matchers: Optional[Sequence[Callable[..., Awaitable[bool]]]] = None, middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None, auto_acknowledge: bool = True, + ack_timeout: int = 3, ) -> Callable[..., Optional[Callable[..., Awaitable[BoltResponse]]]]: """Registers a new Function listener. This method can be used as either a decorator or a method. @@ -967,6 +975,9 @@ async def reverse_string(ack: AsyncAck, inputs: dict, complete: AsyncComplete, f middleware: A list of lister middleware functions. Only when all the middleware call `next()` method, the listener function can be invoked. """ + if auto_acknowledge is True: + if ack_timeout != 3: + self._framework_logger.warning(warning_ack_timeout_has_no_effect(callback_id, ack_timeout)) matchers = list(matchers) if matchers else [] middleware = list(middleware) if middleware else [] @@ -976,7 +987,7 @@ def __call__(*args, **kwargs): primary_matcher = builtin_matchers.function_executed( callback_id=callback_id, base_logger=self._base_logger, asyncio=True ) - return self._register_listener(functions, primary_matcher, matchers, middleware, auto_acknowledge) + return self._register_listener(functions, primary_matcher, matchers, middleware, auto_acknowledge, ack_timeout) return __call__ @@ -1002,7 +1013,7 @@ async def repeat_text(ack, say, command): # Pass a function to this method app.command("/echo")(repeat_text) - Refer to https://api.slack.com/interactivity/slash-commands for details of Slash Commands. + Refer to https://docs.slack.dev/interactivity/implementing-slash-commands/ for details of Slash Commands. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.async_args`'s API document. @@ -1049,7 +1060,7 @@ async def open_modal(ack, body, client): # Pass a function to this method app.shortcut("open_modal")(open_modal) - Refer to https://api.slack.com/interactivity/shortcuts for details about Shortcuts. + Refer to https://docs.slack.dev/interactivity/implementing-shortcuts/ for details about Shortcuts. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.async_args`'s API document. @@ -1117,9 +1128,9 @@ async def update_message(ack): # Pass a function to this method app.action("approve_button")(update_message) - * Refer to https://api.slack.com/reference/interaction-payloads/block-actions for actions in `blocks`. - * Refer to https://api.slack.com/legacy/message-buttons for actions in `attachments`. - * Refer to https://api.slack.com/dialogs for actions in dialogs. + * Refer to https://docs.slack.dev/reference/interaction-payloads/block_actions-payload/ for actions in `blocks`. + * Refer to https://docs.slack.dev/legacy/legacy-messaging/legacy-message-buttons/ for actions in `attachments`. + * Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for actions in dialogs. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.async_args`'s API document. @@ -1145,7 +1156,7 @@ def block_action( middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None, ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]: """Registers a new `block_actions` action listener. - Refer to https://api.slack.com/reference/interaction-payloads/block-actions for details. + Refer to https://docs.slack.dev/reference/interaction-payloads/block_actions-payload/ for details. """ def __call__(*args, **kwargs): @@ -1162,7 +1173,7 @@ def attachment_action( middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None, ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]: """Registers a new `interactive_message` action listener. - Refer to https://api.slack.com/legacy/message-buttons for details.""" + Refer to https://docs.slack.dev/legacy/legacy-messaging/legacy-message-buttons/ for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1178,7 +1189,7 @@ def dialog_submission( middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None, ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]: """Registers a new `dialog_submission` listener. - Refer to https://api.slack.com/dialogs for details.""" + Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1194,7 +1205,7 @@ def dialog_cancellation( middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None, ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]: """Registers a new `dialog_submission` listener. - Refer to https://api.slack.com/dialogs for details.""" + Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1235,7 +1246,7 @@ async def handle_submission(ack, body, client, view): # Pass a function to this method app.view("view_1")(handle_submission) - Refer to https://api.slack.com/reference/interaction-payloads/views for details of payloads. + Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload for details of payloads. To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.async_args`'s API document. @@ -1261,7 +1272,9 @@ def view_submission( middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None, ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]: """Registers a new `view_submission` listener. - Refer to https://api.slack.com/reference/interaction-payloads/views#view_submission for details.""" + Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_submission for + details. + """ def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1277,7 +1290,7 @@ def view_closed( middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None, ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]: """Registers a new `view_closed` listener. - Refer to https://api.slack.com/reference/interaction-payloads/views#view_closed for details.""" + Refer to https://docs.slack.dev/reference/interaction-payloads/view-interactions-payload/#view_closed for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1318,8 +1331,8 @@ async def show_menu_options(ack): Refer to the following documents for details: - * https://api.slack.com/reference/block-kit/block-elements#external_select - * https://api.slack.com/reference/block-kit/block-elements#external_multi_select + * https://docs.slack.dev/reference/block-kit/block-elements/select-menu-element#external_select + * https://docs.slack.dev/reference/block-kit/block-elements/multi-select-menu-element#external_multi_select To learn available arguments for middleware/listeners, see `slack_bolt.kwargs_injection.async_args`'s API document. @@ -1359,7 +1372,7 @@ def dialog_suggestion( middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]] = None, ) -> Callable[..., Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]]: """Registers a new `dialog_suggestion` listener. - Refer to https://api.slack.com/dialogs for details.""" + Refer to https://docs.slack.dev/legacy/legacy-dialogs/ for details.""" def __call__(*args, **kwargs): functions = self._to_listener_functions(kwargs) if kwargs else list(args) @@ -1413,6 +1426,7 @@ def _init_context(self, req: AsyncBoltRequest): trust_env_in_session=self._async_client.trust_env_in_session, headers=self._async_client.headers, team_id=req.context.team_id, + logger=self._async_client.logger, retry_handlers=( self._async_client.retry_handlers.copy() if self._async_client.retry_handlers is not None else None ), @@ -1423,20 +1437,6 @@ def _init_context(self, req: AsyncBoltRequest): # It is intended for apps that start lazy listeners from their custom global middleware. req.context["listener_runner"] = self.listener_runner - # For AI Agents & Assistants - if is_assistant_event(req.body): - assistant = AsyncAssistantUtilities( - payload=to_event(req.body), # type:ignore[arg-type] - context=req.context, - thread_context_store=self._assistant_thread_context_store, - ) - req.context["say"] = assistant.say - req.context["set_status"] = assistant.set_status - req.context["set_title"] = assistant.set_title - req.context["set_suggested_prompts"] = assistant.set_suggested_prompts - req.context["get_thread_context"] = assistant.get_thread_context - req.context["save_thread_context"] = assistant.save_thread_context - @staticmethod def _to_listener_functions( kwargs: dict, @@ -1455,6 +1455,7 @@ def _register_listener( matchers: Optional[Sequence[Callable[..., Awaitable[bool]]]], middleware: Optional[Sequence[Union[Callable, AsyncMiddleware]]], auto_acknowledgement: bool = False, + ack_timeout: int = 3, ) -> Optional[Callable[..., Awaitable[Optional[BoltResponse]]]]: value_to_return = None if not isinstance(functions, list): @@ -1486,10 +1487,11 @@ def _register_listener( AsyncCustomListener( app_name=self.name, ack_function=functions.pop(0), - lazy_functions=functions, + lazy_functions=functions, # type: ignore[arg-type] matchers=listener_matchers, middleware=listener_middleware, auto_acknowledgement=auto_acknowledgement, + ack_timeout=ack_timeout, base_logger=self._base_logger, ) ) diff --git a/slack_bolt/app/async_server.py b/slack_bolt/app/async_server.py index 998cd5a4b..f21d35932 100644 --- a/slack_bolt/app/async_server.py +++ b/slack_bolt/app/async_server.py @@ -1,5 +1,5 @@ import logging -from typing import Optional +from typing import Optional, TYPE_CHECKING from aiohttp import web @@ -7,19 +7,22 @@ from slack_bolt.response import BoltResponse from slack_bolt.util.utils import get_boot_message +if TYPE_CHECKING: + from slack_bolt.app.async_app import AsyncApp + class AsyncSlackAppServer: port: int path: str host: str - bolt_app: "AsyncApp" # type: ignore[name-defined] + bolt_app: "AsyncApp" web_app: web.Application def __init__( self, port: int, path: str, - app: "AsyncApp", # type: ignore[name-defined] + app: "AsyncApp", host: Optional[str] = None, ): """Standalone AIOHTTP Web Server. @@ -34,7 +37,7 @@ def __init__( self.port = port self.path = path self.host = host if host is not None else "0.0.0.0" - self.bolt_app: "AsyncApp" = app # type: ignore[name-defined] + self.bolt_app: "AsyncApp" = app self.web_app = web.Application() self._bolt_oauth_flow = self.bolt_app.oauth_flow if self._bolt_oauth_flow: diff --git a/slack_bolt/async_app.py b/slack_bolt/async_app.py index 10878c51b..f95d952aa 100644 --- a/slack_bolt/async_app.py +++ b/slack_bolt/async_app.py @@ -5,7 +5,7 @@ If you'd prefer to build your app with [asyncio](https://docs.python.org/3/library/asyncio.html), you can import the [AIOHTTP](https://docs.aiohttp.org/en/stable/) library and call the `AsyncApp` constructor. Within async apps, you can use the async/await pattern. ```bash -# Python 3.6+ required +# Python 3.7+ required python -m venv .venv source .venv/bin/activate @@ -59,6 +59,7 @@ async def command(ack, body, respond): from .context.set_suggested_prompts.async_set_suggested_prompts import AsyncSetSuggestedPrompts from .context.get_thread_context.async_get_thread_context import AsyncGetThreadContext from .context.save_thread_context.async_save_thread_context import AsyncSaveThreadContext +from .context.say_stream.async_say_stream import AsyncSayStream __all__ = [ "AsyncApp", @@ -66,6 +67,7 @@ async def command(ack, body, respond): "AsyncBoltContext", "AsyncRespond", "AsyncSay", + "AsyncSayStream", "AsyncListener", "AsyncCustomListenerMatcher", "AsyncBoltRequest", diff --git a/slack_bolt/authorization/__init__.py b/slack_bolt/authorization/__init__.py index a936a866b..4b80a93bb 100644 --- a/slack_bolt/authorization/__init__.py +++ b/slack_bolt/authorization/__init__.py @@ -1,7 +1,7 @@ """Authorization is the process of determining which Slack credentials should be available while processing an incoming Slack event. -Refer to https://slack.dev/bolt-python/concepts#authorization for details. +Refer to https://docs.slack.dev/tools/bolt-python/concepts/authorization for details. """ from .authorize_result import AuthorizeResult diff --git a/slack_bolt/context/__init__.py b/slack_bolt/context/__init__.py index fb3337c7a..865825601 100644 --- a/slack_bolt/context/__init__.py +++ b/slack_bolt/context/__init__.py @@ -2,7 +2,7 @@ Bolt automatically attaches information that is included in the incoming event, like `user_id`, `team_id`, `channel_id`, and `enterprise_id`. -Refer to https://slack.dev/bolt-python/concepts#context for details. +Refer to https://docs.slack.dev/tools/bolt-python/concepts/context for details. """ # Don't add async module imports here diff --git a/slack_bolt/context/assistant/assistant_utilities.py b/slack_bolt/context/assistant/assistant_utilities.py index 53500efdb..e9614fd8d 100644 --- a/slack_bolt/context/assistant/assistant_utilities.py +++ b/slack_bolt/context/assistant/assistant_utilities.py @@ -10,8 +10,6 @@ from .internals import has_channel_id_and_thread_ts from ..get_thread_context.get_thread_context import GetThreadContext from ..save_thread_context import SaveThreadContext -from ..set_status import SetStatus -from ..set_suggested_prompts import SetSuggestedPrompts from ..set_title import SetTitle @@ -46,21 +44,10 @@ def __init__( # When moving this code to Bolt internals, no need to raise an exception for this pattern raise ValueError(f"Cannot instantiate Assistant for this event pattern ({self.payload})") - def is_valid(self) -> bool: - return self.channel_id is not None and self.thread_ts is not None - - @property - def set_status(self) -> SetStatus: - return SetStatus(self.client, self.channel_id, self.thread_ts) - @property def set_title(self) -> SetTitle: return SetTitle(self.client, self.channel_id, self.thread_ts) - @property - def set_suggested_prompts(self) -> SetSuggestedPrompts: - return SetSuggestedPrompts(self.client, self.channel_id, self.thread_ts) - @property def say(self) -> Say: def build_metadata() -> Optional[dict]: diff --git a/slack_bolt/context/assistant/async_assistant_utilities.py b/slack_bolt/context/assistant/async_assistant_utilities.py index 5a7324e99..6acf531ce 100644 --- a/slack_bolt/context/assistant/async_assistant_utilities.py +++ b/slack_bolt/context/assistant/async_assistant_utilities.py @@ -13,8 +13,6 @@ from .internals import has_channel_id_and_thread_ts from ..get_thread_context.async_get_thread_context import AsyncGetThreadContext from ..save_thread_context.async_save_thread_context import AsyncSaveThreadContext -from ..set_status.async_set_status import AsyncSetStatus -from ..set_suggested_prompts.async_set_suggested_prompts import AsyncSetSuggestedPrompts from ..set_title.async_set_title import AsyncSetTitle @@ -49,21 +47,10 @@ def __init__( # When moving this code to Bolt internals, no need to raise an exception for this pattern raise ValueError(f"Cannot instantiate Assistant for this event pattern ({self.payload})") - def is_valid(self) -> bool: - return self.channel_id is not None and self.thread_ts is not None - - @property - def set_status(self) -> AsyncSetStatus: - return AsyncSetStatus(self.client, self.channel_id, self.thread_ts) - @property def set_title(self) -> AsyncSetTitle: return AsyncSetTitle(self.client, self.channel_id, self.thread_ts) - @property - def set_suggested_prompts(self) -> AsyncSetSuggestedPrompts: - return AsyncSetSuggestedPrompts(self.client, self.channel_id, self.thread_ts) - @property def say(self) -> AsyncSay: return AsyncSay( diff --git a/slack_bolt/context/async_context.py b/slack_bolt/context/async_context.py index 47eb4744e..94b2b5cbe 100644 --- a/slack_bolt/context/async_context.py +++ b/slack_bolt/context/async_context.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Optional, TYPE_CHECKING from slack_sdk.web.async_client import AsyncWebClient @@ -10,11 +10,15 @@ from slack_bolt.context.get_thread_context.async_get_thread_context import AsyncGetThreadContext from slack_bolt.context.save_thread_context.async_save_thread_context import AsyncSaveThreadContext from slack_bolt.context.say.async_say import AsyncSay +from slack_bolt.context.say_stream.async_say_stream import AsyncSayStream from slack_bolt.context.set_status.async_set_status import AsyncSetStatus from slack_bolt.context.set_suggested_prompts.async_set_suggested_prompts import AsyncSetSuggestedPrompts from slack_bolt.context.set_title.async_set_title import AsyncSetTitle from slack_bolt.util.utils import create_copy +if TYPE_CHECKING: + from slack_bolt.listener.asyncio_runner import AsyncioListenerRunner + class AsyncBoltContext(BaseContext): """Context object associated with a request from Slack.""" @@ -41,7 +45,7 @@ def to_copyable(self) -> "AsyncBoltContext": # The return type is intentionally string to avoid circular imports @property - def listener_runner(self) -> "AsyncioListenerRunner": # type: ignore[name-defined] + def listener_runner(self) -> "AsyncioListenerRunner": """The properly configured listener_runner that is available for middleware/listeners.""" return self["listener_runner"] @@ -110,7 +114,7 @@ async def handle_button_clicks(ack, say): Callable `say()` function """ if "say" not in self: - self["say"] = AsyncSay(client=self.client, channel=self.channel_id, thread_ts=self.thread_ts) + self["say"] = AsyncSay(client=self.client, channel=self.channel_id) return self["say"] @property @@ -203,6 +207,10 @@ def set_suggested_prompts(self) -> Optional[AsyncSetSuggestedPrompts]: def get_thread_context(self) -> Optional[AsyncGetThreadContext]: return self.get("get_thread_context") + @property + def say_stream(self) -> Optional[AsyncSayStream]: + return self.get("say_stream") + @property def save_thread_context(self) -> Optional[AsyncSaveThreadContext]: return self.get("save_thread_context") diff --git a/slack_bolt/context/base_context.py b/slack_bolt/context/base_context.py index 843d5ef60..502febcb8 100644 --- a/slack_bolt/context/base_context.py +++ b/slack_bolt/context/base_context.py @@ -38,6 +38,7 @@ class BaseContext(dict): "set_status", "set_title", "set_suggested_prompts", + "say_stream", ] # Note that these items are not copyable, so when you add new items to this list, # you must modify ThreadListenerRunner/AsyncioListenerRunner's _build_lazy_request method to pass the values. diff --git a/slack_bolt/context/complete/async_complete.py b/slack_bolt/context/complete/async_complete.py index fe3d796d1..bb81c2d4a 100644 --- a/slack_bolt/context/complete/async_complete.py +++ b/slack_bolt/context/complete/async_complete.py @@ -7,6 +7,7 @@ class AsyncComplete: client: AsyncWebClient function_execution_id: Optional[str] + _called: bool def __init__( self, @@ -15,6 +16,7 @@ def __init__( ): self.client = client self.function_execution_id = function_execution_id + self._called = False async def __call__(self, outputs: Optional[Dict[str, Any]] = None) -> AsyncSlackResponse: """Signal the successful completion of the custom function. @@ -31,6 +33,15 @@ async def __call__(self, outputs: Optional[Dict[str, Any]] = None) -> AsyncSlack if self.function_execution_id is None: raise ValueError("complete is unsupported here as there is no function_execution_id") + self._called = True return await self.client.functions_completeSuccess( function_execution_id=self.function_execution_id, outputs=outputs or {} ) + + def has_been_called(self) -> bool: + """Check if this complete function has been called. + + Returns: + bool: True if the complete function has been called, False otherwise. + """ + return self._called diff --git a/slack_bolt/context/complete/complete.py b/slack_bolt/context/complete/complete.py index acba3a412..dc9382384 100644 --- a/slack_bolt/context/complete/complete.py +++ b/slack_bolt/context/complete/complete.py @@ -7,6 +7,7 @@ class Complete: client: WebClient function_execution_id: Optional[str] + _called: bool def __init__( self, @@ -15,6 +16,7 @@ def __init__( ): self.client = client self.function_execution_id = function_execution_id + self._called = False def __call__(self, outputs: Optional[Dict[str, Any]] = None) -> SlackResponse: """Signal the successful completion of the custom function. @@ -31,4 +33,13 @@ def __call__(self, outputs: Optional[Dict[str, Any]] = None) -> SlackResponse: if self.function_execution_id is None: raise ValueError("complete is unsupported here as there is no function_execution_id") + self._called = True return self.client.functions_completeSuccess(function_execution_id=self.function_execution_id, outputs=outputs or {}) + + def has_been_called(self) -> bool: + """Check if this complete function has been called. + + Returns: + bool: True if the complete function has been called, False otherwise. + """ + return self._called diff --git a/slack_bolt/context/context.py b/slack_bolt/context/context.py index 31edf2891..b101460a5 100644 --- a/slack_bolt/context/context.py +++ b/slack_bolt/context/context.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Optional, TYPE_CHECKING from slack_sdk import WebClient @@ -10,11 +10,15 @@ from slack_bolt.context.respond import Respond from slack_bolt.context.save_thread_context import SaveThreadContext from slack_bolt.context.say import Say +from slack_bolt.context.say_stream import SayStream from slack_bolt.context.set_status import SetStatus from slack_bolt.context.set_suggested_prompts import SetSuggestedPrompts from slack_bolt.context.set_title import SetTitle from slack_bolt.util.utils import create_copy +if TYPE_CHECKING: + from slack_bolt.listener.thread_runner import ThreadListenerRunner + class BoltContext(BaseContext): """Context object associated with a request from Slack.""" @@ -42,7 +46,7 @@ def to_copyable(self) -> "BoltContext": # The return type is intentionally string to avoid circular imports @property - def listener_runner(self) -> "ThreadListenerRunner": # type: ignore[name-defined] + def listener_runner(self) -> "ThreadListenerRunner": """The properly configured listener_runner that is available for middleware/listeners.""" return self["listener_runner"] @@ -111,7 +115,7 @@ def handle_button_clicks(ack, say): Callable `say()` function """ if "say" not in self: - self["say"] = Say(client=self.client, channel=self.channel_id, thread_ts=self.thread_ts) + self["say"] = Say(client=self.client, channel=self.channel_id) return self["say"] @property @@ -204,6 +208,10 @@ def set_suggested_prompts(self) -> Optional[SetSuggestedPrompts]: def get_thread_context(self) -> Optional[GetThreadContext]: return self.get("get_thread_context") + @property + def say_stream(self) -> Optional[SayStream]: + return self.get("say_stream") + @property def save_thread_context(self) -> Optional[SaveThreadContext]: return self.get("save_thread_context") diff --git a/slack_bolt/context/fail/async_fail.py b/slack_bolt/context/fail/async_fail.py index 10a39f735..da01067ba 100644 --- a/slack_bolt/context/fail/async_fail.py +++ b/slack_bolt/context/fail/async_fail.py @@ -7,6 +7,7 @@ class AsyncFail: client: AsyncWebClient function_execution_id: Optional[str] + _called: bool def __init__( self, @@ -15,6 +16,7 @@ def __init__( ): self.client = client self.function_execution_id = function_execution_id + self._called = False async def __call__(self, error: str) -> AsyncSlackResponse: """Signal that the custom function failed to complete. @@ -31,4 +33,13 @@ async def __call__(self, error: str) -> AsyncSlackResponse: if self.function_execution_id is None: raise ValueError("fail is unsupported here as there is no function_execution_id") + self._called = True return await self.client.functions_completeError(function_execution_id=self.function_execution_id, error=error) + + def has_been_called(self) -> bool: + """Check if this fail function has been called. + + Returns: + bool: True if the fail function has been called, False otherwise. + """ + return self._called diff --git a/slack_bolt/context/fail/fail.py b/slack_bolt/context/fail/fail.py index 483bcebc3..9b04f6118 100644 --- a/slack_bolt/context/fail/fail.py +++ b/slack_bolt/context/fail/fail.py @@ -7,6 +7,7 @@ class Fail: client: WebClient function_execution_id: Optional[str] + _called: bool def __init__( self, @@ -15,6 +16,7 @@ def __init__( ): self.client = client self.function_execution_id = function_execution_id + self._called = False def __call__(self, error: str) -> SlackResponse: """Signal that the custom function failed to complete. @@ -31,4 +33,13 @@ def __call__(self, error: str) -> SlackResponse: if self.function_execution_id is None: raise ValueError("fail is unsupported here as there is no function_execution_id") + self._called = True return self.client.functions_completeError(function_execution_id=self.function_execution_id, error=error) + + def has_been_called(self) -> bool: + """Check if this fail function has been called. + + Returns: + bool: True if the fail function has been called, False otherwise. + """ + return self._called diff --git a/slack_bolt/context/get_thread_context/async_get_thread_context.py b/slack_bolt/context/get_thread_context/async_get_thread_context.py index cb8683a10..03f7c6076 100644 --- a/slack_bolt/context/get_thread_context/async_get_thread_context.py +++ b/slack_bolt/context/get_thread_context/async_get_thread_context.py @@ -31,14 +31,10 @@ async def __call__(self) -> Optional[AssistantThreadContext]: if self.thread_context_loaded is True: return self._thread_context - if self.payload.get("assistant_thread") is not None: + thread = self.payload.get("assistant_thread") + if isinstance(thread, dict) and thread.get("context", {}).get("channel_id") is not None: # assistant_thread_started - thread = self.payload["assistant_thread"] - self._thread_context = ( - AssistantThreadContext(thread["context"]) - if thread.get("context", {}).get("channel_id") is not None - else None - ) + self._thread_context = AssistantThreadContext(thread["context"]) # for this event, the context will never be changed self.thread_context_loaded = True elif self.payload.get("channel") is not None and self.payload.get("thread_ts") is not None: diff --git a/slack_bolt/context/get_thread_context/get_thread_context.py b/slack_bolt/context/get_thread_context/get_thread_context.py index 0a77d2d9f..b9c9751e1 100644 --- a/slack_bolt/context/get_thread_context/get_thread_context.py +++ b/slack_bolt/context/get_thread_context/get_thread_context.py @@ -31,14 +31,10 @@ def __call__(self) -> Optional[AssistantThreadContext]: if self.thread_context_loaded is True: return self._thread_context - if self.payload.get("assistant_thread") is not None: + thread = self.payload.get("assistant_thread") + if isinstance(thread, dict) and thread.get("context", {}).get("channel_id") is not None: # assistant_thread_started - thread = self.payload["assistant_thread"] - self._thread_context = ( - AssistantThreadContext(thread["context"]) - if thread.get("context", {}).get("channel_id") is not None - else None - ) + self._thread_context = AssistantThreadContext(thread["context"]) # for this event, the context will never be changed self.thread_context_loaded = True elif self.payload.get("channel") is not None and self.payload.get("thread_ts") is not None: diff --git a/slack_bolt/context/say/async_say.py b/slack_bolt/context/say/async_say.py index b771529b0..c492e5d77 100644 --- a/slack_bolt/context/say/async_say.py +++ b/slack_bolt/context/say/async_say.py @@ -1,14 +1,14 @@ -from typing import Optional, Union, Dict, Sequence, Callable, Awaitable +from typing import Awaitable, Callable, Dict, Optional, Sequence, Union -from slack_sdk.models.metadata import Metadata - -from slack_bolt.context.say.internals import _can_say -from slack_bolt.util.utils import create_copy from slack_sdk.models.attachments import Attachment from slack_sdk.models.blocks import Block +from slack_sdk.models.metadata import Metadata from slack_sdk.web.async_client import AsyncWebClient from slack_sdk.web.async_slack_response import AsyncSlackResponse +from slack_bolt.context.say.internals import _can_say +from slack_bolt.util.utils import create_copy + class AsyncSay: client: Optional[AsyncWebClient] @@ -42,6 +42,7 @@ async def __call__( icon_emoji: Optional[str] = None, icon_url: Optional[str] = None, username: Optional[str] = None, + markdown_text: Optional[str] = None, mrkdwn: Optional[bool] = None, link_names: Optional[bool] = None, parse: Optional[str] = None, # none, full @@ -67,6 +68,7 @@ async def __call__( icon_emoji=icon_emoji, icon_url=icon_url, username=username, + markdown_text=markdown_text, mrkdwn=mrkdwn, link_names=link_names, parse=parse, diff --git a/slack_bolt/context/say/say.py b/slack_bolt/context/say/say.py index 6cfbcd801..a6e5904e3 100644 --- a/slack_bolt/context/say/say.py +++ b/slack_bolt/context/say/say.py @@ -1,4 +1,4 @@ -from typing import Optional, Union, Dict, Sequence, Callable +from typing import Callable, Dict, Optional, Sequence, Union from slack_sdk import WebClient from slack_sdk.models.attachments import Attachment @@ -45,6 +45,7 @@ def __call__( icon_emoji: Optional[str] = None, icon_url: Optional[str] = None, username: Optional[str] = None, + markdown_text: Optional[str] = None, mrkdwn: Optional[bool] = None, link_names: Optional[bool] = None, parse: Optional[str] = None, # none, full @@ -70,6 +71,7 @@ def __call__( icon_emoji=icon_emoji, icon_url=icon_url, username=username, + markdown_text=markdown_text, mrkdwn=mrkdwn, link_names=link_names, parse=parse, diff --git a/slack_bolt/context/say_stream/__init__.py b/slack_bolt/context/say_stream/__init__.py new file mode 100644 index 000000000..86db7b1cc --- /dev/null +++ b/slack_bolt/context/say_stream/__init__.py @@ -0,0 +1,6 @@ +# Don't add async module imports here +from .say_stream import SayStream + +__all__ = [ + "SayStream", +] diff --git a/slack_bolt/context/say_stream/async_say_stream.py b/slack_bolt/context/say_stream/async_say_stream.py new file mode 100644 index 000000000..df9b362e2 --- /dev/null +++ b/slack_bolt/context/say_stream/async_say_stream.py @@ -0,0 +1,71 @@ +from typing import Optional + +from slack_sdk.web.async_client import AsyncWebClient +from slack_sdk.web.async_chat_stream import AsyncChatStream + + +class AsyncSayStream: + client: AsyncWebClient + channel: Optional[str] + recipient_team_id: Optional[str] + recipient_user_id: Optional[str] + thread_ts: Optional[str] + + def __init__( + self, + *, + client: AsyncWebClient, + channel: Optional[str] = None, + recipient_team_id: Optional[str] = None, + recipient_user_id: Optional[str] = None, + thread_ts: Optional[str] = None, + ): + self.client = client + self.channel = channel + self.recipient_team_id = recipient_team_id + self.recipient_user_id = recipient_user_id + self.thread_ts = thread_ts + + async def __call__( + self, + *, + buffer_size: Optional[int] = None, + channel: Optional[str] = None, + recipient_team_id: Optional[str] = None, + recipient_user_id: Optional[str] = None, + thread_ts: Optional[str] = None, + icon_emoji: Optional[str] = None, + icon_url: Optional[str] = None, + username: Optional[str] = None, + **kwargs, + ) -> AsyncChatStream: + """Starts a new chat stream with context.""" + channel = channel or self.channel + thread_ts = thread_ts or self.thread_ts + if channel is None: + raise ValueError("say_stream without channel here is unsupported") + if thread_ts is None: + raise ValueError("say_stream without thread_ts here is unsupported") + + if buffer_size is not None: + return await self.client.chat_stream( + buffer_size=buffer_size, + channel=channel, + recipient_team_id=recipient_team_id or self.recipient_team_id, + recipient_user_id=recipient_user_id or self.recipient_user_id, + thread_ts=thread_ts, + icon_emoji=icon_emoji, + icon_url=icon_url, + username=username, + **kwargs, + ) + return await self.client.chat_stream( + channel=channel, + recipient_team_id=recipient_team_id or self.recipient_team_id, + recipient_user_id=recipient_user_id or self.recipient_user_id, + thread_ts=thread_ts, + icon_emoji=icon_emoji, + icon_url=icon_url, + username=username, + **kwargs, + ) diff --git a/slack_bolt/context/say_stream/say_stream.py b/slack_bolt/context/say_stream/say_stream.py new file mode 100644 index 000000000..15bdcc110 --- /dev/null +++ b/slack_bolt/context/say_stream/say_stream.py @@ -0,0 +1,71 @@ +from typing import Optional + +from slack_sdk import WebClient +from slack_sdk.web.chat_stream import ChatStream + + +class SayStream: + client: WebClient + channel: Optional[str] + recipient_team_id: Optional[str] + recipient_user_id: Optional[str] + thread_ts: Optional[str] + + def __init__( + self, + *, + client: WebClient, + channel: Optional[str] = None, + recipient_team_id: Optional[str] = None, + recipient_user_id: Optional[str] = None, + thread_ts: Optional[str] = None, + ): + self.client = client + self.channel = channel + self.recipient_team_id = recipient_team_id + self.recipient_user_id = recipient_user_id + self.thread_ts = thread_ts + + def __call__( + self, + *, + buffer_size: Optional[int] = None, + channel: Optional[str] = None, + recipient_team_id: Optional[str] = None, + recipient_user_id: Optional[str] = None, + thread_ts: Optional[str] = None, + icon_emoji: Optional[str] = None, + icon_url: Optional[str] = None, + username: Optional[str] = None, + **kwargs, + ) -> ChatStream: + """Starts a new chat stream with context.""" + channel = channel or self.channel + thread_ts = thread_ts or self.thread_ts + if channel is None: + raise ValueError("say_stream without channel here is unsupported") + if thread_ts is None: + raise ValueError("say_stream without thread_ts here is unsupported") + + if buffer_size is not None: + return self.client.chat_stream( + buffer_size=buffer_size, + channel=channel, + recipient_team_id=recipient_team_id or self.recipient_team_id, + recipient_user_id=recipient_user_id or self.recipient_user_id, + thread_ts=thread_ts, + icon_emoji=icon_emoji, + icon_url=icon_url, + username=username, + **kwargs, + ) + return self.client.chat_stream( + channel=channel, + recipient_team_id=recipient_team_id or self.recipient_team_id, + recipient_user_id=recipient_user_id or self.recipient_user_id, + thread_ts=thread_ts, + icon_emoji=icon_emoji, + icon_url=icon_url, + username=username, + **kwargs, + ) diff --git a/slack_bolt/context/set_status/async_set_status.py b/slack_bolt/context/set_status/async_set_status.py index 926ec6de8..f10cc195c 100644 --- a/slack_bolt/context/set_status/async_set_status.py +++ b/slack_bolt/context/set_status/async_set_status.py @@ -1,3 +1,5 @@ +from typing import List, Optional + from slack_sdk.web.async_client import AsyncWebClient from slack_sdk.web.async_slack_response import AsyncSlackResponse @@ -17,9 +19,22 @@ def __init__( self.channel_id = channel_id self.thread_ts = thread_ts - async def __call__(self, status: str) -> AsyncSlackResponse: + async def __call__( + self, + status: str, + loading_messages: Optional[List[str]] = None, + icon_emoji: Optional[str] = None, + icon_url: Optional[str] = None, + username: Optional[str] = None, + **kwargs, + ) -> AsyncSlackResponse: return await self.client.assistant_threads_setStatus( - status=status, channel_id=self.channel_id, thread_ts=self.thread_ts, + status=status, + loading_messages=loading_messages, + icon_emoji=icon_emoji, + icon_url=icon_url, + username=username, + **kwargs, ) diff --git a/slack_bolt/context/set_status/set_status.py b/slack_bolt/context/set_status/set_status.py index 8df0d49a7..055a5cab7 100644 --- a/slack_bolt/context/set_status/set_status.py +++ b/slack_bolt/context/set_status/set_status.py @@ -1,3 +1,5 @@ +from typing import List, Optional + from slack_sdk import WebClient from slack_sdk.web import SlackResponse @@ -17,9 +19,22 @@ def __init__( self.channel_id = channel_id self.thread_ts = thread_ts - def __call__(self, status: str) -> SlackResponse: + def __call__( + self, + status: str, + loading_messages: Optional[List[str]] = None, + icon_emoji: Optional[str] = None, + icon_url: Optional[str] = None, + username: Optional[str] = None, + **kwargs, + ) -> SlackResponse: return self.client.assistant_threads_setStatus( - status=status, channel_id=self.channel_id, thread_ts=self.thread_ts, + status=status, + loading_messages=loading_messages, + icon_emoji=icon_emoji, + icon_url=icon_url, + username=username, + **kwargs, ) diff --git a/slack_bolt/context/set_suggested_prompts/async_set_suggested_prompts.py b/slack_bolt/context/set_suggested_prompts/async_set_suggested_prompts.py index aeeb244d7..68b41858b 100644 --- a/slack_bolt/context/set_suggested_prompts/async_set_suggested_prompts.py +++ b/slack_bolt/context/set_suggested_prompts/async_set_suggested_prompts.py @@ -1,4 +1,4 @@ -from typing import List, Dict, Union, Optional +from typing import Dict, List, Optional, Sequence, Union from slack_sdk.web.async_client import AsyncWebClient from slack_sdk.web.async_slack_response import AsyncSlackResponse @@ -7,13 +7,13 @@ class AsyncSetSuggestedPrompts: client: AsyncWebClient channel_id: str - thread_ts: str + thread_ts: Optional[str] def __init__( self, client: AsyncWebClient, channel_id: str, - thread_ts: str, + thread_ts: Optional[str] = None, ): self.client = client self.channel_id = channel_id @@ -21,8 +21,9 @@ def __init__( async def __call__( self, - prompts: List[Union[str, Dict[str, str]]], + prompts: Sequence[Union[str, Dict[str, str]]], title: Optional[str] = None, + thread_ts: Optional[str] = None, ) -> AsyncSlackResponse: prompts_arg: List[Dict[str, str]] = [] for prompt in prompts: @@ -33,7 +34,7 @@ async def __call__( return await self.client.assistant_threads_setSuggestedPrompts( channel_id=self.channel_id, - thread_ts=self.thread_ts, + thread_ts=thread_ts if thread_ts is not None else self.thread_ts, prompts=prompts_arg, title=title, ) diff --git a/slack_bolt/context/set_suggested_prompts/set_suggested_prompts.py b/slack_bolt/context/set_suggested_prompts/set_suggested_prompts.py index fc9304b17..349481df4 100644 --- a/slack_bolt/context/set_suggested_prompts/set_suggested_prompts.py +++ b/slack_bolt/context/set_suggested_prompts/set_suggested_prompts.py @@ -1,4 +1,4 @@ -from typing import List, Dict, Union, Optional +from typing import Dict, List, Optional, Sequence, Union from slack_sdk import WebClient from slack_sdk.web import SlackResponse @@ -7,13 +7,13 @@ class SetSuggestedPrompts: client: WebClient channel_id: str - thread_ts: str + thread_ts: Optional[str] def __init__( self, client: WebClient, channel_id: str, - thread_ts: str, + thread_ts: Optional[str] = None, ): self.client = client self.channel_id = channel_id @@ -21,8 +21,9 @@ def __init__( def __call__( self, - prompts: List[Union[str, Dict[str, str]]], + prompts: Sequence[Union[str, Dict[str, str]]], title: Optional[str] = None, + thread_ts: Optional[str] = None, ) -> SlackResponse: prompts_arg: List[Dict[str, str]] = [] for prompt in prompts: @@ -33,7 +34,7 @@ def __call__( return self.client.assistant_threads_setSuggestedPrompts( channel_id=self.channel_id, - thread_ts=self.thread_ts, + thread_ts=thread_ts if thread_ts is not None else self.thread_ts, prompts=prompts_arg, title=title, ) diff --git a/slack_bolt/kwargs_injection/args.py b/slack_bolt/kwargs_injection/args.py index 1a0ec3ca8..f2b4099d6 100644 --- a/slack_bolt/kwargs_injection/args.py +++ b/slack_bolt/kwargs_injection/args.py @@ -10,6 +10,7 @@ from slack_bolt.context.respond import Respond from slack_bolt.context.save_thread_context import SaveThreadContext from slack_bolt.context.say import Say +from slack_bolt.context.say_stream import SayStream from slack_bolt.context.set_status import SetStatus from slack_bolt.context.set_suggested_prompts import SetSuggestedPrompts from slack_bolt.context.set_title import SetTitle @@ -102,6 +103,8 @@ def handle_buttons(args): """`get_thread_context()` utility function for AI Agents & Assistants""" save_thread_context: Optional[SaveThreadContext] """`save_thread_context()` utility function for AI Agents & Assistants""" + say_stream: Optional[SayStream] + """`say_stream()` utility function for conversations, AI Agents & Assistants""" # middleware next: Callable[[], None] """`next()` utility function, which tells the middleware chain that it can continue with the next one""" @@ -135,6 +138,7 @@ def __init__( set_suggested_prompts: Optional[SetSuggestedPrompts] = None, get_thread_context: Optional[GetThreadContext] = None, save_thread_context: Optional[SaveThreadContext] = None, + say_stream: Optional[SayStream] = None, # As this method is not supposed to be invoked by bolt-python users, # the naming conflict with the built-in one affects # only the internals of this method @@ -168,6 +172,7 @@ def __init__( self.set_suggested_prompts = set_suggested_prompts self.get_thread_context = get_thread_context self.save_thread_context = save_thread_context + self.say_stream = say_stream self.next: Callable[[], None] = next self.next_: Callable[[], None] = next diff --git a/slack_bolt/kwargs_injection/async_args.py b/slack_bolt/kwargs_injection/async_args.py index 4953f2167..2217cfe9f 100644 --- a/slack_bolt/kwargs_injection/async_args.py +++ b/slack_bolt/kwargs_injection/async_args.py @@ -9,6 +9,7 @@ from slack_bolt.context.get_thread_context.async_get_thread_context import AsyncGetThreadContext from slack_bolt.context.save_thread_context.async_save_thread_context import AsyncSaveThreadContext from slack_bolt.context.say.async_say import AsyncSay +from slack_bolt.context.say_stream.async_say_stream import AsyncSayStream from slack_bolt.context.set_status.async_set_status import AsyncSetStatus from slack_bolt.context.set_suggested_prompts.async_set_suggested_prompts import AsyncSetSuggestedPrompts from slack_bolt.context.set_title.async_set_title import AsyncSetTitle @@ -101,6 +102,8 @@ async def handle_buttons(args): """`get_thread_context()` utility function for AI Agents & Assistants""" save_thread_context: Optional[AsyncSaveThreadContext] """`save_thread_context()` utility function for AI Agents & Assistants""" + say_stream: Optional[AsyncSayStream] + """`say_stream()` utility function for AI Agents & Assistants""" # middleware next: Callable[[], Awaitable[None]] """`next()` utility function, which tells the middleware chain that it can continue with the next one""" @@ -134,6 +137,7 @@ def __init__( set_suggested_prompts: Optional[AsyncSetSuggestedPrompts] = None, get_thread_context: Optional[AsyncGetThreadContext] = None, save_thread_context: Optional[AsyncSaveThreadContext] = None, + say_stream: Optional[AsyncSayStream] = None, next: Callable[[], Awaitable[None]], **kwargs, # noqa ): @@ -164,6 +168,7 @@ def __init__( self.set_suggested_prompts = set_suggested_prompts self.get_thread_context = get_thread_context self.save_thread_context = save_thread_context + self.say_stream = say_stream self.next: Callable[[], Awaitable[None]] = next self.next_: Callable[[], Awaitable[None]] = next diff --git a/slack_bolt/kwargs_injection/async_utils.py b/slack_bolt/kwargs_injection/async_utils.py index c8870c3cc..246fd10c9 100644 --- a/slack_bolt/kwargs_injection/async_utils.py +++ b/slack_bolt/kwargs_injection/async_utils.py @@ -29,7 +29,7 @@ def build_async_required_kwargs( error: Optional[Exception] = None, # for error handlers next_keys_required: bool = True, # False for listeners / middleware / error handlers ) -> Dict[str, Any]: - all_available_args = { + all_available_args: Dict[str, Any] = { "logger": logger, "client": request.context.client, "req": request, @@ -58,6 +58,7 @@ def build_async_required_kwargs( "set_suggested_prompts": request.context.set_suggested_prompts, "get_thread_context": request.context.get_thread_context, "save_thread_context": request.context.save_thread_context, + "say_stream": request.context.say_stream, # middleware "next": next_func, "next_": next_func, # for the middleware using Python's built-in `next()` function @@ -102,7 +103,7 @@ def build_async_required_kwargs( for name in required_arg_names: if name == "args": if isinstance(request, AsyncBoltRequest): - kwargs[name] = AsyncArgs(**all_available_args) # type: ignore[arg-type] + kwargs[name] = AsyncArgs(**all_available_args) else: logger.warning(f"Unknown Request object type detected ({type(request)})") diff --git a/slack_bolt/kwargs_injection/utils.py b/slack_bolt/kwargs_injection/utils.py index c1909c67a..218fbeb6e 100644 --- a/slack_bolt/kwargs_injection/utils.py +++ b/slack_bolt/kwargs_injection/utils.py @@ -29,7 +29,7 @@ def build_required_kwargs( error: Optional[Exception] = None, # for error handlers next_keys_required: bool = True, # False for listeners / middleware / error handlers ) -> Dict[str, Any]: - all_available_args = { + all_available_args: Dict[str, Any] = { "logger": logger, "client": request.context.client, "req": request, @@ -57,6 +57,7 @@ def build_required_kwargs( "set_title": request.context.set_title, "set_suggested_prompts": request.context.set_suggested_prompts, "save_thread_context": request.context.save_thread_context, + "say_stream": request.context.say_stream, # middleware "next": next_func, "next_": next_func, # for the middleware using Python's built-in `next()` function @@ -101,7 +102,7 @@ def build_required_kwargs( for name in required_arg_names: if name == "args": if isinstance(request, BoltRequest): - kwargs[name] = Args(**all_available_args) # type: ignore[arg-type] + kwargs[name] = Args(**all_available_args) else: logger.warning(f"Unknown Request object type detected ({type(request)})") diff --git a/slack_bolt/lazy_listener/__init__.py b/slack_bolt/lazy_listener/__init__.py index 4d9111cc3..a92c18483 100644 --- a/slack_bolt/lazy_listener/__init__.py +++ b/slack_bolt/lazy_listener/__init__.py @@ -19,7 +19,7 @@ def run_long_process(respond, body): lazy=[run_long_process] ) -Refer to https://slack.dev/bolt-python/concepts#lazy-listeners for more details. +Refer to https://docs.slack.dev/tools/bolt-python/concepts/lazy-listeners for more details. """ # Don't add async module imports here diff --git a/slack_bolt/listener/async_listener.py b/slack_bolt/listener/async_listener.py index c8758daf2..1717b1a8d 100644 --- a/slack_bolt/listener/async_listener.py +++ b/slack_bolt/listener/async_listener.py @@ -15,6 +15,7 @@ class AsyncListener(metaclass=ABCMeta): ack_function: Callable[..., Awaitable[BoltResponse]] lazy_functions: Sequence[Callable[..., Awaitable[None]]] auto_acknowledgement: bool + ack_timeout: int async def async_matches( self, @@ -71,13 +72,7 @@ async def run_ack_function(self, *, request: AsyncBoltRequest, response: BoltRes from logging import Logger -from typing import Callable, Awaitable - -from slack_bolt.listener_matcher.async_listener_matcher import AsyncListenerMatcher from slack_bolt.logger import get_bolt_app_logger -from slack_bolt.middleware.async_middleware import AsyncMiddleware -from slack_bolt.request.async_request import AsyncBoltRequest -from slack_bolt.response import BoltResponse class AsyncCustomListener(AsyncListener): @@ -87,6 +82,7 @@ class AsyncCustomListener(AsyncListener): matchers: Sequence[AsyncListenerMatcher] middleware: Sequence[AsyncMiddleware] auto_acknowledgement: bool + ack_timeout: int arg_names: MutableSequence[str] logger: Logger @@ -99,6 +95,7 @@ def __init__( matchers: Sequence[AsyncListenerMatcher], middleware: Sequence[AsyncMiddleware], auto_acknowledgement: bool = False, + ack_timeout: int = 3, base_logger: Optional[Logger] = None, ): self.app_name = app_name @@ -107,6 +104,7 @@ def __init__( self.matchers = matchers self.middleware = middleware self.auto_acknowledgement = auto_acknowledgement + self.ack_timeout = ack_timeout self.arg_names = get_arg_names_of_callable(ack_function) self.logger = get_bolt_app_logger(app_name, self.ack_function, base_logger) diff --git a/slack_bolt/listener/async_listener_error_handler.py b/slack_bolt/listener/async_listener_error_handler.py index 88f4b3510..b1a73458e 100644 --- a/slack_bolt/listener/async_listener_error_handler.py +++ b/slack_bolt/listener/async_listener_error_handler.py @@ -48,9 +48,10 @@ async def handle( ) returned_response = await self.func(**kwargs) if returned_response is not None and isinstance(returned_response, BoltResponse): - response.status = returned_response.status # type: ignore[union-attr] - response.headers = returned_response.headers # type: ignore[union-attr] - response.body = returned_response.body # type: ignore[union-attr] + assert response is not None, "response must be provided when returning a BoltResponse from an error handler" + response.status = returned_response.status + response.headers = returned_response.headers + response.body = returned_response.body class AsyncDefaultListenerErrorHandler(AsyncListenerErrorHandler): diff --git a/slack_bolt/listener/asyncio_runner.py b/slack_bolt/listener/asyncio_runner.py index 56dc29cc1..81f9e6106 100644 --- a/slack_bolt/listener/asyncio_runner.py +++ b/slack_bolt/listener/asyncio_runner.py @@ -149,7 +149,7 @@ async def run_ack_function_asynchronously( self._start_lazy_function(lazy_func, request) # await for the completion of ack() in the async listener execution - while ack.response is None and time.time() - starting_time <= 3: + while ack.response is None and time.time() - starting_time <= listener.ack_timeout: await asyncio.sleep(0.01) if response is None and ack.response is None: diff --git a/slack_bolt/listener/custom_listener.py b/slack_bolt/listener/custom_listener.py index b785dab6d..2b73018db 100644 --- a/slack_bolt/listener/custom_listener.py +++ b/slack_bolt/listener/custom_listener.py @@ -18,6 +18,7 @@ class CustomListener(Listener): matchers: Sequence[ListenerMatcher] middleware: Sequence[Middleware] auto_acknowledgement: bool + ack_timeout: int = 3 arg_names: MutableSequence[str] logger: Logger @@ -30,6 +31,7 @@ def __init__( matchers: Sequence[ListenerMatcher], middleware: Sequence[Middleware], auto_acknowledgement: bool = False, + ack_timeout: int = 3, base_logger: Optional[Logger] = None, ): self.app_name = app_name @@ -38,6 +40,7 @@ def __init__( self.matchers = matchers self.middleware = middleware self.auto_acknowledgement = auto_acknowledgement + self.ack_timeout = ack_timeout self.arg_names = get_arg_names_of_callable(ack_function) self.logger = get_bolt_app_logger(app_name, self.ack_function, base_logger) diff --git a/slack_bolt/listener/listener.py b/slack_bolt/listener/listener.py index d938935df..7685f3c7b 100644 --- a/slack_bolt/listener/listener.py +++ b/slack_bolt/listener/listener.py @@ -13,6 +13,7 @@ class Listener(metaclass=ABCMeta): ack_function: Callable[..., BoltResponse] lazy_functions: Sequence[Callable[..., None]] auto_acknowledgement: bool + ack_timeout: int = 3 def matches( self, diff --git a/slack_bolt/listener/listener_error_handler.py b/slack_bolt/listener/listener_error_handler.py index 0ad98f738..7dd6d066b 100644 --- a/slack_bolt/listener/listener_error_handler.py +++ b/slack_bolt/listener/listener_error_handler.py @@ -48,9 +48,10 @@ def handle( ) returned_response = self.func(**kwargs) if returned_response is not None and isinstance(returned_response, BoltResponse): - response.status = returned_response.status # type: ignore[union-attr] - response.headers = returned_response.headers # type: ignore[union-attr] - response.body = returned_response.body # type: ignore[union-attr] + assert response is not None, "response must be provided when returning a BoltResponse from an error handler" + response.status = returned_response.status + response.headers = returned_response.headers + response.body = returned_response.body class DefaultListenerErrorHandler(ListenerErrorHandler): diff --git a/slack_bolt/listener/thread_runner.py b/slack_bolt/listener/thread_runner.py index 0b79c6ffd..378ca1bfa 100644 --- a/slack_bolt/listener/thread_runner.py +++ b/slack_bolt/listener/thread_runner.py @@ -111,7 +111,7 @@ def run( if not request.lazy_only: # start the listener function asynchronously def run_ack_function_asynchronously(): - nonlocal ack, request, response + nonlocal response try: self.listener_start_handler.handle( request=request, @@ -160,7 +160,7 @@ def run_ack_function_asynchronously(): self._start_lazy_function(lazy_func, request) # await for the completion of ack() in the async listener execution - while ack.response is None and time.time() - starting_time <= 3: + while ack.response is None and time.time() - starting_time <= listener.ack_timeout: time.sleep(0.01) if response is None and ack.response is None: diff --git a/slack_bolt/listener_matcher/async_listener_matcher.py b/slack_bolt/listener_matcher/async_listener_matcher.py index 83e04e478..3230bb342 100644 --- a/slack_bolt/listener_matcher/async_listener_matcher.py +++ b/slack_bolt/listener_matcher/async_listener_matcher.py @@ -25,8 +25,6 @@ async def async_matches(self, req: AsyncBoltRequest, resp: BoltResponse) -> bool from slack_bolt.kwargs_injection.async_utils import build_async_required_kwargs from slack_bolt.logger import get_bolt_app_logger -from slack_bolt.request.async_request import AsyncBoltRequest -from slack_bolt.response import BoltResponse class AsyncCustomListenerMatcher(AsyncListenerMatcher): diff --git a/slack_bolt/listener_matcher/builtins.py b/slack_bolt/listener_matcher/builtins.py index fe06b9eef..76c12d452 100644 --- a/slack_bolt/listener_matcher/builtins.py +++ b/slack_bolt/listener_matcher/builtins.py @@ -1,5 +1,4 @@ import re -import sys from logging import Logger from slack_bolt.error import BoltError @@ -25,10 +24,7 @@ from ..logger.messages import error_message_event_type from ..util.utils import get_arg_names_of_callable -if sys.version_info.major == 3 and sys.version_info.minor <= 6: - from re import _pattern_type as Pattern # type: ignore[attr-defined] -else: - from re import Pattern +from re import Pattern from typing import Callable, Awaitable, Any, Sequence, Optional, Union, Dict from slack_bolt.kwargs_injection import build_required_kwargs @@ -169,7 +165,7 @@ def _check_event_subtype(event_payload: dict, constraints: dict) -> bool: return True -def _verify_message_event_type(event_type: str) -> None: +def _verify_message_event_type(event_type: Union[str, Pattern]) -> None: if isinstance(event_type, str) and event_type.startswith("message."): raise ValueError(error_message_event_type(event_type)) if isinstance(event_type, Pattern) and "message\\." in event_type.pattern: @@ -298,7 +294,7 @@ def func(body: Dict[str, Any]) -> bool: return dialog_submission(constraints["callback_id"], asyncio) if action_type == "dialog_cancellation": return dialog_cancellation(constraints["callback_id"], asyncio) - # https://api.slack.com/workflows/steps + # https://docs.slack.dev/legacy/legacy-steps-from-apps/ if action_type == "workflow_step_edit": return workflow_step_edit(constraints["callback_id"], asyncio) @@ -324,7 +320,7 @@ def _block_action( elif isinstance(constraints, dict): # block_id matching is optional block_id: Optional[Union[str, Pattern]] = constraints.get("block_id") - action_id = constraints.get("action_id") + action_id = constraints.get("action_id") # type: ignore[assignment] if block_id is None and action_id is None: return False block_id_matched = block_id is None or _matches(block_id, action.get("block_id")) # type: ignore[union-attr] diff --git a/slack_bolt/logger/messages.py b/slack_bolt/logger/messages.py index 16149a45b..80e68d022 100644 --- a/slack_bolt/logger/messages.py +++ b/slack_bolt/logger/messages.py @@ -1,3 +1,4 @@ +from re import Pattern import time from typing import Union, Dict, Any, Optional @@ -59,7 +60,7 @@ def error_authorize_conflicts() -> str: return "`authorize` in the top-level arguments is not allowed when you pass either `oauth_settings` or `oauth_flow`" -def error_message_event_type(event_type: str) -> str: +def error_message_event_type(event_type: Union[str, Pattern]) -> str: return ( f'Although the document mentions "{event_type}", ' 'it is not a valid event type. Use "message" instead. ' @@ -184,7 +185,7 @@ def warning_unhandled_request( ) -> str: filtered_body = _build_filtered_body(req.body) default_message = f"Unhandled request ({filtered_body})" - is_async = type(req) != BoltRequest + is_async = not isinstance(req, BoltRequest) if is_workflow_step_edit(req.body) or is_workflow_step_save(req.body) or is_workflow_step_execute(req.body): # @app.step callback_id = ( @@ -331,6 +332,11 @@ def warning_skip_uncommon_arg_name(arg_name: str) -> str: ) +def warning_ack_timeout_has_no_effect(identifier: Union[str, Pattern], ack_timeout: int) -> str: + handler_example = f'@app.function("{identifier}")' if isinstance(identifier, str) else f"@app.function({identifier})" + return f"On {handler_example}, as `auto_acknowledge` is `True`, " f"`ack_timeout={ack_timeout}` you gave will be unused" + + # ------------------------------- # Info # ------------------------------- @@ -342,7 +348,7 @@ def info_default_oauth_settings_loaded() -> str: "Bolt has enabled the file-based InstallationStore/OAuthStateStore for you. " "Note that these file-based stores are for local development. " "If you'd like to use a different data store, set the oauth_settings argument in the App constructor. " - "Please refer to https://slack.dev/bolt-python/concepts#authenticating-oauth for more details." + "Please refer to https://docs.slack.dev/tools/bolt-python/concepts/authenticating-oauth for more details." ) diff --git a/slack_bolt/middleware/__init__.py b/slack_bolt/middleware/__init__.py index 0e4044f99..c28ffd78d 100644 --- a/slack_bolt/middleware/__init__.py +++ b/slack_bolt/middleware/__init__.py @@ -17,6 +17,7 @@ from .ssl_check import SslCheck from .url_verification import UrlVerification from .attaching_function_token import AttachingFunctionToken +from .attaching_conversation_kwargs import AttachingConversationKwargs builtin_middleware_classes = [ SslCheck, @@ -41,5 +42,6 @@ "SslCheck", "UrlVerification", "AttachingFunctionToken", + "AttachingConversationKwargs", "builtin_middleware_classes", ] diff --git a/slack_bolt/middleware/assistant/assistant.py b/slack_bolt/middleware/assistant/assistant.py index beac71bca..ad842f94d 100644 --- a/slack_bolt/middleware/assistant/assistant.py +++ b/slack_bolt/middleware/assistant/assistant.py @@ -7,6 +7,7 @@ from slack_bolt.context.assistant.thread_context_store.store import AssistantThreadContextStore from slack_bolt.listener_matcher.builtins import build_listener_matcher +from slack_bolt.middleware.attaching_conversation_kwargs import AttachingConversationKwargs from slack_bolt.request.request import BoltRequest from slack_bolt.response.response import BoltResponse from slack_bolt.listener_matcher import CustomListenerMatcher @@ -67,7 +68,7 @@ def thread_started( self.build_listener( listener_or_functions=func, matchers=all_matchers, - middleware=middleware, # type:ignore[arg-type] + middleware=middleware, # type: ignore[arg-type] ) ) return func @@ -106,7 +107,7 @@ def user_message( self.build_listener( listener_or_functions=func, matchers=all_matchers, - middleware=middleware, # type:ignore[arg-type] + middleware=middleware, # type: ignore[arg-type] ) ) return func @@ -145,7 +146,7 @@ def bot_message( self.build_listener( listener_or_functions=func, matchers=all_matchers, - middleware=middleware, # type:ignore[arg-type] + middleware=middleware, # type: ignore[arg-type] ) ) return func @@ -184,7 +185,7 @@ def thread_context_changed( self.build_listener( listener_or_functions=func, matchers=all_matchers, - middleware=middleware, # type:ignore[arg-type] + middleware=middleware, # type: ignore[arg-type] ) ) return func @@ -214,13 +215,13 @@ def _merge_matchers( ): return [CustomListenerMatcher(app_name=self.app_name, func=primary_matcher)] + ( custom_matchers or [] - ) # type:ignore[operator] + ) # type: ignore[operator] @staticmethod def default_thread_context_changed(save_thread_context: SaveThreadContext, payload: dict): save_thread_context(payload["assistant_thread"]["context"]) - def process( # type:ignore[return] + def process( # type: ignore[return] self, *, req: BoltRequest, resp: BoltResponse, next: Callable[[], BoltResponse] ) -> Optional[BoltResponse]: if self._thread_context_changed_listeners is None: @@ -236,6 +237,15 @@ def process( # type:ignore[return] if listeners is not None: for listener in listeners: if listener.matches(req=req, resp=resp): + middleware_resp, next_was_not_called = listener.run_middleware(req=req, resp=resp) + if next_was_not_called: + if middleware_resp is not None: + return middleware_resp + # The listener middleware didn't call next(). + # Skip this listener and try the next one. + continue + if middleware_resp is not None: + resp = middleware_resp return listener_runner.run( request=req, response=resp, @@ -255,13 +265,14 @@ def build_listener( middleware: Optional[List[Middleware]] = None, base_logger: Optional[Logger] = None, ) -> Listener: - if isinstance(listener_or_functions, Callable): # type:ignore[arg-type] - listener_or_functions = [listener_or_functions] # type:ignore[list-item] + if isinstance(listener_or_functions, Callable): # type: ignore[arg-type] + listener_or_functions = [listener_or_functions] # type: ignore[list-item] if isinstance(listener_or_functions, Listener): return listener_or_functions elif isinstance(listener_or_functions, list): middleware = middleware if middleware else [] + middleware.insert(0, AttachingConversationKwargs(self.thread_context_store)) functions = listener_or_functions ack_function = functions.pop(0) @@ -270,7 +281,7 @@ def build_listener( for matcher in matchers: if isinstance(matcher, ListenerMatcher): listener_matchers.append(matcher) - elif isinstance(matcher, Callable): # type:ignore[arg-type] + elif isinstance(matcher, Callable): # type: ignore[arg-type] listener_matchers.append( build_listener_matcher( func=matcher, diff --git a/slack_bolt/middleware/assistant/async_assistant.py b/slack_bolt/middleware/assistant/async_assistant.py index 2fdd828d7..588de8b41 100644 --- a/slack_bolt/middleware/assistant/async_assistant.py +++ b/slack_bolt/middleware/assistant/async_assistant.py @@ -8,6 +8,9 @@ from slack_bolt.listener.asyncio_runner import AsyncioListenerRunner from slack_bolt.listener_matcher.builtins import build_listener_matcher +from slack_bolt.middleware.attaching_conversation_kwargs.async_attaching_conversation_kwargs import ( + AsyncAttachingConversationKwargs, +) from slack_bolt.request.async_request import AsyncBoltRequest from slack_bolt.response import BoltResponse from slack_bolt.error import BoltError @@ -63,7 +66,7 @@ def thread_started( func=is_assistant_thread_started_event, asyncio=True, base_logger=self.base_logger, - ), # type:ignore[arg-type] + ), # type: ignore[arg-type] matchers, ) if is_used_without_argument(args): @@ -72,7 +75,7 @@ def thread_started( self.build_listener( listener_or_functions=func, matchers=all_matchers, - middleware=middleware, # type:ignore[arg-type] + middleware=middleware, # type: ignore[arg-type] ) ) return func @@ -109,7 +112,7 @@ def user_message( func=is_user_message_event_in_assistant_thread, asyncio=True, base_logger=self.base_logger, - ), # type:ignore[arg-type] + ), # type: ignore[arg-type] matchers, ) if is_used_without_argument(args): @@ -118,7 +121,7 @@ def user_message( self.build_listener( listener_or_functions=func, matchers=all_matchers, - middleware=middleware, # type:ignore[arg-type] + middleware=middleware, # type: ignore[arg-type] ) ) return func @@ -155,7 +158,7 @@ def bot_message( func=is_bot_message_event_in_assistant_thread, asyncio=True, base_logger=self.base_logger, - ), # type:ignore[arg-type] + ), # type: ignore[arg-type] matchers, ) if is_used_without_argument(args): @@ -164,7 +167,7 @@ def bot_message( self.build_listener( listener_or_functions=func, matchers=all_matchers, - middleware=middleware, # type:ignore[arg-type] + middleware=middleware, # type: ignore[arg-type] ) ) return func @@ -201,7 +204,7 @@ def thread_context_changed( func=is_assistant_thread_context_changed_event, asyncio=True, base_logger=self.base_logger, - ), # type:ignore[arg-type] + ), # type: ignore[arg-type] matchers, ) if is_used_without_argument(args): @@ -210,7 +213,7 @@ def thread_context_changed( self.build_listener( listener_or_functions=func, matchers=all_matchers, - middleware=middleware, # type:ignore[arg-type] + middleware=middleware, # type: ignore[arg-type] ) ) return func @@ -238,14 +241,14 @@ def _merge_matchers( primary_matcher: Union[Callable[..., bool], AsyncListenerMatcher], custom_matchers: Optional[Union[Callable[..., bool], AsyncListenerMatcher]], ): - return [primary_matcher] + (custom_matchers or []) # type:ignore[operator] + return [primary_matcher] + (custom_matchers or []) # type: ignore[operator] @staticmethod async def default_thread_context_changed(save_thread_context: AsyncSaveThreadContext, payload: dict): new_context: dict = payload["assistant_thread"]["context"] await save_thread_context(new_context) - async def async_process( # type:ignore[return] + async def async_process( # type: ignore[return] self, *, req: AsyncBoltRequest, @@ -265,6 +268,15 @@ async def async_process( # type:ignore[return] if listeners is not None: for listener in listeners: if listener is not None and await listener.async_matches(req=req, resp=resp): + middleware_resp, next_was_not_called = await listener.run_async_middleware(req=req, resp=resp) + if next_was_not_called: + if middleware_resp is not None: + return middleware_resp + # The listener middleware didn't call next(). + # Skip this listener and try the next one. + continue + if middleware_resp is not None: + resp = middleware_resp return await listener_runner.run( request=req, response=resp, @@ -284,13 +296,14 @@ def build_listener( middleware: Optional[List[AsyncMiddleware]] = None, base_logger: Optional[Logger] = None, ) -> AsyncListener: - if isinstance(listener_or_functions, Callable): # type:ignore[arg-type] - listener_or_functions = [listener_or_functions] # type:ignore[list-item] + if isinstance(listener_or_functions, Callable): # type: ignore[arg-type] + listener_or_functions = [listener_or_functions] # type: ignore[list-item] if isinstance(listener_or_functions, AsyncListener): return listener_or_functions elif isinstance(listener_or_functions, list): middleware = middleware if middleware else [] + middleware.insert(0, AsyncAttachingConversationKwargs(self.thread_context_store)) functions = listener_or_functions ack_function = functions.pop(0) @@ -302,7 +315,7 @@ def build_listener( else: listener_matchers.append( build_listener_matcher( - func=matcher, # type:ignore[arg-type] + func=matcher, # type: ignore[arg-type] asyncio=True, base_logger=base_logger, ) diff --git a/slack_bolt/middleware/async_builtins.py b/slack_bolt/middleware/async_builtins.py index d2d82c1fb..8de07fb88 100644 --- a/slack_bolt/middleware/async_builtins.py +++ b/slack_bolt/middleware/async_builtins.py @@ -10,6 +10,7 @@ AsyncMessageListenerMatches, ) from .attaching_function_token.async_attaching_function_token import AsyncAttachingFunctionToken +from .attaching_conversation_kwargs.async_attaching_conversation_kwargs import AsyncAttachingConversationKwargs __all__ = [ "AsyncIgnoringSelfEvents", @@ -18,4 +19,5 @@ "AsyncUrlVerification", "AsyncMessageListenerMatches", "AsyncAttachingFunctionToken", + "AsyncAttachingConversationKwargs", ] diff --git a/slack_bolt/middleware/async_middleware_error_handler.py b/slack_bolt/middleware/async_middleware_error_handler.py index 1957d3ab6..932b0770b 100644 --- a/slack_bolt/middleware/async_middleware_error_handler.py +++ b/slack_bolt/middleware/async_middleware_error_handler.py @@ -48,9 +48,10 @@ async def handle( ) returned_response = await self.func(**kwargs) if returned_response is not None and isinstance(returned_response, BoltResponse): - response.status = returned_response.status # type: ignore[union-attr] - response.headers = returned_response.headers # type: ignore[union-attr] - response.body = returned_response.body # type: ignore[union-attr] + assert response is not None, "response must be provided when returning a BoltResponse from an error handler" + response.status = returned_response.status + response.headers = returned_response.headers + response.body = returned_response.body class AsyncDefaultMiddlewareErrorHandler(AsyncMiddlewareErrorHandler): diff --git a/slack_bolt/middleware/attaching_conversation_kwargs/__init__.py b/slack_bolt/middleware/attaching_conversation_kwargs/__init__.py new file mode 100644 index 000000000..ec72e0037 --- /dev/null +++ b/slack_bolt/middleware/attaching_conversation_kwargs/__init__.py @@ -0,0 +1,5 @@ +from .attaching_conversation_kwargs import AttachingConversationKwargs + +__all__ = [ + "AttachingConversationKwargs", +] diff --git a/slack_bolt/middleware/attaching_conversation_kwargs/async_attaching_conversation_kwargs.py b/slack_bolt/middleware/attaching_conversation_kwargs/async_attaching_conversation_kwargs.py new file mode 100644 index 000000000..ab69f5768 --- /dev/null +++ b/slack_bolt/middleware/attaching_conversation_kwargs/async_attaching_conversation_kwargs.py @@ -0,0 +1,80 @@ +from typing import Optional, Callable, Awaitable + +from slack_bolt.context.assistant.async_assistant_utilities import AsyncAssistantUtilities +from slack_bolt.context.assistant.thread_context_store.async_store import AsyncAssistantThreadContextStore +from slack_bolt.context.say_stream.async_say_stream import AsyncSayStream +from slack_bolt.context.set_status.async_set_status import AsyncSetStatus +from slack_bolt.context.set_suggested_prompts.async_set_suggested_prompts import AsyncSetSuggestedPrompts +from slack_bolt.middleware.async_middleware import AsyncMiddleware +from slack_bolt.request.async_request import AsyncBoltRequest +from slack_bolt.request.payload_utils import ( + is_app_home_opened_event, + is_assistant_event, + is_assistant_thread_context_changed_event, + is_assistant_thread_started_event, + is_im_message_event, + to_event, +) +from slack_bolt.response import BoltResponse + + +class AsyncAttachingConversationKwargs(AsyncMiddleware): + + thread_context_store: Optional[AsyncAssistantThreadContextStore] + + def __init__(self, thread_context_store: Optional[AsyncAssistantThreadContextStore] = None): + self.thread_context_store = thread_context_store + + async def async_process( + self, + *, + req: AsyncBoltRequest, + resp: BoltResponse, + next: Callable[[], Awaitable[BoltResponse]], + ) -> Optional[BoltResponse]: + event = to_event(req.body) + if event is None: + return await next() + if req.context.channel_id is None: + return await next() + + if is_assistant_event(req.body): + # TODO: eventually we might remove this assistant specific logic + assistant = AsyncAssistantUtilities( + payload=event, + context=req.context, + thread_context_store=self.thread_context_store, + ) + req.context["say"] = assistant.say + req.context["set_title"] = assistant.set_title + req.context["get_thread_context"] = assistant.get_thread_context + req.context["save_thread_context"] = assistant.save_thread_context + + if ( + is_im_message_event(req.body) + or is_assistant_thread_started_event(req.body) + or is_assistant_thread_context_changed_event(req.body) + or is_app_home_opened_event(req.body, tab="messages") + ): + req.context["set_suggested_prompts"] = AsyncSetSuggestedPrompts( + client=req.context.client, + channel_id=req.context.channel_id, + thread_ts=req.context.thread_ts, + ) + + # TODO: in the future we might want to introduce a "proper" extract_ts utility + thread_ts_or_ts = req.context.thread_ts or event.get("ts") + if thread_ts_or_ts: + req.context["set_status"] = AsyncSetStatus( + client=req.context.client, + channel_id=req.context.channel_id, + thread_ts=thread_ts_or_ts, + ) + req.context["say_stream"] = AsyncSayStream( + client=req.context.client, + channel=req.context.channel_id, + recipient_team_id=req.context.team_id or req.context.enterprise_id, + recipient_user_id=req.context.user_id, + thread_ts=thread_ts_or_ts, + ) + return await next() diff --git a/slack_bolt/middleware/attaching_conversation_kwargs/attaching_conversation_kwargs.py b/slack_bolt/middleware/attaching_conversation_kwargs/attaching_conversation_kwargs.py new file mode 100644 index 000000000..2d6ce7b01 --- /dev/null +++ b/slack_bolt/middleware/attaching_conversation_kwargs/attaching_conversation_kwargs.py @@ -0,0 +1,74 @@ +from typing import Optional, Callable + +from slack_bolt.context.assistant.thread_context_store.store import AssistantThreadContextStore +from slack_bolt.context.say_stream.say_stream import SayStream +from slack_bolt.context.set_status.set_status import SetStatus +from slack_bolt.context.set_suggested_prompts.set_suggested_prompts import SetSuggestedPrompts +from slack_bolt.middleware import Middleware +from slack_bolt.context.assistant.assistant_utilities import AssistantUtilities +from slack_bolt.request.payload_utils import ( + is_app_home_opened_event, + is_assistant_event, + is_assistant_thread_context_changed_event, + is_assistant_thread_started_event, + is_im_message_event, + to_event, +) +from slack_bolt.request.request import BoltRequest +from slack_bolt.response.response import BoltResponse + + +class AttachingConversationKwargs(Middleware): + + thread_context_store: Optional[AssistantThreadContextStore] + + def __init__(self, thread_context_store: Optional[AssistantThreadContextStore] = None): + self.thread_context_store = thread_context_store + + def process(self, *, req: BoltRequest, resp: BoltResponse, next: Callable[[], BoltResponse]) -> Optional[BoltResponse]: + event = to_event(req.body) + if event is None: + return next() + if req.context.channel_id is None: + return next() + + if is_assistant_event(req.body): + # TODO: eventually we might remove this assistant specific logic + assistant = AssistantUtilities( + payload=event, + context=req.context, + thread_context_store=self.thread_context_store, + ) + req.context["say"] = assistant.say + req.context["set_title"] = assistant.set_title + req.context["get_thread_context"] = assistant.get_thread_context + req.context["save_thread_context"] = assistant.save_thread_context + + if ( + is_im_message_event(req.body) + or is_assistant_thread_started_event(req.body) + or is_assistant_thread_context_changed_event(req.body) + or is_app_home_opened_event(req.body, tab="messages") + ): + req.context["set_suggested_prompts"] = SetSuggestedPrompts( + client=req.context.client, + channel_id=req.context.channel_id, + thread_ts=req.context.thread_ts, + ) + + # TODO: in the future we might want to introduce a "proper" extract_ts utility + thread_ts_or_ts = req.context.thread_ts or event.get("ts") + if thread_ts_or_ts: + req.context["set_status"] = SetStatus( + client=req.context.client, + channel_id=req.context.channel_id, + thread_ts=thread_ts_or_ts, + ) + req.context["say_stream"] = SayStream( + client=req.context.client, + channel=req.context.channel_id, + recipient_team_id=req.context.team_id or req.context.enterprise_id, + recipient_user_id=req.context.user_id, + thread_ts=thread_ts_or_ts, + ) + return next() diff --git a/slack_bolt/middleware/middleware_error_handler.py b/slack_bolt/middleware/middleware_error_handler.py index fe57e400c..5919414bb 100644 --- a/slack_bolt/middleware/middleware_error_handler.py +++ b/slack_bolt/middleware/middleware_error_handler.py @@ -48,9 +48,10 @@ def handle( ) returned_response = self.func(**kwargs) if returned_response is not None and isinstance(returned_response, BoltResponse): - response.status = returned_response.status # type: ignore[union-attr] - response.headers = returned_response.headers # type: ignore[union-attr] - response.body = returned_response.body # type: ignore[union-attr] + assert response is not None, "response must be provided when returning a BoltResponse from an error handler" + response.status = returned_response.status + response.headers = returned_response.headers + response.body = returned_response.body class DefaultMiddlewareErrorHandler(MiddlewareErrorHandler): diff --git a/slack_bolt/middleware/request_verification/async_request_verification.py b/slack_bolt/middleware/request_verification/async_request_verification.py index 68484bde0..3fb9e209b 100644 --- a/slack_bolt/middleware/request_verification/async_request_verification.py +++ b/slack_bolt/middleware/request_verification/async_request_verification.py @@ -10,7 +10,7 @@ class AsyncRequestVerification(RequestVerification, AsyncMiddleware): """Verifies an incoming request by checking the validity of `x-slack-signature`, `x-slack-request-timestamp`, and its body data. - Refer to https://api.slack.com/authentication/verifying-requests-from-slack for details. + Refer to https://docs.slack.dev/authentication/verifying-requests-from-slack/ for details. """ async def async_process( diff --git a/slack_bolt/middleware/request_verification/request_verification.py b/slack_bolt/middleware/request_verification/request_verification.py index 5662dcf08..af505bc84 100644 --- a/slack_bolt/middleware/request_verification/request_verification.py +++ b/slack_bolt/middleware/request_verification/request_verification.py @@ -1,5 +1,5 @@ from logging import Logger -from typing import Callable, Dict, Any, Optional +from typing import Any, Callable, Dict, Optional from slack_sdk.signature import SignatureVerifier @@ -14,15 +14,23 @@ def __init__(self, signing_secret: str, base_logger: Optional[Logger] = None): """Verifies an incoming request by checking the validity of `x-slack-signature`, `x-slack-request-timestamp`, and its body data. - Refer to https://api.slack.com/authentication/verifying-requests-from-slack for details. + Refer to https://docs.slack.dev/authentication/verifying-requests-from-slack/ for details. Args: signing_secret: The signing secret base_logger: The base logger """ - self.verifier = SignatureVerifier(signing_secret=signing_secret) + self._signing_secret = signing_secret + self._verifier: Optional[SignatureVerifier] = None self.logger = get_bolt_logger(RequestVerification, base_logger=base_logger) + @property + def verifier(self) -> SignatureVerifier: + # Defer initialization to avoid errors during start up + if self._verifier is None: + self._verifier = SignatureVerifier(signing_secret=self._signing_secret) + return self._verifier + def process( self, *, @@ -49,7 +57,7 @@ def process( @staticmethod def _can_skip(mode: str, body: Dict[str, Any]) -> bool: - return mode == "socket_mode" or (body is not None and body.get("ssl_check") == "1") + return mode == "socket_mode" @staticmethod def _build_error_response() -> BoltResponse: diff --git a/slack_bolt/middleware/ssl_check/ssl_check.py b/slack_bolt/middleware/ssl_check/ssl_check.py index d608e5c3d..88c5105ef 100644 --- a/slack_bolt/middleware/ssl_check/ssl_check.py +++ b/slack_bolt/middleware/ssl_check/ssl_check.py @@ -17,11 +17,11 @@ def __init__( base_logger: Optional[Logger] = None, ): """Handles `ssl_check` requests. - Refer to https://api.slack.com/interactivity/slash-commands for details. + Refer to https://docs.slack.dev/interactivity/implementing-slash-commands/ for details. Args: verification_token: The verification token to check - (optional as it's already deprecated - https://api.slack.com/authentication/verifying-requests-from-slack#verification_token_deprecation) + (optional as it's already deprecated - https://docs.slack.dev/authentication/verifying-requests-from-slack/#deprecation) base_logger: The base logger """ # noqa: E501 self.verification_token = verification_token diff --git a/slack_bolt/middleware/url_verification/url_verification.py b/slack_bolt/middleware/url_verification/url_verification.py index e59398fd7..7505c9c15 100644 --- a/slack_bolt/middleware/url_verification/url_verification.py +++ b/slack_bolt/middleware/url_verification/url_verification.py @@ -11,7 +11,7 @@ class UrlVerification(Middleware): def __init__(self, base_logger: Optional[Logger] = None): """Handles url_verification requests. - Refer to https://api.slack.com/events/url_verification for details. + Refer to https://docs.slack.dev/reference/events/url_verification/ for details. Args: base_logger: The base logger diff --git a/slack_bolt/oauth/__init__.py b/slack_bolt/oauth/__init__.py index c4f806698..0a5c3db07 100644 --- a/slack_bolt/oauth/__init__.py +++ b/slack_bolt/oauth/__init__.py @@ -1,6 +1,6 @@ """Slack OAuth flow support for building an app that is installable in any workspaces. -Refer to https://slack.dev/bolt-python/concepts#authenticating-oauth for details. +Refer to https://docs.slack.dev/tools/bolt-python/concepts/authenticating-oauth for details. """ # Don't add async module imports here diff --git a/slack_bolt/oauth/async_callback_options.py b/slack_bolt/oauth/async_callback_options.py index 88518d7e8..e1c2b2e4c 100644 --- a/slack_bolt/oauth/async_callback_options.py +++ b/slack_bolt/oauth/async_callback_options.py @@ -1,6 +1,6 @@ import logging from logging import Logger -from typing import Optional, Callable, Awaitable +from typing import Optional, Callable, Awaitable, TYPE_CHECKING from slack_sdk.oauth import RedirectUriPageRenderer, OAuthStateUtils from slack_sdk.oauth.installation_store import Installation @@ -9,6 +9,9 @@ from slack_bolt.request.async_request import AsyncBoltRequest from slack_bolt.response import BoltResponse +if TYPE_CHECKING: + from slack_bolt.oauth.async_oauth_settings import AsyncOAuthSettings + class AsyncSuccessArgs: def __init__( @@ -16,7 +19,7 @@ def __init__( *, request: AsyncBoltRequest, installation: Installation, - settings: "AsyncOAuthSettings", # type: ignore[name-defined] + settings: "AsyncOAuthSettings", default: "AsyncCallbackOptions", ): """The arguments for a success function. @@ -41,7 +44,7 @@ def __init__( reason: str, error: Optional[Exception] = None, suggested_status_code: int, - settings: "AsyncOAuthSettings", # type: ignore[name-defined] + settings: "AsyncOAuthSettings", default: "AsyncCallbackOptions", ): """The arguments for a failure function. diff --git a/slack_bolt/oauth/callback_options.py b/slack_bolt/oauth/callback_options.py index f267ed154..09584a365 100644 --- a/slack_bolt/oauth/callback_options.py +++ b/slack_bolt/oauth/callback_options.py @@ -1,6 +1,6 @@ import logging from logging import Logger -from typing import Optional, Callable +from typing import Optional, Callable, TYPE_CHECKING from slack_sdk.oauth import RedirectUriPageRenderer, OAuthStateUtils from slack_sdk.oauth.installation_store import Installation @@ -9,6 +9,9 @@ from slack_bolt.request import BoltRequest from slack_bolt.response import BoltResponse +if TYPE_CHECKING: + from slack_bolt.oauth.oauth_settings import OAuthSettings + class SuccessArgs: def __init__( @@ -16,7 +19,7 @@ def __init__( *, request: BoltRequest, installation: Installation, - settings: "OAuthSettings", # type: ignore[name-defined] + settings: "OAuthSettings", default: "CallbackOptions", ): """The arguments for a success function. @@ -41,7 +44,7 @@ def __init__( reason: str, error: Optional[Exception] = None, suggested_status_code: int, - settings: "OAuthSettings", # type: ignore[name-defined] + settings: "OAuthSettings", default: "CallbackOptions", ): """The arguments for a failure function. diff --git a/slack_bolt/request/__init__.py b/slack_bolt/request/__init__.py index ee8b435a7..8610b6019 100644 --- a/slack_bolt/request/__init__.py +++ b/slack_bolt/request/__init__.py @@ -1,6 +1,6 @@ """Incoming request from Slack through either HTTP request or Socket Mode connection. -Refer to https://api.slack.com/apis/connections for the two types of connections. +Refer to https://docs.slack.dev/apis/events-api/ for the two types of connections. This interface encapsulates the difference between the two. """ diff --git a/slack_bolt/request/internals.py b/slack_bolt/request/internals.py index 014a8134a..e0863a713 100644 --- a/slack_bolt/request/internals.py +++ b/slack_bolt/request/internals.py @@ -3,7 +3,6 @@ from urllib.parse import parse_qsl, parse_qs from slack_bolt.context import BoltContext -from slack_bolt.request.payload_utils import is_assistant_event def parse_query(query: Optional[Union[str, Dict[str, str], Dict[str, Sequence[str]]]]) -> Dict[str, Sequence[str]]: @@ -66,10 +65,10 @@ def extract_enterprise_id(payload: Dict[str, Any]) -> Optional[str]: return extract_enterprise_id(payload["authorizations"][0]) if "enterprise_id" in payload: return payload.get("enterprise_id") - if payload.get("team") is not None and "enterprise_id" in payload["team"]: + if isinstance(payload.get("team"), dict) and "enterprise_id" in payload["team"]: # In the case where the type is view_submission return payload["team"].get("enterprise_id") - if payload.get("event") is not None: + if isinstance(payload.get("event"), dict): return extract_enterprise_id(payload["event"]) return None @@ -89,13 +88,13 @@ def extract_actor_enterprise_id(payload: Dict[str, Any]) -> Optional[str]: def extract_team_id(payload: Dict[str, Any]) -> Optional[str]: - app_installed_team_id = payload.get("view", {}).get("app_installed_team_id") - if app_installed_team_id is not None: + view = payload.get("view") + if isinstance(view, dict) and view.get("app_installed_team_id") is not None: # view_submission payloads can have `view.app_installed_team_id` when a modal view that was opened # in a different workspace via some operations inside a Slack Connect channel. # Note that the same for enterprise_id does not exist. When you need to know the enterprise_id as well, # you have to run some query toward your InstallationStore to know the org where the team_id belongs to. - return app_installed_team_id + return view["app_installed_team_id"] if payload.get("team") is not None: # With org-wide installations, payload.team in interactivity payloads can be None # You need to extract either payload.user.team_id or payload.view.team_id as below @@ -110,12 +109,12 @@ def extract_team_id(payload: Dict[str, Any]) -> Optional[str]: return extract_team_id(payload["authorizations"][0]) if "team_id" in payload: return payload.get("team_id") - if payload.get("event") is not None: + if isinstance(payload.get("event"), dict): return extract_team_id(payload["event"]) - if payload.get("user") is not None: - return payload["user"]["team_id"] - if payload.get("view") is not None: - return payload.get("view", {})["team_id"] + if isinstance(payload.get("user"), dict): + return payload["user"].get("team_id") + if isinstance(payload.get("view"), dict): + return payload["view"].get("team_id") return None @@ -170,12 +169,12 @@ def extract_user_id(payload: Dict[str, Any]) -> Optional[str]: return user.get("id") if "user_id" in payload: return payload.get("user_id") - if payload.get("event") is not None: + if isinstance(payload.get("event"), dict): return extract_user_id(payload["event"]) - if payload.get("message") is not None: + if isinstance(payload.get("message"), dict): # message_changed: body["event"]["message"] return extract_user_id(payload["message"]) - if payload.get("previous_message") is not None: + if isinstance(payload.get("previous_message"), dict): # message_deleted: body["event"]["previous_message"] return extract_user_id(payload["previous_message"]) return None @@ -203,51 +202,38 @@ def extract_channel_id(payload: Dict[str, Any]) -> Optional[str]: return channel.get("id") if "channel_id" in payload: return payload.get("channel_id") - if payload.get("event") is not None: + if isinstance(payload.get("event"), dict): return extract_channel_id(payload["event"]) - if payload.get("item") is not None: + if isinstance(payload.get("item"), dict): # reaction_added: body["event"]["item"] return extract_channel_id(payload["item"]) - if payload.get("assistant_thread") is not None: + if isinstance(payload.get("assistant_thread"), dict): # assistant_thread_started return extract_channel_id(payload["assistant_thread"]) return None def extract_thread_ts(payload: Dict[str, Any]) -> Optional[str]: - # This utility initially supports only the use cases for AI assistants, but it may be fine to add more patterns. - # That said, note that thread_ts is always required for assistant threads, but it's not for channels. - # Thus, blindly setting this thread_ts to say utility can break existing apps' behaviors. - if is_assistant_event(payload): - event = payload["event"] - if ( - event.get("assistant_thread") is not None - and event["assistant_thread"].get("channel_id") is not None - and event["assistant_thread"].get("thread_ts") is not None - ): - # assistant_thread_started, assistant_thread_context_changed - # "assistant_thread" property can exist for message event without channel_id and thread_ts - # Thus, the above if check verifies these properties exist - return event["assistant_thread"]["thread_ts"] - elif event.get("channel") is not None: - if event.get("thread_ts") is not None: - # message in an assistant thread - return event["thread_ts"] - elif event.get("message", {}).get("thread_ts") is not None: - # message_changed - return event["message"]["thread_ts"] - elif event.get("previous_message", {}).get("thread_ts") is not None: - # message_deleted - return event["previous_message"]["thread_ts"] + thread_ts = payload.get("thread_ts") + if thread_ts is not None: + return thread_ts + if isinstance(payload.get("event"), dict): + return extract_thread_ts(payload["event"]) + if isinstance(payload.get("assistant_thread"), dict): + return extract_thread_ts(payload["assistant_thread"]) + if isinstance(payload.get("message"), dict): + return extract_thread_ts(payload["message"]) + if isinstance(payload.get("previous_message"), dict): + return extract_thread_ts(payload["previous_message"]) return None def extract_function_execution_id(payload: Dict[str, Any]) -> Optional[str]: if payload.get("function_execution_id") is not None: return payload.get("function_execution_id") - if payload.get("event") is not None: + if isinstance(payload.get("event"), dict): return extract_function_execution_id(payload["event"]) - if payload.get("function_data") is not None: + if isinstance(payload.get("function_data"), dict): return payload["function_data"].get("execution_id") return None @@ -255,15 +241,15 @@ def extract_function_execution_id(payload: Dict[str, Any]) -> Optional[str]: def extract_function_bot_access_token(payload: Dict[str, Any]) -> Optional[str]: if payload.get("bot_access_token") is not None: return payload.get("bot_access_token") - if payload.get("event") is not None: + if isinstance(payload.get("event"), dict): return payload["event"].get("bot_access_token") return None def extract_function_inputs(payload: Dict[str, Any]) -> Optional[Dict[str, Any]]: - if payload.get("event") is not None: + if isinstance(payload.get("event"), dict): return payload["event"].get("inputs") - if payload.get("function_data") is not None: + if isinstance(payload.get("function_data"), dict): return payload["function_data"].get("inputs") return None diff --git a/slack_bolt/request/payload_utils.py b/slack_bolt/request/payload_utils.py index c1016c65d..b74238461 100644 --- a/slack_bolt/request/payload_utils.py +++ b/slack_bolt/request/payload_utils.py @@ -1,6 +1,5 @@ from typing import Dict, Any, Optional - # ------------------------------------------ # Public Utilities # ------------------------------------------ @@ -15,7 +14,7 @@ def to_event(body: Dict[str, Any]) -> Optional[Dict[str, Any]]: def to_message(body: Dict[str, Any]) -> Optional[Dict[str, Any]]: - if is_event(body) and body["event"]["type"] == "message": + if is_message_event(body): return to_event(body) return None @@ -32,6 +31,25 @@ def is_workflow_step_execute(body: Dict[str, Any]) -> bool: return is_event(body) and body["event"]["type"] == "workflow_step_execute" and "workflow_step" in body["event"] +def is_message_event(body: Dict[str, Any]) -> bool: + if is_event(body): + return body["event"]["type"] == "message" + return False + + +def is_any_im_message_event(body: Dict[str, Any]) -> bool: + if is_message_event(body): + # Any message event with no subtype or any subtype (message_changed, message_deleted, etc.) + return body["event"].get("channel_type") == "im" + return False + + +def is_im_message_event(body: Dict[str, Any]) -> bool: + if is_any_im_message_event(body): + return body["event"].get("subtype") in (None, "file_share") + return False + + def is_assistant_event(body: Dict[str, Any]) -> bool: return is_event(body) and ( is_assistant_thread_started_event(body) @@ -53,28 +71,24 @@ def is_assistant_thread_context_changed_event(body: Dict[str, Any]) -> bool: return False -def is_message_event_in_assistant_thread(body: Dict[str, Any]) -> bool: - if is_event(body): - return body["event"]["type"] == "message" and body["event"].get("channel_type") == "im" +def is_app_home_opened_event(body: Dict[str, Any], tab: Optional[str] = None) -> bool: + if is_event(body) and body["event"]["type"] == "app_home_opened": + if tab is not None: + return body["event"].get("tab") == tab + return True return False def is_user_message_event_in_assistant_thread(body: Dict[str, Any]) -> bool: - if is_event(body): - return ( - is_message_event_in_assistant_thread(body) - and body["event"].get("subtype") in (None, "file_share") - and body["event"].get("thread_ts") is not None - and body["event"].get("bot_id") is None - ) + if is_im_message_event(body): + return body["event"].get("thread_ts") is not None and body["event"].get("bot_id") is None return False def is_bot_message_event_in_assistant_thread(body: Dict[str, Any]) -> bool: - if is_event(body): + if is_any_im_message_event(body): return ( - is_message_event_in_assistant_thread(body) - and body["event"].get("subtype") is None + body["event"].get("subtype") is None and body["event"].get("thread_ts") is not None and body["event"].get("bot_id") is not None ) @@ -83,14 +97,10 @@ def is_bot_message_event_in_assistant_thread(body: Dict[str, Any]) -> bool: def is_other_message_sub_event_in_assistant_thread(body: Dict[str, Any]) -> bool: # message_changed, message_deleted etc. - if is_event(body): - return ( - is_message_event_in_assistant_thread(body) - and not is_user_message_event_in_assistant_thread(body) - and ( - _is_other_message_sub_event(body["event"].get("message")) - or _is_other_message_sub_event(body["event"].get("previous_message")) - ) + if is_any_im_message_event(body): + return not is_user_message_event_in_assistant_thread(body) and ( + _is_other_message_sub_event(body["event"].get("message")) + or _is_other_message_sub_event(body["event"].get("previous_message")) ) return False diff --git a/slack_bolt/response/__init__.py b/slack_bolt/response/__init__.py index 373acccf2..c390b2d8e 100644 --- a/slack_bolt/response/__init__.py +++ b/slack_bolt/response/__init__.py @@ -3,7 +3,7 @@ In Socket Mode, the response data can be transformed to a WebSocket message. In the HTTP endpoint mode, the response data becomes an HTTP response data. -Refer to https://api.slack.com/apis/connections for the two types of connections. +Refer to https://docs.slack.dev/apis/events-api/ for the two types of connections. """ from .response import BoltResponse diff --git a/slack_bolt/util/utils.py b/slack_bolt/util/utils.py index 0abdcfcbd..9ee313821 100644 --- a/slack_bolt/util/utils.py +++ b/slack_bolt/util/utils.py @@ -32,19 +32,7 @@ def convert_to_dict(obj: Union[Dict, JsonObject]) -> Dict: def create_copy(original: Any) -> Any: - if sys.version_info.major == 3 and sys.version_info.minor <= 6: - # NOTE: Unfortunately, copy.deepcopy doesn't work in Python 3.6.5. - # -------------------- - # > rv = reductor(4) - # E TypeError: can't pickle _thread.RLock objects - # ../../.pyenv/versions/3.6.10/lib/python3.6/copy.py:169: TypeError - # -------------------- - # As a workaround, this operation uses shallow copies in Python 3.6. - # If your code modifies the shared data in threads / async functions, race conditions may arise. - # Please consider upgrading Python major version to 3.7+ if you encounter some issues due to this. - return copy.copy(original) - else: - return copy.deepcopy(original) + return copy.deepcopy(original) def get_boot_message(development_server: bool = False) -> str: diff --git a/slack_bolt/version.py b/slack_bolt/version.py index 531baf7bd..2c08c0adb 100644 --- a/slack_bolt/version.py +++ b/slack_bolt/version.py @@ -1,3 +1,3 @@ """Check the latest version at https://pypi.org/project/slack-bolt/""" -__version__ = "1.22.0" +__version__ = "1.30.0" diff --git a/slack_bolt/workflows/__init__.py b/slack_bolt/workflows/__init__.py index 97e6ec765..c0f6d96b7 100644 --- a/slack_bolt/workflows/__init__.py +++ b/slack_bolt/workflows/__init__.py @@ -6,5 +6,5 @@ * `slack_bolt.workflows.step.utilities` * `slack_bolt.workflows.step.async_step` (if you use asyncio-based `AsyncApp`) -Refer to https://api.slack.com/workflows/steps for details. +Refer to https://docs.slack.dev/legacy/legacy-steps-from-apps/ for details. """ diff --git a/slack_bolt/workflows/step/async_step.py b/slack_bolt/workflows/step/async_step.py index 250d2e900..7fa0ed858 100644 --- a/slack_bolt/workflows/step/async_step.py +++ b/slack_bolt/workflows/step/async_step.py @@ -29,7 +29,7 @@ class AsyncWorkflowStepBuilder: """Steps from apps - Refer to https://api.slack.com/workflows/steps for details. + Refer to https://docs.slack.dev/legacy/legacy-steps-from-apps/ for details. """ callback_id: Union[str, Pattern] @@ -47,7 +47,7 @@ def __init__( """ Deprecated: Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://api.slack.com/automation/functions/custom-bolt + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ This builder is supposed to be used as decorator. @@ -89,7 +89,7 @@ def edit( """ Deprecated: Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://api.slack.com/automation/functions/custom-bolt + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ Registers a new edit listener with details. @@ -142,7 +142,7 @@ def save( """ Deprecated: Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://api.slack.com/automation/functions/custom-bolt + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ Registers a new save listener with details. @@ -195,7 +195,7 @@ def execute( """ Deprecated: Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://api.slack.com/automation/functions/custom-bolt + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ Registers a new execute listener with details. @@ -242,7 +242,7 @@ def build(self, base_logger: Optional[Logger] = None) -> "AsyncWorkflowStep": """ Deprecated: Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://api.slack.com/automation/functions/custom-bolt + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ Constructs a WorkflowStep object. This method may raise an exception if the builder doesn't have enough configurations to build the object. @@ -340,7 +340,7 @@ def __init__( """ Deprecated: Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://api.slack.com/automation/functions/custom-bolt + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ Args: callback_id: The callback_id for this step from app @@ -386,7 +386,7 @@ def builder( """ Deprecated: Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://api.slack.com/automation/functions/custom-bolt + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ """ return AsyncWorkflowStepBuilder(callback_id, base_logger=base_logger) diff --git a/slack_bolt/workflows/step/step.py b/slack_bolt/workflows/step/step.py index 7cdbb913c..4fca25717 100644 --- a/slack_bolt/workflows/step/step.py +++ b/slack_bolt/workflows/step/step.py @@ -24,7 +24,7 @@ class WorkflowStepBuilder: """Steps from apps - Refer to https://api.slack.com/workflows/steps for details. + Refer to https://docs.slack.dev/legacy/legacy-steps-from-apps/ for details. """ callback_id: Union[str, Pattern] @@ -42,7 +42,7 @@ def __init__( """ Deprecated: Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://api.slack.com/automation/functions/custom-bolt + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ This builder is supposed to be used as decorator. @@ -84,7 +84,7 @@ def edit( """ Deprecated: Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://api.slack.com/automation/functions/custom-bolt + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ Registers a new edit listener with details. @@ -138,7 +138,7 @@ def save( """ Deprecated: Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://api.slack.com/automation/functions/custom-bolt + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ Registers a new save listener with details. @@ -191,7 +191,7 @@ def execute( """ Deprecated: Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://api.slack.com/automation/functions/custom-bolt + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ Registers a new execute listener with details. @@ -238,7 +238,7 @@ def build(self, base_logger: Optional[Logger] = None) -> "WorkflowStep": """ Deprecated: Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://api.slack.com/automation/functions/custom-bolt + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ Constructs a WorkflowStep object. This method may raise an exception if the builder doesn't have enough configurations to build the object. @@ -351,7 +351,7 @@ def __init__( """ Deprecated: Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://api.slack.com/automation/functions/custom-bolt + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ Args: callback_id: The callback_id for this step from app @@ -393,7 +393,7 @@ def builder(cls, callback_id: Union[str, Pattern], base_logger: Optional[Logger] """ Deprecated: Steps from apps for legacy workflows are now deprecated. - Use new custom steps: https://api.slack.com/automation/functions/custom-bolt + Use new custom steps: https://docs.slack.dev/workflows/workflow-steps/ """ return WorkflowStepBuilder( callback_id, diff --git a/slack_bolt/workflows/step/utilities/async_configure.py b/slack_bolt/workflows/step/utilities/async_configure.py index 721d5049c..5b9a7f9ae 100644 --- a/slack_bolt/workflows/step/utilities/async_configure.py +++ b/slack_bolt/workflows/step/utilities/async_configure.py @@ -32,7 +32,7 @@ async def edit(ack, step, configure): ) app.step(ws) - Refer to https://api.slack.com/workflows/steps for details. + Refer to https://docs.slack.dev/legacy/legacy-steps-from-apps/ for details. """ def __init__(self, *, callback_id: str, client: AsyncWebClient, body: dict): diff --git a/slack_bolt/workflows/step/utilities/configure.py b/slack_bolt/workflows/step/utilities/configure.py index d44c8d0da..1280be8f7 100644 --- a/slack_bolt/workflows/step/utilities/configure.py +++ b/slack_bolt/workflows/step/utilities/configure.py @@ -32,7 +32,7 @@ def edit(ack, step, configure): ) app.step(ws) - Refer to https://api.slack.com/workflows/steps for details. + Refer to https://docs.slack.dev/legacy/legacy-steps-from-apps/ for details. """ def __init__(self, *, callback_id: str, client: WebClient, body: dict): diff --git a/tests/adapter_tests/asgi/test_asgi_http.py b/tests/adapter_tests/asgi/test_asgi_http.py index 72b6434bf..f9f106461 100644 --- a/tests/adapter_tests/asgi/test_asgi_http.py +++ b/tests/adapter_tests/asgi/test_asgi_http.py @@ -223,6 +223,59 @@ async def test_url_verification(self): assert response.headers.get("content-type") == "application/json;charset=utf-8" assert_auth_test_count(self, 1) + @pytest.mark.asyncio + async def test_content_length_multibyte_body(self): + app = App( + client=self.web_client, + signing_secret=self.signing_secret, + ) + + def command_handler(ack): + ack(text="Hello ☃") # snowman is 3 bytes in UTF-8 + + app.command("/hello-world")(command_handler) + + body = ( + "token=verification_token" + "&team_id=T111" + "&team_domain=test-domain" + "&channel_id=C111" + "&channel_name=random" + "&user_id=W111" + "&user_name=primary-owner" + "&command=%2Fhello-world" + "&text=Hi" + "&enterprise_id=E111" + "&enterprise_name=Org+Name" + "&response_url=https%3A%2F%2Fhooks.slack.com%2Fcommands%2FT111%2F111%2Fxxxxx" + "&trigger_id=111.111.xxx" + ) + + headers = self.build_raw_headers(str(int(time())), body) + + asgi_server = AsgiTestServer(SlackRequestHandler(app)) + response = await asgi_server.http("POST", headers, body) + + assert response.status_code == 200 + content_length = int(response.headers.get("content-length")) + actual_bytes = len(response.body.encode("utf-8")) + assert content_length == actual_bytes + + @pytest.mark.asyncio + async def test_multi_value_headers(self): + from slack_bolt.adapter.asgi.http_response import AsgiHttpResponse + + headers = { + "set-cookie": ["cookie1=value1; Path=/", "cookie2=value2; Path=/"], + "content-type": ["text/html; charset=utf-8"], + } + response = AsgiHttpResponse(status=200, headers=headers, body="OK") + + set_cookie_headers = [(name, value) for name, value in response.raw_headers if name == b"set-cookie"] + assert len(set_cookie_headers) == 2 + assert set_cookie_headers[0] == (b"set-cookie", b"cookie1=value1; Path=/") + assert set_cookie_headers[1] == (b"set-cookie", b"cookie2=value2; Path=/") + @pytest.mark.asyncio async def test_unsupported_method(self): app = App( diff --git a/tests/adapter_tests/asgi/test_asgi_lifespan.py b/tests/adapter_tests/asgi/test_asgi_lifespan.py index 488a990f1..5b48f6b8a 100644 --- a/tests/adapter_tests/asgi/test_asgi_lifespan.py +++ b/tests/adapter_tests/asgi/test_asgi_lifespan.py @@ -1,5 +1,6 @@ import pytest +from asgiref.testing import ApplicationCommunicator from slack_sdk.signature import SignatureVerifier from slack_sdk.web import WebClient @@ -59,6 +60,24 @@ async def test_shutdown(self): assert response.type == "lifespan.shutdown.complete" assert response.message == "" + @pytest.mark.asyncio + async def test_full_lifespan_cycle(self): + app = App( + client=self.web_client, + signing_secret=self.signing_secret, + ) + + scope = {"type": "lifespan", "asgi": {"version": "3.0", "spec_version": "2.3"}} + communicator = ApplicationCommunicator(SlackRequestHandler(app), scope) + + await communicator.send_input({"type": "lifespan.startup"}) + startup_response = await communicator.receive_output(timeout=1) + assert startup_response["type"] == "lifespan.startup.complete" + + await communicator.send_input({"type": "lifespan.shutdown"}) + shutdown_response = await communicator.receive_output(timeout=1) + assert shutdown_response["type"] == "lifespan.shutdown.complete" + @pytest.mark.asyncio async def test_failed_event(self): app = App( diff --git a/tests/adapter_tests/django/conftest.py b/tests/adapter_tests/django/conftest.py new file mode 100644 index 000000000..b2697fe2b --- /dev/null +++ b/tests/adapter_tests/django/conftest.py @@ -0,0 +1,6 @@ +import os + +import django + +os.environ["DJANGO_SETTINGS_MODULE"] = "tests.adapter_tests.django.test_django_settings" +django.setup() diff --git a/tests/adapter_tests/django/test_django.py b/tests/adapter_tests/django/test_django.py index cb14f966d..f31a46411 100644 --- a/tests/adapter_tests/django/test_django.py +++ b/tests/adapter_tests/django/test_django.py @@ -1,5 +1,4 @@ import json -import os from time import time from urllib.parse import quote @@ -29,7 +28,6 @@ class TestDjango(TestCase): base_url=mock_api_server_base_url, ) - os.environ["DJANGO_SETTINGS_MODULE"] = "tests.adapter_tests.django.test_django_settings" rf = RequestFactory() def setUp(self): diff --git a/tests/adapter_tests/falcon/test_falcon.py b/tests/adapter_tests/falcon/test_falcon.py index d7841a24a..4a2f34d36 100644 --- a/tests/adapter_tests/falcon/test_falcon.py +++ b/tests/adapter_tests/falcon/test_falcon.py @@ -205,3 +205,17 @@ def test_oauth(self): response = client.simulate_get("/slack/install") assert response.status_code == 200 assert "https://slack.com/oauth/v2/authorize?state=" in response.text + + def test_get_no_oauth(self): + app = App( + client=self.web_client, + signing_secret=self.signing_secret, + ) + api = new_falcon_app() + resource = SlackAppResource(app) + api.add_route("/slack/events", resource) + + client = testing.TestClient(api) + response = client.simulate_get("/slack/events") + assert response.status_code == 404 + assert "The page is not found" in response.text diff --git a/tests/adapter_tests/socket_mode/mock_socket_mode_server.py b/tests/adapter_tests/socket_mode/mock_socket_mode_server.py index 997657368..f59999192 100644 --- a/tests/adapter_tests/socket_mode/mock_socket_mode_server.py +++ b/tests/adapter_tests/socket_mode/mock_socket_mode_server.py @@ -28,8 +28,8 @@ def reset_server_state(): async def health(request: web.Request): wr = web.Response() - await wr.prepare(request) wr.set_status(200) + await wr.prepare(request) return wr async def link(request: web.Request): diff --git a/tests/adapter_tests/socket_mode/test_interactions_builtin.py b/tests/adapter_tests/socket_mode/test_interactions_builtin.py index 2ecd52554..aec04d938 100644 --- a/tests/adapter_tests/socket_mode/test_interactions_builtin.py +++ b/tests/adapter_tests/socket_mode/test_interactions_builtin.py @@ -36,7 +36,7 @@ def teardown_method(self): def test_interactions(self): app = App(client=self.web_client) - result = {"shortcut": False, "command": False} + result = {"shortcut": False, "command": False, "message": False} @app.shortcut("do-something") def shortcut_handler(ack): @@ -48,6 +48,13 @@ def command_handler(ack): result["command"] = True ack() + @app.message("<@W111>") + def message_handler(ack, req): + result["message"] = req.headers.get("x-slack-retry-num") == ["1"] and req.headers.get( + "x-slack-retry-reason" + ) == ["timeout"] + ack() + handler = SocketModeHandler( app_token="xapp-A111-222-xyz", app=app, @@ -66,5 +73,6 @@ def command_handler(ack): time.sleep(2) assert result["shortcut"] is True assert result["command"] is True + assert result["message"] is True finally: handler.client.close() diff --git a/tests/adapter_tests/socket_mode/test_interactions_websocket_client.py b/tests/adapter_tests/socket_mode/test_interactions_websocket_client.py index ccaa89d3e..90e027f9d 100644 --- a/tests/adapter_tests/socket_mode/test_interactions_websocket_client.py +++ b/tests/adapter_tests/socket_mode/test_interactions_websocket_client.py @@ -37,7 +37,7 @@ def test_interactions(self): app = App(client=self.web_client) - result = {"shortcut": False, "command": False} + result = {"shortcut": False, "command": False, "message": False} @app.shortcut("do-something") def shortcut_handler(ack): @@ -49,6 +49,13 @@ def command_handler(ack): result["command"] = True ack() + @app.message("<@W111>") + def message_handler(ack, req): + result["message"] = req.headers.get("x-slack-retry-num") == ["1"] and req.headers.get( + "x-slack-retry-reason" + ) == ["timeout"] + ack() + handler = SocketModeHandler( app_token="xapp-A111-222-xyz", app=app, @@ -67,5 +74,6 @@ def command_handler(ack): time.sleep(2) assert result["shortcut"] is True assert result["command"] is True + assert result["message"] is True finally: handler.client.close() diff --git a/tests/adapter_tests/socket_mode/test_internals.py b/tests/adapter_tests/socket_mode/test_internals.py new file mode 100644 index 000000000..fede30b48 --- /dev/null +++ b/tests/adapter_tests/socket_mode/test_internals.py @@ -0,0 +1,20 @@ +from slack_sdk.socket_mode.request import SocketModeRequest + +from slack_bolt.adapter.socket_mode.internals import build_headers, run_bolt_app + + +class TestSocketModeInternals: + def test_build_retry_headers_without_retry(self): + req = SocketModeRequest(type="events_api", envelope_id="e1", payload={"type": "event_callback"}) + assert build_headers(req) is None + + def test_build_retry_headers_with_retry(self): + req = SocketModeRequest( + type="events_api", + envelope_id="e1", + payload={"type": "event_callback"}, + retry_attempt=2, + retry_reason="http_timeout", + ) + headers = build_headers(req) + assert headers == {"x-slack-retry-num": "2", "x-slack-retry-reason": "http_timeout"} diff --git a/tests/adapter_tests/starlette/test_fastapi.py b/tests/adapter_tests/starlette/test_fastapi.py index aa16ffa69..f91b9897e 100644 --- a/tests/adapter_tests/starlette/test_fastapi.py +++ b/tests/adapter_tests/starlette/test_fastapi.py @@ -94,7 +94,7 @@ async def endpoint(req: Request): client = TestClient(api) response = client.post( "/slack/events", - data=body, + content=body, headers=self.build_headers(timestamp, body), ) assert response.status_code == 200 @@ -138,7 +138,7 @@ async def endpoint(req: Request): client = TestClient(api) response = client.post( "/slack/events", - data=body, + content=body, headers=self.build_headers(timestamp, body), ) assert response.status_code == 200 @@ -182,7 +182,7 @@ async def endpoint(req: Request): client = TestClient(api) response = client.post( "/slack/events", - data=body, + content=body, headers=self.build_headers(timestamp, body), ) assert response.status_code == 200 @@ -206,7 +206,8 @@ async def endpoint(req: Request): return await app_handler.handle(req) client = TestClient(api) - response = client.get("/slack/install", allow_redirects=False) + client.follow_redirects = False + response = client.get("/slack/install") assert response.status_code == 200 assert response.headers.get("content-type") == "text/html; charset=utf-8" assert "https://slack.com/oauth/v2/authorize?state=" in response.text @@ -253,7 +254,7 @@ async def endpoint(req: Request, foo: str = Depends(get_foo)): client = TestClient(api) response = client.post( "/slack/events", - data=body, + content=body, headers=self.build_headers(timestamp, body), ) assert response.status_code == 200 diff --git a/tests/adapter_tests/starlette/test_starlette.py b/tests/adapter_tests/starlette/test_starlette.py index 66bf163cb..18066a9d2 100644 --- a/tests/adapter_tests/starlette/test_starlette.py +++ b/tests/adapter_tests/starlette/test_starlette.py @@ -97,7 +97,7 @@ async def endpoint(req: Request): client = TestClient(api) response = client.post( "/slack/events", - data=body, + content=body, headers=self.build_headers(timestamp, body), ) assert response.status_code == 200 @@ -143,7 +143,7 @@ async def endpoint(req: Request): client = TestClient(api) response = client.post( "/slack/events", - data=body, + content=body, headers=self.build_headers(timestamp, body), ) assert response.status_code == 200 @@ -189,7 +189,7 @@ async def endpoint(req: Request): client = TestClient(api) response = client.post( "/slack/events", - data=body, + content=body, headers=self.build_headers(timestamp, body), ) assert response.status_code == 200 @@ -215,7 +215,8 @@ async def endpoint(req: Request): routes=[Route("/slack/install", endpoint=endpoint, methods=["GET"])], ) client = TestClient(api) - response = client.get("/slack/install", allow_redirects=False) + client.follow_redirects = False + response = client.get("/slack/install") assert response.status_code == 200 assert response.headers.get("content-type") == "text/html; charset=utf-8" assert "https://slack.com/oauth/v2/authorize?state=" in response.text diff --git a/tests/adapter_tests/wsgi/test_wsgi_http.py b/tests/adapter_tests/wsgi/test_wsgi_http.py index 63ac62627..c7fca2e25 100644 --- a/tests/adapter_tests/wsgi/test_wsgi_http.py +++ b/tests/adapter_tests/wsgi/test_wsgi_http.py @@ -89,6 +89,47 @@ def command_handler(ack): assert response.headers.get("content-type") == "text/plain;charset=utf-8" assert_auth_test_count(self, 1) + def test_ssl_check_param_does_not_bypass_request_verification(self): + app = App( + client=self.web_client, + signing_secret=self.signing_secret, + ssl_check_enabled=False, + ) + command_called = False + + def command_handler(ack): + nonlocal command_called + command_called = True + ack() + + app.command("/hello-world")(command_handler) + + body = ( + "token=verification_token" + "&team_id=T111" + "&team_domain=test-domain" + "&channel_id=C111" + "&channel_name=random" + "&user_id=W111" + "&user_name=primary-owner" + "&command=%2Fhello-world" + "&text=Hi" + "&enterprise_id=E111" + "&enterprise_name=Org+Name" + "&response_url=https%3A%2F%2Fhooks.slack.com%2Fcommands%2FT111%2F111%2Fxxxxx" + "&trigger_id=111.111.xxx" + "&ssl_check=1" + ) + headers = self.build_raw_headers("0", body) + headers["x-slack-signature"] = "v0=invalid" + + wsgi_server = WsgiTestServer(SlackRequestHandler(app)) + response = wsgi_server.http(method="POST", headers=headers, body=body) + + assert response.status == "401 Unauthorized" + assert response.body == """{"error": "invalid request"}""" + assert command_called is False + def test_events(self): app = App( client=self.web_client, diff --git a/tests/adapter_tests_async/socket_mode/test_async_aiohttp.py b/tests/adapter_tests_async/socket_mode/test_async_aiohttp.py index 1720f7ec6..812806a8c 100644 --- a/tests/adapter_tests_async/socket_mode/test_async_aiohttp.py +++ b/tests/adapter_tests_async/socket_mode/test_async_aiohttp.py @@ -9,7 +9,7 @@ setup_mock_web_api_server, cleanup_mock_web_api_server, ) -from tests.utils import remove_os_env_temporarily, restore_os_env, get_event_loop +from tests.utils import remove_os_env_temporarily, restore_os_env from ...adapter_tests.socket_mode.mock_socket_mode_server import ( start_socket_mode_server, stop_socket_mode_server, @@ -24,16 +24,14 @@ class TestSocketModeAiohttp: base_url=mock_api_server_base_url, ) - @pytest.fixture - def event_loop(self): + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server(self) try: - setup_mock_web_api_server(self) - loop = get_event_loop() - yield loop - loop.close() - cleanup_mock_web_api_server(self) + yield # run the test here finally: + cleanup_mock_web_api_server(self) restore_os_env(old_os_env) @pytest.mark.asyncio @@ -42,7 +40,7 @@ async def test_events(self): app = AsyncApp(client=self.web_client) - result = {"shortcut": False, "command": False} + result = {"shortcut": False, "command": False, "message": False} @app.shortcut("do-something") async def shortcut_handler(ack): @@ -54,6 +52,13 @@ async def command_handler(ack): result["command"] = True await ack() + @app.message("<@W111>") + async def message_handler(ack, req): + result["message"] = req.headers.get("x-slack-retry-num") == ["1"] and req.headers.get( + "x-slack-retry-reason" + ) == ["timeout"] + await ack() + handler = AsyncSocketModeHandler( app_token="xapp-A111-222-xyz", app=app, @@ -69,6 +74,7 @@ async def command_handler(ack): await asyncio.sleep(2) assert result["shortcut"] is True assert result["command"] is True + assert result["message"] is True finally: await handler.client.close() stop_socket_mode_server(self) diff --git a/tests/adapter_tests_async/socket_mode/test_async_lazy_listeners.py b/tests/adapter_tests_async/socket_mode/test_async_lazy_listeners.py index 11268c6a1..9144bd239 100644 --- a/tests/adapter_tests_async/socket_mode/test_async_lazy_listeners.py +++ b/tests/adapter_tests_async/socket_mode/test_async_lazy_listeners.py @@ -9,7 +9,7 @@ setup_mock_web_api_server, cleanup_mock_web_api_server, ) -from tests.utils import remove_os_env_temporarily, restore_os_env, get_event_loop +from tests.utils import remove_os_env_temporarily, restore_os_env from ...adapter_tests.socket_mode.mock_socket_mode_server import ( start_socket_mode_server, stop_socket_mode_server, @@ -24,16 +24,14 @@ class TestSocketModeAiohttp: base_url=mock_api_server_base_url, ) - @pytest.fixture - def event_loop(self): + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server(self) try: - setup_mock_web_api_server(self) - loop = get_event_loop() - yield loop - loop.close() - cleanup_mock_web_api_server(self) + yield # run the test here finally: + cleanup_mock_web_api_server(self) restore_os_env(old_os_env) @pytest.mark.asyncio diff --git a/tests/adapter_tests_async/socket_mode/test_async_websockets.py b/tests/adapter_tests_async/socket_mode/test_async_websockets.py index db2680fc6..fc27150ee 100644 --- a/tests/adapter_tests_async/socket_mode/test_async_websockets.py +++ b/tests/adapter_tests_async/socket_mode/test_async_websockets.py @@ -9,7 +9,7 @@ setup_mock_web_api_server, cleanup_mock_web_api_server, ) -from tests.utils import remove_os_env_temporarily, restore_os_env, get_event_loop +from tests.utils import remove_os_env_temporarily, restore_os_env from ...adapter_tests.socket_mode.mock_socket_mode_server import ( start_socket_mode_server, stop_socket_mode_server, @@ -24,16 +24,14 @@ class TestSocketModeWebsockets: base_url=mock_api_server_base_url, ) - @pytest.fixture - def event_loop(self): + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server(self) try: - setup_mock_web_api_server(self) - loop = get_event_loop() - yield loop - loop.close() - cleanup_mock_web_api_server(self) + yield # run the test here finally: + cleanup_mock_web_api_server(self) restore_os_env(old_os_env) @pytest.mark.asyncio @@ -42,7 +40,7 @@ async def test_events(self): app = AsyncApp(client=self.web_client) - result = {"shortcut": False, "command": False} + result = {"shortcut": False, "command": False, "message": False} @app.shortcut("do-something") async def shortcut_handler(ack): @@ -54,6 +52,13 @@ async def command_handler(ack): result["command"] = True await ack() + @app.message("<@W111>") + async def message_handler(ack, req): + result["message"] = req.headers.get("x-slack-retry-num") == ["1"] and req.headers.get( + "x-slack-retry-reason" + ) == ["timeout"] + await ack() + handler = AsyncSocketModeHandler( app_token="xapp-A111-222-xyz", app=app, @@ -69,6 +74,7 @@ async def command_handler(ack): await asyncio.sleep(2) assert result["shortcut"] is True assert result["command"] is True + assert result["message"] is True finally: await handler.client.close() stop_socket_mode_server(self) diff --git a/tests/adapter_tests_async/test_async_falcon.py b/tests/adapter_tests_async/test_async_falcon.py index 6e3901fdf..d7a8c277a 100644 --- a/tests/adapter_tests_async/test_async_falcon.py +++ b/tests/adapter_tests_async/test_async_falcon.py @@ -2,8 +2,9 @@ from time import time from urllib.parse import quote +import pytest import falcon -from falcon import testing +from falcon.testing import ASGIConductor from slack_sdk.signature import SignatureVerifier from slack_sdk.web.async_client import AsyncWebClient @@ -55,7 +56,8 @@ def build_headers(self, timestamp: str, body: str): "x-slack-request-timestamp": timestamp, } - def test_events(self): + @pytest.mark.asyncio + async def test_events(self): app = AsyncApp( client=self.web_client, signing_secret=self.signing_secret, @@ -92,16 +94,17 @@ async def event_handler(): resource = AsyncSlackAppResource(app) api.add_route("/slack/events", resource) - client = testing.TestClient(api) - response = client.simulate_post( - "/slack/events", - body=body, - headers=self.build_headers(timestamp, body), - ) + async with ASGIConductor(api) as conductor: + response = await conductor.simulate_post( + "/slack/events", + body=body, + headers=self.build_headers(timestamp, body), + ) assert response.status_code == 200 assert_auth_test_count(self, 1) - def test_shortcuts(self): + @pytest.mark.asyncio + async def test_shortcuts(self): app = AsyncApp( client=self.web_client, signing_secret=self.signing_secret, @@ -133,16 +136,17 @@ async def shortcut_handler(ack): resource = AsyncSlackAppResource(app) api.add_route("/slack/events", resource) - client = testing.TestClient(api) - response = client.simulate_post( - "/slack/events", - body=body, - headers=self.build_headers(timestamp, body), - ) + async with ASGIConductor(api) as conductor: + response = await conductor.simulate_post( + "/slack/events", + body=body, + headers=self.build_headers(timestamp, body), + ) assert response.status_code == 200 assert_auth_test_count(self, 1) - def test_commands(self): + @pytest.mark.asyncio + async def test_commands(self): app = AsyncApp( client=self.web_client, signing_secret=self.signing_secret, @@ -174,16 +178,17 @@ async def command_handler(ack): resource = AsyncSlackAppResource(app) api.add_route("/slack/events", resource) - client = testing.TestClient(api) - response = client.simulate_post( - "/slack/events", - body=body, - headers=self.build_headers(timestamp, body), - ) + async with ASGIConductor(api) as conductor: + response = await conductor.simulate_post( + "/slack/events", + body=body, + headers=self.build_headers(timestamp, body), + ) assert response.status_code == 200 assert_auth_test_count(self, 1) - def test_oauth(self): + @pytest.mark.asyncio + async def test_oauth(self): app = AsyncApp( client=self.web_client, signing_secret=self.signing_secret, @@ -197,9 +202,24 @@ def test_oauth(self): resource = AsyncSlackAppResource(app) api.add_route("/slack/install", resource) - client = testing.TestClient(api) - response = client.simulate_get("/slack/install") + async with ASGIConductor(api) as conductor: + response = await conductor.simulate_get("/slack/install") assert response.status_code == 200 assert response.headers.get("content-type") == "text/html; charset=utf-8" assert response.headers.get("content-length") == "607" assert "https://slack.com/oauth/v2/authorize?state=" in response.text + + @pytest.mark.asyncio + async def test_get_no_oauth(self): + app = AsyncApp( + client=self.web_client, + signing_secret=self.signing_secret, + ) + api = new_falcon_app() + resource = AsyncSlackAppResource(app) + api.add_route("/slack/events", resource) + + async with ASGIConductor(api) as conductor: + response = await conductor.simulate_get("/slack/events") + assert response.status_code == 404 + assert "The page is not found" in response.text diff --git a/tests/adapter_tests_async/test_async_fastapi.py b/tests/adapter_tests_async/test_async_fastapi.py index 311c802fe..e0175d3fa 100644 --- a/tests/adapter_tests_async/test_async_fastapi.py +++ b/tests/adapter_tests_async/test_async_fastapi.py @@ -94,7 +94,7 @@ async def endpoint(req: Request): client = TestClient(api) response = client.post( "/slack/events", - data=body, + content=body, headers=self.build_headers(timestamp, body), ) assert response.status_code == 200 @@ -138,7 +138,7 @@ async def endpoint(req: Request): client = TestClient(api) response = client.post( "/slack/events", - data=body, + content=body, headers=self.build_headers(timestamp, body), ) assert response.status_code == 200 @@ -182,7 +182,7 @@ async def endpoint(req: Request): client = TestClient(api) response = client.post( "/slack/events", - data=body, + content=body, headers=self.build_headers(timestamp, body), ) assert response.status_code == 200 @@ -206,7 +206,8 @@ async def endpoint(req: Request): return await app_handler.handle(req) client = TestClient(api) - response = client.get("/slack/install", allow_redirects=False) + client.follow_redirects = False + response = client.get("/slack/install") assert response.status_code == 200 assert response.headers.get("content-type") == "text/html; charset=utf-8" assert response.headers.get("content-length") == "607" @@ -254,7 +255,7 @@ async def endpoint(req: Request, foo: str = Depends(get_foo)): client = TestClient(api) response = client.post( "/slack/events", - data=body, + content=body, headers=self.build_headers(timestamp, body), ) assert response.status_code == 200 diff --git a/tests/adapter_tests_async/test_async_sanic.py b/tests/adapter_tests_async/test_async_sanic.py index 316110a87..9a948e3a6 100644 --- a/tests/adapter_tests_async/test_async_sanic.py +++ b/tests/adapter_tests_async/test_async_sanic.py @@ -17,7 +17,7 @@ cleanup_mock_web_api_server, assert_auth_test_count, ) -from tests.utils import remove_os_env_temporarily, restore_os_env, get_event_loop +from tests.utils import remove_os_env_temporarily, restore_os_env class TestSanic: @@ -34,16 +34,14 @@ class TestSanic: def unique_sanic_app_name() -> str: return f"awesome-slack-app-{str(time()).replace('.', '-')}" - @pytest.fixture - def event_loop(self): + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server(self) try: - setup_mock_web_api_server(self) - loop = get_event_loop() - yield loop - loop.close() - cleanup_mock_web_api_server(self) + yield # run the test here finally: + cleanup_mock_web_api_server(self) restore_os_env(old_os_env) def generate_signature(self, body: str, timestamp: str): diff --git a/tests/adapter_tests_async/test_async_starlette.py b/tests/adapter_tests_async/test_async_starlette.py index db3a68a56..849c75168 100644 --- a/tests/adapter_tests_async/test_async_starlette.py +++ b/tests/adapter_tests_async/test_async_starlette.py @@ -97,7 +97,7 @@ async def endpoint(req: Request): client = TestClient(api) response = client.post( "/slack/events", - data=body, + content=body, headers=self.build_headers(timestamp, body), ) assert response.status_code == 200 @@ -143,7 +143,7 @@ async def endpoint(req: Request): client = TestClient(api) response = client.post( "/slack/events", - data=body, + content=body, headers=self.build_headers(timestamp, body), ) assert response.status_code == 200 @@ -189,7 +189,7 @@ async def endpoint(req: Request): client = TestClient(api) response = client.post( "/slack/events", - data=body, + content=body, headers=self.build_headers(timestamp, body), ) assert response.status_code == 200 @@ -216,7 +216,8 @@ async def endpoint(req: Request): ) client = TestClient(api) - response = client.get("/slack/install", allow_redirects=False) + client.follow_redirects = False + response = client.get("/slack/install") assert response.status_code == 200 assert response.headers.get("content-type") == "text/html; charset=utf-8" assert response.headers.get("content-length") == "607" diff --git a/tests/mock_asgi_server.py b/tests/mock_asgi_server.py index e71a0d3cb..0b29a3e5a 100644 --- a/tests/mock_asgi_server.py +++ b/tests/mock_asgi_server.py @@ -1,28 +1,44 @@ -from typing import Iterable, Tuple, Union +from typing import Iterable, Tuple + +from asgiref.testing import ApplicationCommunicator + from slack_bolt.adapter.asgi.base_handler import BaseSlackRequestHandler ENCODING = "utf-8" class AsgiTestServerResponse: - def __init__(self): - self.status_code: int = None - self._headers: Iterable[Tuple[bytes, bytes]] = [] - self._body: bytearray = bytearray(b"") + def __init__( + self, + status_code: int, + headers: Iterable[Tuple[bytes, bytes]] = (), + body: bytes = b"", + ): + self.status_code = status_code + self._headers = headers + self._body = body @property - def body(self): + def body(self) -> str: return self._body.decode(ENCODING) @property - def headers(self): - return {header[0].decode(ENCODING): header[1].decode(ENCODING) for header in self._headers} + def headers(self) -> dict: + result = {} + for header in self._headers: + key = header[0].decode(ENCODING) + if key not in result: + result[key] = header[1].decode(ENCODING) + return result + + def get_headers_list(self, name: str) -> list: + return [header[1].decode(ENCODING) for header in self._headers if header[0].decode(ENCODING) == name] class AsgiTestServerLifespanResponse: - def __init__(self): - self.type: str = None - self.message: str = "" + def __init__(self, type: str, message: str = ""): + self.type = type + self.message = message class AsgiTestServer: @@ -61,22 +77,17 @@ async def http( }, ) - async def receive(): - return {"type": "http.request", "body": bytes(body, ENCODING), "more_body": False} + communicator = ApplicationCommunicator(self.asgi_app, scope) + await communicator.send_input({"type": "http.request", "body": bytes(body, ENCODING), "more_body": False}) - response = AsgiTestServerResponse() + response_start = await communicator.receive_output(timeout=1) + response_body = await communicator.receive_output(timeout=1) - async def send(event): - if event["type"] == "http.response.start": - response.status_code = event["status"] - response._headers = event["headers"] - elif event["type"] == "http.response.body": - response._body.extend(event["body"]) - else: - raise TypeError(f"Sent type {event['type']} in response {event} is not valid") - - await self.asgi_app(scope, receive, send) - return response + return AsgiTestServerResponse( + status_code=response_start["status"], + headers=response_start.get("headers", []), + body=response_body.get("body", b""), + ) async def lifespan(self, event: str) -> AsgiTestServerLifespanResponse: """This implements the server side behavior of the lifespan event @@ -92,17 +103,20 @@ async def lifespan(self, event: str) -> AsgiTestServerLifespanResponse: }, ) - async def receive(): - return {"type": f"lifespan.{event}"} + communicator = ApplicationCommunicator(self.asgi_app, scope) + await communicator.send_input({"type": f"lifespan.{event}"}) - response = AsgiTestServerLifespanResponse() + result = await communicator.receive_output(timeout=1) - async def send(event: dict): - response.type = event["type"] - response.message = event.get("message", "") + # Send shutdown so the handler exits cleanly + if event == "startup": + await communicator.send_input({"type": "lifespan.shutdown"}) + await communicator.receive_output(timeout=1) - await self.asgi_app(scope, receive, send) - return response + return AsgiTestServerLifespanResponse( + type=result["type"], + message=result.get("message", ""), + ) async def websocket(self) -> None: """This is not implemented""" @@ -113,10 +127,6 @@ async def websocket(self) -> None: }, ) - async def receive(): - return {} - - async def send(event: dict): - print(event) - - await self.asgi_app(scope, receive, send) + communicator = ApplicationCommunicator(self.asgi_app, scope) + await communicator.send_input({}) + await communicator.receive_output(timeout=1) diff --git a/tests/mock_wsgi_server.py b/tests/mock_wsgi_server.py index a389a898e..7e8bad2e8 100644 --- a/tests/mock_wsgi_server.py +++ b/tests/mock_wsgi_server.py @@ -1,4 +1,7 @@ -from typing import Dict, Iterable, Optional, Tuple +import io +from typing import Any, Callable, Dict, List, Optional, Tuple +from wsgiref.util import setup_testing_defaults +from wsgiref.validate import validator from slack_bolt.adapter.wsgi import SlackRequestHandler @@ -6,37 +9,49 @@ class WsgiTestServerResponse: - def __init__(self): + def __init__(self) -> None: self.status: Optional[str] = None - self._headers: Iterable[Tuple[str, str]] = [] - self._body: Iterable[bytes] = [] + self._headers: List[Tuple[str, str]] = [] + self._body: List[bytes] = [] @property def headers(self) -> Dict[str, str]: return {header[0]: header[1] for header in self._headers} @property - def body(self, length: int = 0) -> str: - return "".join([chunk.decode(ENCODING) for chunk in self._body[length:]]) + def body(self) -> str: + return "".join([chunk.decode(ENCODING) for chunk in self._body]) class MockReadable: + """PEP 3333 compliant input stream. + + Implements read, readline, readlines, and __iter__ as required + by the WSGI specification for wsgi.input. + """ + def __init__(self, body: str): self.body = body - self._body = bytes(body, ENCODING) + self._stream = io.BytesIO(bytes(body, ENCODING)) def get_content_length(self) -> int: - return len(self._body) + return len(self.body.encode(ENCODING)) + + def read(self, size: int = -1) -> bytes: + if size == -1: + return self._stream.read() + return self._stream.read(size) + + def readline(self, size: int = -1) -> bytes: + if size == -1: + return self._stream.readline() + return self._stream.readline(size) - def read(self, size: int) -> bytes: - if size < 0: - raise ValueError("Size must be positive.") - if size == 0: - return b"" - # The body can only be read once - _body = self._body[:size] - self._body = b"" - return _body + def readlines(self, hint: int = -1) -> List[bytes]: + return self._stream.readlines(hint) + + def __iter__(self): + return iter(self._stream) class WsgiTestServer: @@ -44,29 +59,26 @@ def __init__( self, wsgi_app: SlackRequestHandler, root_path: str = "", - version: Tuple[int, int] = (1, 0), - multithread: bool = False, - multiprocess: bool = False, - run_once: bool = False, input_terminated: bool = True, - server_software: bool = "mock/0.0.0", + server_software: str = "mock/0.0.0", url_scheme: str = "https", remote_addr: str = "127.0.0.1", remote_port: str = "63263", ): self.root_path = root_path - self.wsgi_app = wsgi_app - self.environ = { - "wsgi.version": version, - "wsgi.multithread": multithread, - "wsgi.multiprocess": multiprocess, - "wsgi.run_once": run_once, - "wsgi.input_terminated": input_terminated, - "SERVER_SOFTWARE": server_software, - "wsgi.url_scheme": url_scheme, - "REMOTE_ADDR": remote_addr, - "REMOTE_PORT": remote_port, - } + self.wsgi_app = validator(wsgi_app) + self.environ: Dict[str, Any] = {} + setup_testing_defaults(self.environ) + self.environ.update( + { + "wsgi.input_terminated": input_terminated, + "wsgi.errors": io.StringIO(), + "SERVER_SOFTWARE": server_software, + "wsgi.url_scheme": url_scheme, + "REMOTE_ADDR": remote_addr, + "REMOTE_PORT": remote_port, + } + ) def http( self, @@ -101,16 +113,29 @@ def http( environ[f"HTTP_{header_key}"] = value if body is not None: - environ["wsgi.input"] = MockReadable(body) + readable = MockReadable(body) + environ["wsgi.input"] = readable if "CONTENT_LENGTH" not in environ: - environ["CONTENT_LENGTH"] = str(environ["wsgi.input"].get_content_length()) + environ["CONTENT_LENGTH"] = str(readable.get_content_length()) + else: + environ["wsgi.input"] = MockReadable("") response = WsgiTestServerResponse() - def start_response(status, headers): + def start_response( + status: str, + headers: List[Tuple[str, str]], + exc_info: Optional[Any] = None, + ) -> Callable[[bytes], object]: response.status = status response._headers = headers - - response._body = self.wsgi_app(environ=environ, start_response=start_response) + return lambda s: None + + iterator = self.wsgi_app(environ, start_response) + try: + response._body = list(iterator) + finally: + if hasattr(iterator, "close"): + iterator.close() return response diff --git a/tests/scenario_tests/test_app.py b/tests/scenario_tests/test_app.py index 9bbad6d9a..5cdff39bf 100644 --- a/tests/scenario_tests/test_app.py +++ b/tests/scenario_tests/test_app.py @@ -1,7 +1,7 @@ import logging import time from concurrent.futures import Executor -from ssl import SSLContext +import ssl import pytest from slack_sdk import WebClient @@ -96,6 +96,13 @@ def test_token_verification_enabled_False(self): assert self.received_requests.get("/auth.test") is None + def test_socket_mode_app_without_signing_secret(self): + app = App( + client=self.web_client, + token_verification_enabled=False, + ) + assert app is not None + # -------------------------- # multi teams auth # -------------------------- @@ -236,12 +243,12 @@ def test_none_body_no_middleware(self): assert response.body == '{"error": "unhandled request"}' def test_proxy_ssl_for_respond(self): - ssl = SSLContext() + ssl_context = ssl.create_default_context() web_client = WebClient( token=self.valid_token, base_url=self.mock_api_server_base_url, proxy="http://proxy-host:9000/", - ssl=ssl, + ssl=ssl_context, ) app = App( signing_secret="valid", @@ -257,9 +264,9 @@ def test_proxy_ssl_for_respond(self): @app.event("app_mention") def handle(context: BoltContext, respond): assert context.respond.proxy == "http://proxy-host:9000/" - assert context.respond.ssl == ssl + assert context.respond.ssl == ssl_context assert respond.proxy == "http://proxy-host:9000/" - assert respond.ssl == ssl + assert respond.ssl == ssl_context result["called"] = True req = BoltRequest(body=app_mention_event_body, headers={}, mode="socket_mode") diff --git a/tests/scenario_tests/test_attachment_actions.py b/tests/scenario_tests/test_attachment_actions.py index fa40187ee..f40deb22b 100644 --- a/tests/scenario_tests/test_attachment_actions.py +++ b/tests/scenario_tests/test_attachment_actions.py @@ -164,7 +164,7 @@ def test_failure_2(self): assert_auth_test_count(self, 1) -# https://api.slack.com/legacy/interactive-messages +# https://docs.slack.dev/legacy/legacy-messaging/legacy-making-messages-interactive/ body = { "type": "interactive_message", "actions": [ diff --git a/tests/scenario_tests/test_events_assistant.py b/tests/scenario_tests/test_events_assistant.py index 07f7ede53..c95296154 100644 --- a/tests/scenario_tests/test_events_assistant.py +++ b/tests/scenario_tests/test_events_assistant.py @@ -1,12 +1,13 @@ -from time import sleep +from threading import Event +from typing import Callable from slack_sdk.web import WebClient -from slack_bolt import App, BoltRequest, Assistant, Say, SetSuggestedPrompts, SetStatus, BoltContext -from tests.mock_web_api_server import ( - setup_mock_web_api_server, - cleanup_mock_web_api_server, -) +from slack_bolt import App, Assistant, BoltContext, BoltRequest, Say, SetStatus, SetSuggestedPrompts +from slack_bolt.middleware import Middleware +from slack_bolt.request import BoltRequest as BoltRequestType +from slack_bolt.response import BoltResponse +from tests.mock_web_api_server import cleanup_mock_web_api_server, setup_mock_web_api_server from tests.utils import remove_os_env_temporarily, restore_os_env @@ -26,81 +27,228 @@ def teardown_method(self): cleanup_mock_web_api_server(self) restore_os_env(self.old_os_env) - def test_assistant_threads(self): + def test_thread_started(self): app = App(client=self.web_client) assistant = Assistant() - - state = {"called": False} - - def assert_target_called(): - count = 0 - while state["called"] is False and count < 20: - sleep(0.1) - count += 1 - assert state["called"] is True - state["called"] = False + listener_called = Event() @assistant.thread_started - def start_thread(say: Say, set_suggested_prompts: SetSuggestedPrompts, context: BoltContext): + def start_thread(say: Say, set_suggested_prompts: SetSuggestedPrompts, set_status: SetStatus, context: BoltContext): assert context.channel_id == "D111" assert context.thread_ts == "1726133698.626339" + assert set_status.thread_ts == context.thread_ts + assert say.thread_ts == context.thread_ts say("Hi, how can I help you today?") set_suggested_prompts(prompts=[{"title": "What does SLACK stand for?", "message": "What does SLACK stand for?"}]) set_suggested_prompts( prompts=[{"title": "What does SLACK stand for?", "message": "What does SLACK stand for?"}], title="foo" ) - state["called"] = True + listener_called.set() + + app.assistant(assistant) + + request = BoltRequest(body=thread_started_event_body, mode="socket_mode") + response = app.dispatch(request) + assert response.status == 200 + assert listener_called.wait(timeout=0.1) is True + + def test_thread_context_changed(self): + app = App(client=self.web_client) + assistant = Assistant() + listener_called = Event() @assistant.thread_context_changed def handle_thread_context_changed(context: BoltContext): assert context.channel_id == "D111" assert context.thread_ts == "1726133698.626339" - state["called"] = True + listener_called.set() + + app.assistant(assistant) + + request = BoltRequest(body=thread_context_changed_event_body, mode="socket_mode") + response = app.dispatch(request) + assert response.status == 200 + assert listener_called.wait(timeout=0.1) is True + + def test_user_message(self): + app = App(client=self.web_client) + assistant = Assistant() + listener_called = Event() @assistant.user_message def handle_user_message(say: Say, set_status: SetStatus, context: BoltContext): assert context.channel_id == "D111" assert context.thread_ts == "1726133698.626339" + assert say.thread_ts == context.thread_ts try: set_status("is typing...") say("Here you are!") - state["called"] = True + listener_called.set() except Exception as e: - say(f"Oops, something went wrong (error: {e}") + say(f"Oops, something went wrong (error: {e})") app.assistant(assistant) - request = BoltRequest(body=thread_started_event_body, mode="socket_mode") + request = BoltRequest(body=user_message_event_body, mode="socket_mode") response = app.dispatch(request) assert response.status == 200 - assert_target_called() + assert listener_called.wait(timeout=0.1) is True - request = BoltRequest(body=thread_context_changed_event_body, mode="socket_mode") - response = app.dispatch(request) - assert response.status == 200 - assert_target_called() + def test_user_message_with_assistant_thread(self): + app = App(client=self.web_client) + assistant = Assistant() + listener_called = Event() - request = BoltRequest(body=user_message_event_body, mode="socket_mode") + @assistant.user_message + def handle_user_message(say: Say, set_status: SetStatus, context: BoltContext): + assert context.channel_id == "D111" + assert context.thread_ts == "1726133698.626339" + assert say.thread_ts == context.thread_ts + try: + set_status("is typing...") + say("Here you are!") + listener_called.set() + except Exception as e: + say(f"Oops, something went wrong (error: {e})") + + app.assistant(assistant) + + request = BoltRequest(body=user_message_event_body_with_assistant_thread, mode="socket_mode") response = app.dispatch(request) assert response.status == 200 - assert_target_called() + assert listener_called.wait(timeout=0.1) is True - request = BoltRequest(body=user_message_event_body_with_assistant_thread, mode="socket_mode") + def test_message_changed(self): + app = App(client=self.web_client) + assistant = Assistant() + listener_called = Event() + + @assistant.user_message + def handle_user_message(): + listener_called.set() + + @assistant.bot_message + def handle_bot_message(): + listener_called.set() + + app.assistant(assistant) + + request = BoltRequest(body=user_message_event_body_with_action_token, mode="socket_mode") response = app.dispatch(request) assert response.status == 200 - assert_target_called() + assert listener_called.wait(timeout=0.1) is True + listener_called.clear() request = BoltRequest(body=message_changed_event_body, mode="socket_mode") response = app.dispatch(request) assert response.status == 200 + assert listener_called.wait(timeout=0.1) is False + + def test_channel_user_message_ignored(self): + app = App(client=self.web_client) + assistant = Assistant() + listener_called = Event() + + @assistant.user_message + def handle_user_message(): + listener_called.set() + + @assistant.bot_message + def handle_bot_message(): + listener_called.set() + + app.assistant(assistant) request = BoltRequest(body=channel_user_message_event_body, mode="socket_mode") response = app.dispatch(request) assert response.status == 404 + assert listener_called.wait(timeout=0.1) is False + + def test_channel_message_changed_ignored(self): + app = App(client=self.web_client) + assistant = Assistant() + listener_called = Event() + + @assistant.user_message + def handle_user_message(): + listener_called.set() + + @assistant.bot_message + def handle_bot_message(): + listener_called.set() + + app.assistant(assistant) request = BoltRequest(body=channel_message_changed_event_body, mode="socket_mode") response = app.dispatch(request) assert response.status == 404 + assert listener_called.wait(timeout=0.1) is False + + def test_assistant_with_custom_listener_middleware(self): + app = App(client=self.web_client) + assistant = Assistant() + listener_called = Event() + middleware_called = Event() + + class TestMiddleware(Middleware): + def process(self, *, req: BoltRequestType, resp: BoltResponse, next: Callable[[], BoltResponse]): + middleware_called.set() + # Verify assistant utilities are available + assert req.context.get("set_status") is not None + assert req.context.get("set_title") is not None + assert req.context.get("set_suggested_prompts") is not None + assert req.context.get("get_thread_context") is not None + assert req.context.get("save_thread_context") is not None + return next() + + @assistant.thread_started(middleware=[TestMiddleware()]) + def start_thread(): + listener_called.set() + + @assistant.user_message(middleware=[TestMiddleware()]) + def handle_user_message(): + listener_called.set() + + app.assistant(assistant) + + request = BoltRequest(body=thread_started_event_body, mode="socket_mode") + response = app.dispatch(request) + assert response.status == 200 + assert listener_called.wait(timeout=0.1) is True + assert middleware_called.wait(timeout=0.1) is True + + listener_called.clear() + middleware_called.clear() + + request = BoltRequest(body=user_message_event_body, mode="socket_mode") + response = app.dispatch(request) + assert response.status == 200 + assert listener_called.wait(timeout=0.1) is True + assert middleware_called.wait(timeout=0.1) is True + + def test_assistant_custom_middleware_can_short_circuit(self): + app = App(client=self.web_client) + assistant = Assistant() + listener_called = Event() + middleware_called = Event() + + class BlockingMiddleware(Middleware): + def process(self, *, req: BoltRequestType, resp: BoltResponse, next: Callable[[], BoltResponse]): + middleware_called.set() + # Intentionally not calling next() to short-circuit + return BoltResponse(status=200) + + @assistant.thread_started(middleware=[BlockingMiddleware()]) + def start_thread(say: Say, context: BoltContext): + listener_called.set() + + app.assistant(assistant) + + request = BoltRequest(body=thread_started_event_body, mode="socket_mode") + response = app.dispatch(request) + assert response.status == 200 + assert middleware_called.wait(timeout=0.1) is True + assert listener_called.wait(timeout=0.1) is False def build_payload(event: dict) -> dict: @@ -190,6 +338,25 @@ def build_payload(event: dict) -> dict: } ) +user_message_event_body_with_action_token = build_payload( + { + "user": "W222", + "type": "message", + "ts": "1726133700.887259", + "text": "When Slack was released?", + "team": "T111", + "user_team": "T111", + "source_team": "T222", + "user_profile": {}, + "thread_ts": "1726133698.626339", + "parent_user_id": "W222", + "channel": "D111", + "event_ts": "1726133700.887259", + "channel_type": "im", + "assistant_thread": {"action_token": "10647138185092.960436384805.afce3599"}, + } +) + message_changed_event_body = build_payload( { "type": "message", @@ -242,7 +409,7 @@ def build_payload(event: dict) -> dict: "user_profile": {}, "thread_ts": "1726133698.626339", "parent_user_id": "W222", - "channel": "D111", + "channel": "C111", "event_ts": "1726133700.887259", "channel_type": "channel", } @@ -277,7 +444,7 @@ def build_payload(event: dict) -> dict: "reply_users": ["U222", "W111"], "is_locked": False, }, - "channel": "D111", + "channel": "C111", "hidden": True, "ts": "1726133701.028300", "event_ts": "1726133701.028300", diff --git a/tests/scenario_tests/test_events_assistant_without_middleware.py b/tests/scenario_tests/test_events_assistant_without_middleware.py new file mode 100644 index 000000000..18072c05e --- /dev/null +++ b/tests/scenario_tests/test_events_assistant_without_middleware.py @@ -0,0 +1,265 @@ +from threading import Event + +from slack_sdk.web import WebClient + +from slack_bolt import App, BoltContext, BoltRequest, SaveThreadContext, Say, SetStatus, SetSuggestedPrompts, SetTitle +from slack_bolt.context.get_thread_context.get_thread_context import GetThreadContext +from tests.mock_web_api_server import cleanup_mock_web_api_server, setup_mock_web_api_server +from tests.scenario_tests.test_events_assistant import ( + channel_message_changed_event_body, + channel_user_message_event_body, + message_changed_event_body, + thread_context_changed_event_body, + thread_started_event_body, + user_message_event_body, + user_message_event_body_with_assistant_thread, +) +from tests.utils import remove_os_env_temporarily, restore_os_env + + +class TestEventsAssistantWithoutMiddleware: + valid_token = "xoxb-valid" + mock_api_server_base_url = "http://localhost:8888" + web_client = WebClient( + token=valid_token, + base_url=mock_api_server_base_url, + ) + + def setup_method(self): + self.old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server(self) + + def teardown_method(self): + cleanup_mock_web_api_server(self) + restore_os_env(self.old_os_env) + + def test_thread_started(self): + app = App(client=self.web_client) + listener_called = Event() + + @app.event("assistant_thread_started") + def handle_assistant_thread_started( + say: Say, + set_status: SetStatus, + set_title: SetTitle, + set_suggested_prompts: SetSuggestedPrompts, + get_thread_context: GetThreadContext, + save_thread_context: SaveThreadContext, + context: BoltContext, + ): + assert context.channel_id == "D111" + assert context.thread_ts == "1726133698.626339" + assert say.thread_ts == context.thread_ts + assert set_status is not None + assert set_title is not None + assert set_suggested_prompts is not None + assert get_thread_context is not None + assert save_thread_context is not None + say("Hi, how can I help you today?") + set_suggested_prompts(prompts=[{"title": "What does SLACK stand for?", "message": "What does SLACK stand for?"}]) + listener_called.set() + + request = BoltRequest(body=thread_started_event_body, mode="socket_mode") + response = app.dispatch(request) + assert response.status == 200 + assert listener_called.wait(timeout=0.1) is True + + def test_thread_context_changed(self): + app = App(client=self.web_client) + listener_called = Event() + + @app.event("assistant_thread_context_changed") + def handle_assistant_thread_context_changed( + say: Say, + set_status: SetStatus, + set_title: SetTitle, + set_suggested_prompts: SetSuggestedPrompts, + get_thread_context: GetThreadContext, + save_thread_context: SaveThreadContext, + context: BoltContext, + ): + assert context.channel_id == "D111" + assert context.thread_ts == "1726133698.626339" + assert say.thread_ts == context.thread_ts + assert set_status is not None + assert set_title is not None + assert set_suggested_prompts is not None + assert get_thread_context is not None + assert save_thread_context is not None + listener_called.set() + + request = BoltRequest(body=thread_context_changed_event_body, mode="socket_mode") + response = app.dispatch(request) + assert response.status == 200 + assert listener_called.wait(timeout=0.1) is True + + def test_user_message(self): + app = App(client=self.web_client) + listener_called = Event() + + @app.message("") + def handle_message( + say: Say, + set_status: SetStatus, + set_title: SetTitle, + set_suggested_prompts: SetSuggestedPrompts, + get_thread_context: GetThreadContext, + save_thread_context: SaveThreadContext, + context: BoltContext, + ): + assert context.channel_id == "D111" + assert context.thread_ts == "1726133698.626339" + assert say.thread_ts == context.thread_ts + assert set_status is not None + assert set_title is not None + assert set_suggested_prompts is not None + assert get_thread_context is not None + assert save_thread_context is not None + try: + set_status("is typing...") + say("Here you are!") + listener_called.set() + except Exception as e: + say(f"Oops, something went wrong (error: {e})") + + request = BoltRequest(body=user_message_event_body, mode="socket_mode") + response = app.dispatch(request) + assert response.status == 200 + assert listener_called.wait(timeout=0.1) is True + + def test_user_message_with_assistant_thread(self): + app = App(client=self.web_client) + listener_called = Event() + + @app.message("") + def handle_message( + say: Say, + set_status: SetStatus, + set_title: SetTitle, + set_suggested_prompts: SetSuggestedPrompts, + get_thread_context: GetThreadContext, + save_thread_context: SaveThreadContext, + context: BoltContext, + ): + assert context.channel_id == "D111" + assert context.thread_ts == "1726133698.626339" + assert say.thread_ts == context.thread_ts + assert set_status is not None + assert set_title is not None + assert set_suggested_prompts is not None + assert get_thread_context is not None + assert save_thread_context is not None + try: + set_status("is typing...") + say("Here you are!") + listener_called.set() + except Exception as e: + say(f"Oops, something went wrong (error: {e})") + + request = BoltRequest(body=user_message_event_body_with_assistant_thread, mode="socket_mode") + response = app.dispatch(request) + assert response.status == 200 + assert listener_called.wait(timeout=0.1) is True + + def test_message_changed(self): + app = App(client=self.web_client) + listener_called = Event() + + @app.event("message") + def handle_message_event( + say: Say, + set_status: SetStatus, + set_title: SetTitle, + set_suggested_prompts: SetSuggestedPrompts, + get_thread_context: GetThreadContext, + save_thread_context: SaveThreadContext, + context: BoltContext, + ): + assert context.thread_ts == "1726133698.626339" + assert say.thread_ts == None + assert set_status is not None + assert set_title is None + assert set_suggested_prompts is None + assert get_thread_context is None + assert save_thread_context is None + listener_called.set() + + request = BoltRequest(body=message_changed_event_body, mode="socket_mode") + response = app.dispatch(request) + assert response.status == 200 + assert listener_called.wait(timeout=0.1) is True + + def test_channel_user_message(self): + app = App(client=self.web_client) + listener_called = Event() + + @app.event("message") + def handle_message_event( + say: Say, + set_status: SetStatus, + set_title: SetTitle, + set_suggested_prompts: SetSuggestedPrompts, + get_thread_context: GetThreadContext, + save_thread_context: SaveThreadContext, + context: BoltContext, + ): + assert context.thread_ts == "1726133698.626339" + assert say.thread_ts == None + assert set_status is not None + assert set_title is None + assert set_suggested_prompts is None + assert get_thread_context is None + assert save_thread_context is None + listener_called.set() + + request = BoltRequest(body=channel_user_message_event_body, mode="socket_mode") + response = app.dispatch(request) + assert response.status == 200 + assert listener_called.wait(timeout=0.1) is True + + def test_channel_message_changed(self): + app = App(client=self.web_client) + listener_called = Event() + + @app.event("message") + def handle_message_event( + say: Say, + set_status: SetStatus, + set_title: SetTitle, + set_suggested_prompts: SetSuggestedPrompts, + get_thread_context: GetThreadContext, + save_thread_context: SaveThreadContext, + context: BoltContext, + ): + assert context.thread_ts == "1726133698.626339" + assert say.thread_ts == None + assert set_status is not None + assert set_title is None + assert set_suggested_prompts is None + assert get_thread_context is None + assert save_thread_context is None + listener_called.set() + + request = BoltRequest(body=channel_message_changed_event_body, mode="socket_mode") + response = app.dispatch(request) + assert response.status == 200 + assert listener_called.wait(timeout=0.1) is True + + def test_assistant_events_conversation_kwargs_disabled(self): + app = App(client=self.web_client, attaching_conversation_kwargs_enabled=False) + + listener_called = Event() + + @app.event("assistant_thread_started") + def start_thread(context: BoltContext): + assert context.get("set_status") is None + assert context.get("set_title") is None + assert context.get("set_suggested_prompts") is None + assert context.get("get_thread_context") is None + assert context.get("save_thread_context") is None + listener_called.set() + + request = BoltRequest(body=thread_started_event_body, mode="socket_mode") + response = app.dispatch(request) + assert response.status == 200 + assert listener_called.wait(timeout=0.1) is True diff --git a/tests/scenario_tests/test_events_ignore_self.py b/tests/scenario_tests/test_events_ignore_self.py index db7d07ea8..3c66f3278 100644 --- a/tests/scenario_tests/test_events_ignore_self.py +++ b/tests/scenario_tests/test_events_ignore_self.py @@ -100,6 +100,7 @@ def handle_app_mention(say): "type": "message", "channel": "C111", "ts": "1599529504.000400", + "channel_type": "channel", }, "reaction": "heart_eyes", "item_user": "W111", diff --git a/tests/scenario_tests/test_events_say_stream.py b/tests/scenario_tests/test_events_say_stream.py new file mode 100644 index 000000000..e0ab66aab --- /dev/null +++ b/tests/scenario_tests/test_events_say_stream.py @@ -0,0 +1,224 @@ +import json +from threading import Event +from urllib.parse import quote + +from slack_sdk.web import WebClient + +from slack_bolt import App, Assistant, BoltContext, BoltRequest, SayStream +from tests.mock_web_api_server import cleanup_mock_web_api_server, setup_mock_web_api_server +from tests.scenario_tests.test_app import app_mention_event_body +from tests.scenario_tests.test_events_assistant import thread_started_event_body +from tests.scenario_tests.test_events_assistant import user_message_event_body as threaded_user_message_event_body +from tests.scenario_tests.test_message_bot import bot_message_event_payload, user_message_event_payload +from tests.scenario_tests.test_view_submission import body as view_submission_body +from tests.utils import remove_os_env_temporarily, restore_os_env + + +class TestEventsSayStream: + valid_token = "xoxb-valid" + mock_api_server_base_url = "http://localhost:8888" + web_client = WebClient( + token=valid_token, + base_url=mock_api_server_base_url, + ) + + def setup_method(self): + self.old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server(self) + + def teardown_method(self): + cleanup_mock_web_api_server(self) + restore_os_env(self.old_os_env) + + def test_say_stream_injected_for_app_mention(self): + app = App(client=self.web_client) + listener_called = Event() + + @app.event("app_mention") + def handle_mention(say_stream: SayStream, context: BoltContext): + assert say_stream is not None + assert isinstance(say_stream, SayStream) + assert say_stream == context.say_stream + assert say_stream.channel == "C111" + assert say_stream.thread_ts == "1595926230.009600" + assert say_stream.recipient_team_id == context.team_id + assert say_stream.recipient_user_id == context.user_id + listener_called.set() + + request = BoltRequest(body=app_mention_event_body, mode="socket_mode") + response = app.dispatch(request) + assert response.status == 200 + assert listener_called.wait(timeout=0.1) is True + + def test_say_stream_with_org_level_install(self): + app = App(client=self.web_client) + listener_called = Event() + + @app.event("app_mention") + def handle_mention(say_stream: SayStream, context: BoltContext): + assert context.team_id is None + assert context.enterprise_id == "E111" + assert say_stream is not None + assert isinstance(say_stream, SayStream) + assert say_stream.recipient_team_id == "E111" + listener_called.set() + + request = BoltRequest(body=org_app_mention_event_body, mode="socket_mode") + response = app.dispatch(request) + assert response.status == 200 + assert listener_called.wait(timeout=0.1) is True + + def test_say_stream_injected_for_threaded_message(self): + app = App(client=self.web_client) + listener_called = Event() + + @app.event("message") + def handle_message(say_stream: SayStream, context: BoltContext): + assert say_stream is not None + assert isinstance(say_stream, SayStream) + assert say_stream == context.say_stream + assert say_stream.channel == "D111" + assert say_stream.thread_ts == "1726133698.626339" + assert say_stream.recipient_team_id == context.team_id + assert say_stream.recipient_user_id == context.user_id + listener_called.set() + + request = BoltRequest(body=threaded_user_message_event_body, mode="socket_mode") + response = app.dispatch(request) + assert response.status == 200 + assert listener_called.wait(timeout=0.1) is True + + def test_say_stream_in_user_message(self): + app = App(client=self.web_client) + listener_called = Event() + + @app.message("") + def handle_user_message(say_stream: SayStream, context: BoltContext): + assert say_stream is not None + assert isinstance(say_stream, SayStream) + assert say_stream == context.say_stream + assert say_stream.channel == "C111" + assert say_stream.thread_ts == "1610261659.001400" + assert say_stream.recipient_team_id == context.team_id + assert say_stream.recipient_user_id == context.user_id + listener_called.set() + + request = BoltRequest(body=user_message_event_payload, mode="socket_mode") + response = app.dispatch(request) + assert response.status == 200 + assert listener_called.wait(timeout=0.1) is True + + def test_say_stream_in_bot_message(self): + app = App(client=self.web_client) + listener_called = Event() + + @app.message("") + def handle_bot_message(say_stream: SayStream, context: BoltContext): + assert say_stream is not None + assert isinstance(say_stream, SayStream) + assert say_stream == context.say_stream + assert say_stream.channel == "C111" + assert say_stream.thread_ts == "1610261539.000900" + assert say_stream.recipient_team_id == context.team_id + assert say_stream.recipient_user_id == context.user_id + listener_called.set() + + request = BoltRequest(body=bot_message_event_payload, mode="socket_mode") + response = app.dispatch(request) + assert response.status == 200 + assert listener_called.wait(timeout=0.1) is True + + def test_say_stream_in_assistant_thread_started(self): + app = App(client=self.web_client) + assistant = Assistant() + listener_called = Event() + + @assistant.thread_started + def start_thread(say_stream: SayStream, context: BoltContext): + assert say_stream is not None + assert isinstance(say_stream, SayStream) + assert say_stream == context.say_stream + assert say_stream.channel == "D111" + assert say_stream.thread_ts == "1726133698.626339" + assert say_stream.recipient_team_id == context.team_id + assert say_stream.recipient_user_id == context.user_id + listener_called.set() + + app.assistant(assistant) + + request = BoltRequest(body=thread_started_event_body, mode="socket_mode") + response = app.dispatch(request) + assert response.status == 200 + assert listener_called.wait(timeout=0.1) is True + + def test_say_stream_in_assistant_user_message(self): + app = App(client=self.web_client) + assistant = Assistant() + listener_called = Event() + + @assistant.user_message + def handle_user_message(say_stream: SayStream, context: BoltContext): + assert say_stream is not None + assert isinstance(say_stream, SayStream) + assert say_stream == context.say_stream + assert say_stream.channel == "D111" + assert say_stream.thread_ts == "1726133698.626339" + assert say_stream.recipient_team_id == context.team_id + assert say_stream.recipient_user_id == context.user_id + listener_called.set() + + app.assistant(assistant) + + request = BoltRequest(body=threaded_user_message_event_body, mode="socket_mode") + response = app.dispatch(request) + assert response.status == 200 + assert listener_called.wait(timeout=0.1) is True + + def test_say_stream_is_none_for_view_submission(self): + app = App(client=self.web_client, request_verification_enabled=False) + listener_called = Event() + + @app.view("view-id") + def handle_view(ack, say_stream, context: BoltContext): + ack() + assert say_stream is None + assert context.say_stream is None + listener_called.set() + + request = BoltRequest( + body=f"payload={quote(json.dumps(view_submission_body))}", + ) + response = app.dispatch(request) + assert response.status == 200 + assert listener_called.wait(timeout=0.1) is True + + +org_app_mention_event_body = { + "token": "verification_token", + "team_id": "T111", + "enterprise_id": "E111", + "api_app_id": "A111", + "event": { + "client_msg_id": "9cbd4c5b-7ddf-4ede-b479-ad21fca66d63", + "type": "app_mention", + "text": "<@W111> Hi there!", + "user": "W222", + "ts": "1595926230.009600", + "team": "T111", + "channel": "C111", + "event_ts": "1595926230.009600", + }, + "type": "event_callback", + "event_id": "Ev111", + "event_time": 1595926230, + "authorizations": [ + { + "enterprise_id": "E111", + "team_id": None, + "user_id": "W111", + "is_bot": True, + "is_enterprise_install": True, + } + ], + "is_ext_shared_channel": False, +} diff --git a/tests/scenario_tests/test_events_set_status.py b/tests/scenario_tests/test_events_set_status.py new file mode 100644 index 000000000..2dbdd38b8 --- /dev/null +++ b/tests/scenario_tests/test_events_set_status.py @@ -0,0 +1,171 @@ +import json +from threading import Event +from urllib.parse import quote + +from slack_sdk.web import WebClient + +from slack_bolt import App, BoltContext, BoltRequest +from slack_bolt.context.set_status.set_status import SetStatus +from slack_bolt.middleware.assistant import Assistant +from tests.mock_web_api_server import ( + assert_auth_test_count, + assert_received_request_count, + cleanup_mock_web_api_server, + setup_mock_web_api_server, +) +from tests.scenario_tests.test_app import app_mention_event_body +from tests.scenario_tests.test_events_assistant import thread_started_event_body +from tests.scenario_tests.test_events_assistant import user_message_event_body as threaded_user_message_event_body +from tests.scenario_tests.test_message_bot import bot_message_event_payload, user_message_event_payload +from tests.scenario_tests.test_view_submission import body as view_submission_body +from tests.utils import remove_os_env_temporarily, restore_os_env + + +class TestEventsSetStatus: + valid_token = "xoxb-valid" + mock_api_server_base_url = "http://localhost:8888" + web_client = WebClient( + token=valid_token, + base_url=mock_api_server_base_url, + ) + + def setup_method(self): + self.old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server(self) + + def teardown_method(self): + cleanup_mock_web_api_server(self) + restore_os_env(self.old_os_env) + + def test_set_status_injected_for_app_mention(self): + app = App(client=self.web_client) + + @app.event("app_mention") + def handle_mention(set_status: SetStatus, context: BoltContext): + assert set_status is not None + assert isinstance(set_status, SetStatus) + assert set_status == context.set_status + assert set_status.channel_id == "C111" + assert set_status.thread_ts == "1595926230.009600" + set_status(status="Thinking...") + + request = BoltRequest(body=app_mention_event_body, mode="socket_mode") + response = app.dispatch(request) + assert response.status == 200 + assert_auth_test_count(self, 1) + assert_received_request_count(self, path="/assistant.threads.setStatus", min_count=1) + + def test_set_status_injected_for_threaded_message(self): + app = App(client=self.web_client) + + @app.event("message") + def handle_message(set_status: SetStatus, context: BoltContext): + assert set_status is not None + assert isinstance(set_status, SetStatus) + assert set_status == context.set_status + assert set_status.channel_id == "D111" + assert set_status.thread_ts == "1726133698.626339" + set_status(status="Thinking...") + + request = BoltRequest(body=threaded_user_message_event_body, mode="socket_mode") + response = app.dispatch(request) + assert response.status == 200 + assert_auth_test_count(self, 1) + assert_received_request_count(self, path="/assistant.threads.setStatus", min_count=1) + + def test_set_status_in_user_message(self): + app = App(client=self.web_client) + + @app.message("") + def handle_user_message(set_status: SetStatus, context: BoltContext): + assert set_status is not None + assert isinstance(set_status, SetStatus) + assert set_status == context.set_status + assert set_status.channel_id == "C111" + assert set_status.thread_ts == "1610261659.001400" + set_status(status="Thinking...") + + request = BoltRequest(body=user_message_event_payload, mode="socket_mode") + response = app.dispatch(request) + assert response.status == 200 + assert_auth_test_count(self, 1) + assert_received_request_count(self, path="/assistant.threads.setStatus", min_count=1) + + def test_set_status_in_bot_message(self): + app = App(client=self.web_client) + + @app.message("") + def handle_bot_message(set_status: SetStatus, context: BoltContext): + assert set_status is not None + assert isinstance(set_status, SetStatus) + assert set_status == context.set_status + assert set_status.channel_id == "C111" + assert set_status.thread_ts == "1610261539.000900" + set_status(status="Thinking...") + + request = BoltRequest(body=bot_message_event_payload, mode="socket_mode") + response = app.dispatch(request) + assert response.status == 200 + assert_auth_test_count(self, 1) + assert_received_request_count(self, path="/assistant.threads.setStatus", min_count=1) + + def test_set_status_in_assistant_thread_started(self): + app = App(client=self.web_client) + assistant = Assistant() + + @assistant.thread_started + def start_thread(set_status: SetStatus, context: BoltContext): + assert set_status is not None + assert isinstance(set_status, SetStatus) + assert set_status == context.set_status + assert set_status.channel_id == "D111" + assert set_status.thread_ts == "1726133698.626339" + set_status(status="Thinking...") + + app.assistant(assistant) + + request = BoltRequest(body=thread_started_event_body, mode="socket_mode") + response = app.dispatch(request) + assert response.status == 200 + assert_auth_test_count(self, 1) + assert_received_request_count(self, path="/assistant.threads.setStatus", min_count=1) + + def test_set_status_in_assistant_user_message(self): + app = App(client=self.web_client) + assistant = Assistant() + + @assistant.user_message + def handle_user_message(set_status: SetStatus, context: BoltContext): + assert set_status is not None + assert isinstance(set_status, SetStatus) + assert set_status == context.set_status + assert set_status.channel_id == "D111" + assert set_status.thread_ts == "1726133698.626339" + set_status(status="Thinking...") + + app.assistant(assistant) + + request = BoltRequest(body=threaded_user_message_event_body, mode="socket_mode") + response = app.dispatch(request) + assert response.status == 200 + assert_auth_test_count(self, 1) + assert_received_request_count(self, path="/assistant.threads.setStatus", min_count=1) + + def test_set_status_is_none_for_view_submission(self): + app = App(client=self.web_client, request_verification_enabled=False) + listener_called = Event() + + @app.view("view-id") + def handle_view(ack, set_status, context: BoltContext): + ack() + assert set_status is None + assert context.set_status is None + listener_called.set() + + request = BoltRequest( + body=f"payload={quote(json.dumps(view_submission_body))}", + ) + response = app.dispatch(request) + assert response.status == 200 + assert_auth_test_count(self, 1) + assert listener_called.is_set() diff --git a/tests/scenario_tests/test_function.py b/tests/scenario_tests/test_function.py index 00f0efba8..1e5e1c423 100644 --- a/tests/scenario_tests/test_function.py +++ b/tests/scenario_tests/test_function.py @@ -1,10 +1,13 @@ import json +import re import time import pytest +from unittest.mock import Mock from slack_sdk.signature import SignatureVerifier from slack_sdk.web import WebClient +import slack_bolt.listener.thread_runner as runner_module from slack_bolt.app import App from slack_bolt.request import BoltRequest from tests.mock_web_api_server import ( @@ -51,6 +54,10 @@ def build_request_from_body(self, message_body: dict) -> BoltRequest: timestamp, body = str(int(time.time())), json.dumps(message_body) return BoltRequest(body=body, headers=self.build_headers(timestamp, body)) + def setup_time_mocks(self, *, monkeypatch: pytest.MonkeyPatch, time_mock, sleep_mock): + monkeypatch.setattr(runner_module.time, "time", time_mock) + monkeypatch.setattr(runner_module.time, "sleep", sleep_mock) + def test_valid_callback_id_success(self): app = App( client=self.web_client, @@ -124,7 +131,7 @@ def test_auto_acknowledge_false_with_acknowledging(self): assert response.status == 200 assert_auth_test_count(self, 1) - def test_auto_acknowledge_false_without_acknowledging(self, caplog): + def test_auto_acknowledge_false_without_acknowledging(self, caplog, monkeypatch): app = App( client=self.web_client, signing_secret=self.signing_secret, @@ -132,12 +139,85 @@ def test_auto_acknowledge_false_without_acknowledging(self, caplog): app.function("reverse", auto_acknowledge=False)(just_no_ack) request = self.build_request_from_body(function_body) + + elapsed_seconds = 0 + + def fake_time(): + return float(elapsed_seconds) + + def fake_sleep(duration): + nonlocal elapsed_seconds + elapsed_seconds += 1 + + self.setup_time_mocks( + monkeypatch=monkeypatch, + time_mock=fake_time, + sleep_mock=Mock(side_effect=fake_sleep), + ) response = app.dispatch(request) assert response.status == 404 assert_auth_test_count(self, 1) assert f"WARNING {just_no_ack.__name__} didn't call ack()" in caplog.text + def test_function_handler_timeout(self, monkeypatch): + timeout = 5 + app = App( + client=self.web_client, + signing_secret=self.signing_secret, + ) + app.function("reverse", auto_acknowledge=False, ack_timeout=timeout)(just_no_ack) + request = self.build_request_from_body(function_body) + + elapsed_seconds = 0 + + def fake_time(): + return float(elapsed_seconds) + + def fake_sleep(duration): + nonlocal elapsed_seconds + elapsed_seconds += 1 + + self.setup_time_mocks( + monkeypatch=monkeypatch, + time_mock=fake_time, + sleep_mock=Mock(side_effect=fake_sleep), + ) + + response = app.dispatch(request) + + assert response.status == 404 + assert_auth_test_count(self, 1) + assert elapsed_seconds == timeout + 1, ( + f"Expected handler to time out after {timeout + 1} time.sleep calls, " + f"but it was called {elapsed_seconds} times" + ) + + def test_warning_when_timeout_improperly_set(self, caplog): + app = App( + client=self.web_client, + signing_secret=self.signing_secret, + ) + app.function("reverse")(just_no_ack) + assert "WARNING" not in caplog.text + + timeout_argument_name = "ack_timeout" + kwargs = {timeout_argument_name: 5} + + callback_id = "reverse1" + app.function(callback_id, **kwargs)(just_no_ack) + assert ( + f'WARNING On @app.function("{callback_id}"), as `auto_acknowledge` is `True`, `{timeout_argument_name}={kwargs[timeout_argument_name]}` you gave will be unused' + in caplog.text + ) + + callback_id = re.compile(r"hello \w+") + app.function(callback_id, **kwargs)(just_no_ack) + assert ( + f"WARNING On @app.function({callback_id}), as `auto_acknowledge` is `True`, `{timeout_argument_name}={kwargs[timeout_argument_name]}` you gave will be unused" + in caplog.text + ) + function_body = { "token": "verification_token", @@ -240,16 +320,20 @@ def reverse(body, event, context, client, complete, inputs): assert context.client.token == "xwfp-valid" assert client.token == "xwfp-valid" assert complete.client.token == "xwfp-valid" + assert complete.has_been_called() is False complete( outputs={"reverseString": "olleh"}, ) + assert complete.has_been_called() is True def reverse_error(body, event, fail): assert body == function_body assert event == function_body["event"] assert fail.function_execution_id == "Fx111" + assert fail.has_been_called() is False fail(error="there was an error") + assert fail.has_been_called() is True def complete_it(body, event, complete): diff --git a/tests/scenario_tests/test_lazy.py b/tests/scenario_tests/test_lazy.py index d9e88b280..3b2aefbea 100644 --- a/tests/scenario_tests/test_lazy.py +++ b/tests/scenario_tests/test_lazy.py @@ -156,11 +156,11 @@ def async2(context, say): @app.middleware def set_ssl_context(context, next_): - from ssl import SSLContext + import ssl context["foo"] = "FOO" # This causes an error when starting lazy listener executions - context["ssl_context"] = SSLContext() + context["ssl_context"] = ssl.create_default_context() next_() # 2021-12-13 11:14:29 ERROR Failed to run a middleware middleware (error: cannot pickle 'SSLContext' object) diff --git a/tests/scenario_tests/test_slash_command.py b/tests/scenario_tests/test_slash_command.py index 1db13ecca..ae7e7c53b 100644 --- a/tests/scenario_tests/test_slash_command.py +++ b/tests/scenario_tests/test_slash_command.py @@ -93,6 +93,34 @@ def test_failure(self): assert response.status == 404 assert_auth_test_count(self, 1) + def test_ssl_check_param_does_not_bypass_request_verification(self): + app = App( + client=self.web_client, + signing_secret=self.signing_secret, + ssl_check_enabled=False, + ) + command_called = False + + def command_handler(ack): + nonlocal command_called + command_called = True + ack() + + app.command("/hello-world")(command_handler) + + request = BoltRequest( + body=f"{slash_command_body}&ssl_check=1", + headers={ + "content-type": ["application/x-www-form-urlencoded"], + "x-slack-signature": ["v0=invalid"], + "x-slack-request-timestamp": ["0"], + }, + ) + response = app.dispatch(request) + assert response.status == 401 + assert response.body == """{"error": "invalid request"}""" + assert command_called is False + slash_command_body = ( "token=verification_token" diff --git a/tests/scenario_tests/test_view_submission.py b/tests/scenario_tests/test_view_submission.py index b0eb58212..0f5b23f85 100644 --- a/tests/scenario_tests/test_view_submission.py +++ b/tests/scenario_tests/test_view_submission.py @@ -14,7 +14,6 @@ ) from tests.utils import remove_os_env_temporarily, restore_os_env - body = { "type": "view_submission", "team": { diff --git a/tests/scenario_tests/test_web_client_customization.py b/tests/scenario_tests/test_web_client_customization.py index 3967ccb7a..f78c7747a 100644 --- a/tests/scenario_tests/test_web_client_customization.py +++ b/tests/scenario_tests/test_web_client_customization.py @@ -1,4 +1,5 @@ import json +import logging from time import time from urllib.parse import quote @@ -21,6 +22,7 @@ class TestWebClientCustomization: signing_secret = "secret" mock_api_server_base_url = "http://localhost:8888" signature_verifier = SignatureVerifier(signing_secret) + test_logger = logging.getLogger("test.logger") web_client = WebClient( token=valid_token, base_url=mock_api_server_base_url, @@ -69,6 +71,77 @@ def listener(ack, client): assert response.body == "" assert_auth_test_count(self, 1) + def test_web_client_logger_is_default_app_logger(self): + app = App(token=self.valid_token, signing_secret=self.signing_secret, token_verification_enabled=False) + assert app.client.logger == app.logger + + def test_web_client_logger_is_app_logger(self): + app = App( + token=self.valid_token, + signing_secret=self.signing_secret, + logger=self.test_logger, + token_verification_enabled=False, + ) + assert app.client.logger == app.logger + assert app.client.logger == self.test_logger + + def test_default_web_client_uses_bolt_framework_logger(self): + app = App(token=self.valid_token, signing_secret=self.signing_secret, token_verification_enabled=False) + app.client.base_url = self.mock_api_server_base_url + + @app.action("a") + def listener(ack, client: WebClient): + assert client.logger == app.logger + ack() + + request = self.build_valid_request() + response = app.dispatch(request) + assert response.status == 200 + assert response.body == "" + assert_auth_test_count(self, 1) + + def test_default_web_client_uses_bolt_app_custom_logger(self): + app = App( + token=self.valid_token, + signing_secret=self.signing_secret, + token_verification_enabled=False, + logger=self.test_logger, + ) + app.client.base_url = self.mock_api_server_base_url + + assert app.client.logger == app.logger + + @app.action("a") + def listener(ack, client: WebClient): + assert client.logger == app.logger + assert client.logger == self.test_logger + ack() + + request = self.build_valid_request() + response = app.dispatch(request) + assert response.status == 200 + assert response.body == "" + assert_auth_test_count(self, 1) + + def test_custom_web_client_logger_is_used_instead_of_bolt_app_logger(self): + web_client = WebClient(token=self.valid_token, base_url=self.mock_api_server_base_url, logger=self.test_logger) + app = App( + client=web_client, + signing_secret=self.signing_secret, + ) + + @app.action("a") + def listener(ack, client: WebClient): + assert client.logger == self.test_logger + assert app.logger != self.test_logger + ack() + + request = self.build_valid_request() + response = app.dispatch(request) + assert response.status == 200 + assert response.body == "" + assert_auth_test_count(self, 1) + block_actions_body = { "type": "block_actions", diff --git a/tests/scenario_tests_async/test_app.py b/tests/scenario_tests_async/test_app.py index 8a9512f74..6f3fb34f8 100644 --- a/tests/scenario_tests_async/test_app.py +++ b/tests/scenario_tests_async/test_app.py @@ -1,6 +1,6 @@ import asyncio import logging -from ssl import SSLContext +import ssl import pytest from slack_sdk import WebClient @@ -19,7 +19,7 @@ cleanup_mock_web_api_server_async, setup_mock_web_api_server_async, ) -from tests.utils import remove_os_env_temporarily, restore_os_env, get_event_loop +from tests.utils import remove_os_env_temporarily, restore_os_env class TestAsyncApp: @@ -27,16 +27,14 @@ class TestAsyncApp: valid_token = "xoxb-valid" mock_api_server_base_url = "http://localhost:8888" - @pytest.fixture - def event_loop(self): + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server_async(self) try: - setup_mock_web_api_server_async(self) - loop = get_event_loop() - yield loop - loop.close() - cleanup_mock_web_api_server_async(self) + yield # run the test here finally: + cleanup_mock_web_api_server_async(self) restore_os_env(old_os_env) def setup_method(self): @@ -187,14 +185,14 @@ def test_installation_store_conflicts(self): @pytest.mark.asyncio async def test_proxy_ssl_for_respond(self): - ssl = SSLContext() + ssl_ctx = ssl.create_default_context() app = AsyncApp( signing_secret="valid", client=AsyncWebClient( token=self.valid_token, base_url=self.mock_api_server_base_url, proxy="http://proxy-host:9000/", - ssl=ssl, + ssl=ssl_ctx, ), authorize=my_authorize, ) @@ -204,9 +202,9 @@ async def test_proxy_ssl_for_respond(self): @app.event("app_mention") async def handle(context: AsyncBoltContext, respond): assert context.respond.proxy == "http://proxy-host:9000/" - assert context.respond.ssl == ssl + assert context.respond.ssl == ssl_ctx assert respond.proxy == "http://proxy-host:9000/" - assert respond.ssl == ssl + assert respond.ssl == ssl_ctx result["called"] = True req = AsyncBoltRequest(body=app_mention_event_body, headers={}, mode="socket_mode") diff --git a/tests/scenario_tests_async/test_app_actor_user_token.py b/tests/scenario_tests_async/test_app_actor_user_token.py index 35bda0798..0028096be 100644 --- a/tests/scenario_tests_async/test_app_actor_user_token.py +++ b/tests/scenario_tests_async/test_app_actor_user_token.py @@ -23,7 +23,7 @@ assert_auth_test_count_async, setup_mock_web_api_server_async, ) -from tests.utils import remove_os_env_temporarily, restore_os_env, get_event_loop +from tests.utils import remove_os_env_temporarily, restore_os_env class TestApp: @@ -36,16 +36,14 @@ class TestApp: base_url=mock_api_server_base_url, ) - @pytest.fixture - def event_loop(self): + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server_async(self) try: - setup_mock_web_api_server_async(self) - loop = get_event_loop() - yield loop - loop.close() - cleanup_mock_web_api_server_async(self) + yield # run the test here finally: + cleanup_mock_web_api_server_async(self) restore_os_env(old_os_env) def generate_signature(self, body: str, timestamp: str): diff --git a/tests/scenario_tests_async/test_app_bot_only.py b/tests/scenario_tests_async/test_app_bot_only.py index 58e705bac..b350aeb2d 100644 --- a/tests/scenario_tests_async/test_app_bot_only.py +++ b/tests/scenario_tests_async/test_app_bot_only.py @@ -22,7 +22,7 @@ assert_auth_test_count_async, setup_mock_web_api_server_async, ) -from tests.utils import remove_os_env_temporarily, restore_os_env, get_event_loop +from tests.utils import remove_os_env_temporarily, restore_os_env class TestAppBotOnly: @@ -35,16 +35,14 @@ class TestAppBotOnly: base_url=mock_api_server_base_url, ) - @pytest.fixture - def event_loop(self): + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server_async(self) try: - setup_mock_web_api_server_async(self) - loop = get_event_loop() - yield loop - loop.close() - cleanup_mock_web_api_server_async(self) + yield # run the test here finally: + cleanup_mock_web_api_server_async(self) restore_os_env(old_os_env) def generate_signature(self, body: str, timestamp: str): diff --git a/tests/scenario_tests_async/test_app_custom_authorize.py b/tests/scenario_tests_async/test_app_custom_authorize.py index f1e435f63..2b0252645 100644 --- a/tests/scenario_tests_async/test_app_custom_authorize.py +++ b/tests/scenario_tests_async/test_app_custom_authorize.py @@ -26,7 +26,7 @@ assert_auth_test_count_async, setup_mock_web_api_server_async, ) -from tests.utils import remove_os_env_temporarily, restore_os_env, get_event_loop +from tests.utils import remove_os_env_temporarily, restore_os_env class TestAppCustomAuthorize: @@ -39,16 +39,14 @@ class TestAppCustomAuthorize: base_url=mock_api_server_base_url, ) - @pytest.fixture - def event_loop(self): + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server_async(self) try: - setup_mock_web_api_server_async(self) - loop = get_event_loop() - yield loop - loop.close() - cleanup_mock_web_api_server_async(self) + yield # run the test here finally: + cleanup_mock_web_api_server_async(self) restore_os_env(old_os_env) def generate_signature(self, body: str, timestamp: str): diff --git a/tests/scenario_tests_async/test_app_dispatch.py b/tests/scenario_tests_async/test_app_dispatch.py index 814d1897a..c483bed19 100644 --- a/tests/scenario_tests_async/test_app_dispatch.py +++ b/tests/scenario_tests_async/test_app_dispatch.py @@ -7,7 +7,7 @@ cleanup_mock_web_api_server_async, setup_mock_web_api_server_async, ) -from tests.utils import remove_os_env_temporarily, restore_os_env, get_event_loop +from tests.utils import remove_os_env_temporarily, restore_os_env class TestAsyncAppDispatch: @@ -16,16 +16,14 @@ class TestAsyncAppDispatch: mock_api_server_base_url = "http://localhost:8888" web_client = AsyncWebClient(token=valid_token, base_url=mock_api_server_base_url) - @pytest.fixture - def event_loop(self): + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server_async(self) try: - setup_mock_web_api_server_async(self) - loop = get_event_loop() - yield loop - loop.close() - cleanup_mock_web_api_server_async(self) + yield # run the test here finally: + cleanup_mock_web_api_server_async(self) restore_os_env(old_os_env) @pytest.mark.asyncio diff --git a/tests/scenario_tests_async/test_app_installation_store.py b/tests/scenario_tests_async/test_app_installation_store.py index 053adf52f..16670f1b2 100644 --- a/tests/scenario_tests_async/test_app_installation_store.py +++ b/tests/scenario_tests_async/test_app_installation_store.py @@ -23,7 +23,7 @@ assert_auth_test_count_async, setup_mock_web_api_server_async, ) -from tests.utils import remove_os_env_temporarily, restore_os_env, get_event_loop +from tests.utils import remove_os_env_temporarily, restore_os_env class TestApp: @@ -36,16 +36,14 @@ class TestApp: base_url=mock_api_server_base_url, ) - @pytest.fixture - def event_loop(self): + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server_async(self) try: - setup_mock_web_api_server_async(self) - loop = get_event_loop() - yield loop - loop.close() - cleanup_mock_web_api_server_async(self) + yield # run the test here finally: + cleanup_mock_web_api_server_async(self) restore_os_env(old_os_env) def generate_signature(self, body: str, timestamp: str): diff --git a/tests/scenario_tests_async/test_app_using_methods_in_class.py b/tests/scenario_tests_async/test_app_using_methods_in_class.py index a24fe9528..989de511e 100644 --- a/tests/scenario_tests_async/test_app_using_methods_in_class.py +++ b/tests/scenario_tests_async/test_app_using_methods_in_class.py @@ -18,7 +18,7 @@ assert_auth_test_count_async, setup_mock_web_api_server_async, ) -from tests.utils import remove_os_env_temporarily, restore_os_env, get_event_loop +from tests.utils import remove_os_env_temporarily, restore_os_env class TestAppUsingMethodsInClass: @@ -31,16 +31,14 @@ class TestAppUsingMethodsInClass: base_url=mock_api_server_base_url, ) - @pytest.fixture - def event_loop(self): + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server_async(self) try: - setup_mock_web_api_server_async(self) - loop = get_event_loop() - yield loop - loop.close() - cleanup_mock_web_api_server_async(self) + yield # run the test here finally: + cleanup_mock_web_api_server_async(self) restore_os_env(old_os_env) def test_inspect_behaviors(self): diff --git a/tests/scenario_tests_async/test_attachment_actions.py b/tests/scenario_tests_async/test_attachment_actions.py index f6613837b..ea07e6ed0 100644 --- a/tests/scenario_tests_async/test_attachment_actions.py +++ b/tests/scenario_tests_async/test_attachment_actions.py @@ -13,7 +13,7 @@ assert_auth_test_count_async, setup_mock_web_api_server_async, ) -from tests.utils import remove_os_env_temporarily, restore_os_env, get_event_loop +from tests.utils import remove_os_env_temporarily, restore_os_env class TestAsyncAttachmentActions: @@ -26,16 +26,14 @@ class TestAsyncAttachmentActions: base_url=mock_api_server_base_url, ) - @pytest.fixture - def event_loop(self): + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server_async(self) try: - setup_mock_web_api_server_async(self) - loop = get_event_loop() - yield loop - loop.close() - cleanup_mock_web_api_server_async(self) + yield # run the test here finally: + cleanup_mock_web_api_server_async(self) restore_os_env(old_os_env) def generate_signature(self, body: str, timestamp: str): @@ -191,7 +189,7 @@ async def test_failure_2(self): await assert_auth_test_count_async(self, 1) -# https://api.slack.com/legacy/interactive-messages +# https://docs.slack.dev/legacy/legacy-messaging/legacy-making-messages-interactive/ body = { "type": "interactive_message", "actions": [ diff --git a/tests/scenario_tests_async/test_authorize.py b/tests/scenario_tests_async/test_authorize.py index 2cd18531b..9d6e3d4af 100644 --- a/tests/scenario_tests_async/test_authorize.py +++ b/tests/scenario_tests_async/test_authorize.py @@ -16,7 +16,7 @@ assert_auth_test_count_async, setup_mock_web_api_server_async, ) -from tests.utils import remove_os_env_temporarily, restore_os_env, get_event_loop +from tests.utils import remove_os_env_temporarily, restore_os_env valid_token = "xoxb-valid" valid_user_token = "xoxp-valid" @@ -60,16 +60,14 @@ class TestAsyncAuthorize: base_url=mock_api_server_base_url, ) - @pytest.fixture - def event_loop(self): + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server_async(self) try: - setup_mock_web_api_server_async(self) - loop = get_event_loop() - yield loop - loop.close() - cleanup_mock_web_api_server_async(self) + yield # run the test here finally: + cleanup_mock_web_api_server_async(self) restore_os_env(old_os_env) def generate_signature(self, body: str, timestamp: str): diff --git a/tests/scenario_tests_async/test_block_actions.py b/tests/scenario_tests_async/test_block_actions.py index 716a5a80b..6441b2e4b 100644 --- a/tests/scenario_tests_async/test_block_actions.py +++ b/tests/scenario_tests_async/test_block_actions.py @@ -16,7 +16,7 @@ assert_auth_test_count_async, setup_mock_web_api_server_async, ) -from tests.utils import remove_os_env_temporarily, restore_os_env, get_event_loop +from tests.utils import remove_os_env_temporarily, restore_os_env class TestAsyncBlockActions: @@ -29,16 +29,14 @@ class TestAsyncBlockActions: base_url=mock_api_server_base_url, ) - @pytest.fixture - def event_loop(self): + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server_async(self) try: - setup_mock_web_api_server_async(self) - loop = get_event_loop() - yield loop - loop.close() - cleanup_mock_web_api_server_async(self) + yield # run the test here finally: + cleanup_mock_web_api_server_async(self) restore_os_env(old_os_env) def generate_signature(self, body: str, timestamp: str): diff --git a/tests/scenario_tests_async/test_block_actions_respond.py b/tests/scenario_tests_async/test_block_actions_respond.py index 6d8f80884..b95e1bbf3 100644 --- a/tests/scenario_tests_async/test_block_actions_respond.py +++ b/tests/scenario_tests_async/test_block_actions_respond.py @@ -8,7 +8,7 @@ cleanup_mock_web_api_server_async, setup_mock_web_api_server_async, ) -from tests.utils import remove_os_env_temporarily, restore_os_env, get_event_loop +from tests.utils import remove_os_env_temporarily, restore_os_env class TestAsyncBlockActionsRespond: @@ -20,16 +20,14 @@ class TestAsyncBlockActionsRespond: base_url=mock_api_server_base_url, ) - @pytest.fixture - def event_loop(self): + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server_async(self) try: - setup_mock_web_api_server_async(self) - loop = get_event_loop() - yield loop - loop.close() - cleanup_mock_web_api_server_async(self) + yield # run the test here finally: + cleanup_mock_web_api_server_async(self) restore_os_env(old_os_env) @pytest.mark.asyncio diff --git a/tests/scenario_tests_async/test_block_suggestion.py b/tests/scenario_tests_async/test_block_suggestion.py index fab3b48ec..2450957f4 100644 --- a/tests/scenario_tests_async/test_block_suggestion.py +++ b/tests/scenario_tests_async/test_block_suggestion.py @@ -14,7 +14,7 @@ assert_auth_test_count_async, setup_mock_web_api_server_async, ) -from tests.utils import remove_os_env_temporarily, restore_os_env, get_event_loop +from tests.utils import remove_os_env_temporarily, restore_os_env class TestAsyncBlockSuggestion: @@ -27,16 +27,14 @@ class TestAsyncBlockSuggestion: base_url=mock_api_server_base_url, ) - @pytest.fixture - def event_loop(self): + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server_async(self) try: - setup_mock_web_api_server_async(self) - loop = get_event_loop() - yield loop - loop.close() - cleanup_mock_web_api_server_async(self) + yield # run the test here finally: + cleanup_mock_web_api_server_async(self) restore_os_env(old_os_env) def generate_signature(self, body: str, timestamp: str): diff --git a/tests/scenario_tests_async/test_dialogs.py b/tests/scenario_tests_async/test_dialogs.py index 1a3573d00..110fca45a 100644 --- a/tests/scenario_tests_async/test_dialogs.py +++ b/tests/scenario_tests_async/test_dialogs.py @@ -13,7 +13,7 @@ assert_auth_test_count_async, setup_mock_web_api_server_async, ) -from tests.utils import remove_os_env_temporarily, restore_os_env, get_event_loop +from tests.utils import remove_os_env_temporarily, restore_os_env class TestAsyncAttachmentActions: @@ -26,16 +26,14 @@ class TestAsyncAttachmentActions: base_url=mock_api_server_base_url, ) - @pytest.fixture - def event_loop(self): + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server_async(self) try: - setup_mock_web_api_server_async(self) - loop = get_event_loop() - yield loop - loop.close() - cleanup_mock_web_api_server_async(self) + yield # run the test here finally: + cleanup_mock_web_api_server_async(self) restore_os_env(old_os_env) def generate_signature(self, body: str, timestamp: str): diff --git a/tests/scenario_tests_async/test_error_handler.py b/tests/scenario_tests_async/test_error_handler.py index 00cf5c07d..fb0b9ddae 100644 --- a/tests/scenario_tests_async/test_error_handler.py +++ b/tests/scenario_tests_async/test_error_handler.py @@ -16,7 +16,7 @@ cleanup_mock_web_api_server, setup_mock_web_api_server_async, ) -from tests.utils import remove_os_env_temporarily, restore_os_env, get_event_loop +from tests.utils import remove_os_env_temporarily, restore_os_env class TestAsyncErrorHandler: @@ -29,16 +29,14 @@ class TestAsyncErrorHandler: base_url=mock_api_server_base_url, ) - @pytest.fixture - def event_loop(self): + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server_async(self) try: - setup_mock_web_api_server_async(self) - loop = get_event_loop() - yield loop - loop.close() - cleanup_mock_web_api_server_async(self) + yield # run the test here finally: + cleanup_mock_web_api_server_async(self) restore_os_env(old_os_env) # ---------------- diff --git a/tests/scenario_tests_async/test_events.py b/tests/scenario_tests_async/test_events.py index 774d526ee..0cdaa0fac 100644 --- a/tests/scenario_tests_async/test_events.py +++ b/tests/scenario_tests_async/test_events.py @@ -19,7 +19,7 @@ assert_auth_test_count_async, setup_mock_web_api_server_async, ) -from tests.utils import remove_os_env_temporarily, restore_os_env, get_event_loop +from tests.utils import remove_os_env_temporarily, restore_os_env class TestAsyncEvents: @@ -32,16 +32,14 @@ class TestAsyncEvents: base_url=mock_api_server_base_url, ) - @pytest.fixture - def event_loop(self): + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server_async(self) try: - setup_mock_web_api_server_async(self) - loop = get_event_loop() - yield loop - loop.close() - cleanup_mock_web_api_server_async(self) + yield # run the test here finally: + cleanup_mock_web_api_server_async(self) restore_os_env(old_os_env) def generate_signature(self, body: str, timestamp: str): diff --git a/tests/scenario_tests_async/test_events_assistant.py b/tests/scenario_tests_async/test_events_assistant.py index ac2c734c5..9e1176c74 100644 --- a/tests/scenario_tests_async/test_events_assistant.py +++ b/tests/scenario_tests_async/test_events_assistant.py @@ -1,20 +1,22 @@ import asyncio +from typing import Awaitable, Callable, Optional import pytest from slack_sdk.web.async_client import AsyncWebClient -from slack_bolt.app.async_app import AsyncApp -from slack_bolt.context.async_context import AsyncBoltContext -from slack_bolt.context.say.async_say import AsyncSay -from slack_bolt.context.set_status.async_set_status import AsyncSetStatus -from slack_bolt.context.set_suggested_prompts.async_set_suggested_prompts import AsyncSetSuggestedPrompts -from slack_bolt.middleware.assistant.async_assistant import AsyncAssistant -from slack_bolt.request.async_request import AsyncBoltRequest -from tests.mock_web_api_server import ( - cleanup_mock_web_api_server_async, - setup_mock_web_api_server_async, +from slack_bolt.async_app import ( + AsyncApp, + AsyncAssistant, + AsyncBoltContext, + AsyncBoltRequest, + AsyncSay, + AsyncSetStatus, + AsyncSetSuggestedPrompts, ) -from tests.utils import remove_os_env_temporarily, restore_os_env, get_event_loop +from slack_bolt.middleware.async_middleware import AsyncMiddleware +from slack_bolt.response import BoltResponse +from tests.mock_web_api_server import cleanup_mock_web_api_server_async, setup_mock_web_api_server_async +from tests.utils import remove_os_env_temporarily, restore_os_env class TestAsyncEventsAssistant: @@ -25,38 +27,34 @@ class TestAsyncEventsAssistant: base_url=mock_api_server_base_url, ) - @pytest.fixture - def event_loop(self): + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server_async(self) + try: - setup_mock_web_api_server_async(self) - loop = get_event_loop() - yield loop - loop.close() - cleanup_mock_web_api_server_async(self) + yield # run the test here finally: + cleanup_mock_web_api_server_async(self) restore_os_env(old_os_env) @pytest.mark.asyncio - async def test_assistant_events(self): + async def test_thread_started(self): app = AsyncApp(client=self.web_client) - assistant = AsyncAssistant() - - state = {"called": False} - - async def assert_target_called(): - count = 0 - while state["called"] is False and count < 20: - await asyncio.sleep(0.1) - count += 1 - assert state["called"] is True - state["called"] = False + listener_called = asyncio.Event() @assistant.thread_started - async def start_thread(say: AsyncSay, set_suggested_prompts: AsyncSetSuggestedPrompts, context: AsyncBoltContext): + async def start_thread( + say: AsyncSay, + set_suggested_prompts: AsyncSetSuggestedPrompts, + set_status: AsyncSetStatus, + context: AsyncBoltContext, + ): assert context.channel_id == "D111" assert context.thread_ts == "1726133698.626339" + assert set_status.thread_ts == context.thread_ts + assert say.thread_ts == context.thread_ts await say("Hi, how can I help you today?") await set_suggested_prompts( prompts=[{"title": "What does SLACK stand for?", "message": "What does SLACK stand for?"}] @@ -65,58 +63,265 @@ async def start_thread(say: AsyncSay, set_suggested_prompts: AsyncSetSuggestedPr prompts=[{"title": "What does SLACK stand for?", "message": "What does SLACK stand for?"}], title="foo", ) - state["called"] = True + listener_called.set() + + app.assistant(assistant) + + request = AsyncBoltRequest(body=thread_started_event_body, mode="socket_mode") + response = await app.async_dispatch(request) + assert response.status == 200 + assert (await asyncio.wait_for(listener_called.wait(), timeout=0.1)) is True + + @pytest.mark.asyncio + async def test_thread_context_changed(self): + app = AsyncApp(client=self.web_client) + assistant = AsyncAssistant() + listener_called = asyncio.Event() @assistant.thread_context_changed - async def handle_user_message(context: AsyncBoltContext): + async def handle_thread_context_changed(context: AsyncBoltContext): assert context.channel_id == "D111" assert context.thread_ts == "1726133698.626339" - state["called"] = True + listener_called.set() + + app.assistant(assistant) + + request = AsyncBoltRequest(body=thread_context_changed_event_body, mode="socket_mode") + response = await app.async_dispatch(request) + assert response.status == 200 + assert (await asyncio.wait_for(listener_called.wait(), timeout=0.1)) is True + + @pytest.mark.asyncio + async def test_user_message(self): + app = AsyncApp(client=self.web_client) + assistant = AsyncAssistant() + listener_called = asyncio.Event() @assistant.user_message async def handle_user_message(say: AsyncSay, set_status: AsyncSetStatus, context: AsyncBoltContext): assert context.channel_id == "D111" assert context.thread_ts == "1726133698.626339" + assert say.thread_ts == context.thread_ts try: await set_status("is typing...") await say("Here you are!") - state["called"] = True + listener_called.set() except Exception as e: - await say(f"Oops, something went wrong (error: {e}") + await say(f"Oops, something went wrong (error: {e})") app.assistant(assistant) - request = AsyncBoltRequest(body=thread_started_event_body, mode="socket_mode") + request = AsyncBoltRequest(body=user_message_event_body, mode="socket_mode") response = await app.async_dispatch(request) assert response.status == 200 - await assert_target_called() + assert (await asyncio.wait_for(listener_called.wait(), timeout=0.1)) is True - request = AsyncBoltRequest(body=thread_context_changed_event_body, mode="socket_mode") - response = await app.async_dispatch(request) - assert response.status == 200 - await assert_target_called() + @pytest.mark.asyncio + async def test_user_message_with_assistant_thread(self): + app = AsyncApp(client=self.web_client) + assistant = AsyncAssistant() + listener_called = asyncio.Event() - request = AsyncBoltRequest(body=user_message_event_body, mode="socket_mode") + @assistant.user_message + async def handle_user_message(say: AsyncSay, set_status: AsyncSetStatus, context: AsyncBoltContext): + assert context.channel_id == "D111" + assert context.thread_ts == "1726133698.626339" + assert say.thread_ts == context.thread_ts + try: + await set_status("is typing...") + await say("Here you are!") + listener_called.set() + except Exception as e: + await say(f"Oops, something went wrong (error: {e})") + + app.assistant(assistant) + + request = AsyncBoltRequest(body=user_message_event_body_with_assistant_thread, mode="socket_mode") response = await app.async_dispatch(request) assert response.status == 200 - await assert_target_called() + assert (await asyncio.wait_for(listener_called.wait(), timeout=0.1)) is True - request = AsyncBoltRequest(body=user_message_event_body_with_assistant_thread, mode="socket_mode") + @pytest.mark.asyncio + async def test_message_changed(self): + app = AsyncApp(client=self.web_client) + assistant = AsyncAssistant() + listener_called = asyncio.Event() + + @assistant.user_message + async def handle_user_message(): + listener_called.set() + + @assistant.bot_message + async def handle_bot_message(): + listener_called.set() + + app.assistant(assistant) + + request = AsyncBoltRequest(body=user_message_event_body_with_action_token, mode="socket_mode") response = await app.async_dispatch(request) assert response.status == 200 - await assert_target_called() + await asyncio.sleep(0.1) + assert listener_called.is_set() + listener_called.clear() request = AsyncBoltRequest(body=message_changed_event_body, mode="socket_mode") response = await app.async_dispatch(request) assert response.status == 200 + await asyncio.sleep(0.1) + assert not listener_called.is_set() + + @pytest.mark.asyncio + async def test_channel_user_message_ignored(self): + app = AsyncApp(client=self.web_client) + assistant = AsyncAssistant() + listener_called = asyncio.Event() + + @assistant.user_message + async def handle_user_message(): + listener_called.set() + + @assistant.bot_message + async def handle_bot_message(): + listener_called.set() + + app.assistant(assistant) request = AsyncBoltRequest(body=channel_user_message_event_body, mode="socket_mode") response = await app.async_dispatch(request) assert response.status == 404 + await asyncio.sleep(0.1) + assert not listener_called.is_set() + + @pytest.mark.asyncio + async def test_channel_message_changed_ignored(self): + app = AsyncApp(client=self.web_client) + assistant = AsyncAssistant() + listener_called = asyncio.Event() + + @assistant.user_message + async def handle_user_message(): + listener_called.set() + + @assistant.bot_message + async def handle_bot_message(): + listener_called.set() + + app.assistant(assistant) request = AsyncBoltRequest(body=channel_message_changed_event_body, mode="socket_mode") response = await app.async_dispatch(request) assert response.status == 404 + await asyncio.sleep(0.1) + assert not listener_called.is_set() + + @pytest.mark.asyncio + async def test_assistant_events_kwargs_disabled(self): + app = AsyncApp(client=self.web_client, attaching_conversation_kwargs_enabled=False) + listener_called = asyncio.Event() + + @app.event("assistant_thread_started") + async def start_thread(context: AsyncBoltContext): + assert context.get("set_status") is None + assert context.get("set_title") is None + assert context.get("set_suggested_prompts") is None + assert context.get("get_thread_context") is None + assert context.get("save_thread_context") is None + listener_called.set() + + request = AsyncBoltRequest(body=thread_started_event_body, mode="socket_mode") + response = await app.async_dispatch(request) + assert response.status == 200 + assert (await asyncio.wait_for(listener_called.wait(), timeout=0.1)) is True + + @pytest.mark.asyncio + async def test_assistant_with_custom_listener_middleware(self): + app = AsyncApp(client=self.web_client) + assistant = AsyncAssistant() + listener_called = asyncio.Event() + middleware_called = asyncio.Event() + + class TestAsyncMiddleware(AsyncMiddleware): + async def async_process( + self, + *, + req: AsyncBoltRequest, + resp: BoltResponse, + next: Callable[[], Awaitable[BoltResponse]], + ) -> Optional[BoltResponse]: + middleware_called.set() + # Verify assistant utilities are available (set by _AsyncAssistantMiddleware before this) + assert req.context.get("set_status") is not None + assert req.context.get("set_title") is not None + assert req.context.get("set_suggested_prompts") is not None + assert req.context.get("get_thread_context") is not None + assert req.context.get("save_thread_context") is not None + return await next() + + @assistant.thread_started(middleware=[TestAsyncMiddleware()]) + async def start_thread(say: AsyncSay, set_suggested_prompts: AsyncSetSuggestedPrompts, context: AsyncBoltContext): + assert context.channel_id == "D111" + assert context.thread_ts == "1726133698.626339" + assert say.thread_ts == context.thread_ts + await say("Hi, how can I help you today?") + await set_suggested_prompts( + prompts=[{"title": "What does SLACK stand for?", "message": "What does SLACK stand for?"}] + ) + listener_called.set() + + @assistant.user_message(middleware=[TestAsyncMiddleware()]) + async def handle_user_message(say: AsyncSay, set_status: AsyncSetStatus, context: AsyncBoltContext): + assert context.channel_id == "D111" + assert context.thread_ts == "1726133698.626339" + assert say.thread_ts == context.thread_ts + await set_status("is typing...") + await say("Here you are!") + listener_called.set() + + app.assistant(assistant) + + request = AsyncBoltRequest(body=thread_started_event_body, mode="socket_mode") + response = await app.async_dispatch(request) + assert response.status == 200 + assert (await asyncio.wait_for(listener_called.wait(), timeout=0.1)) is True + assert (await asyncio.wait_for(middleware_called.wait(), timeout=0.1)) is True + + listener_called.clear() + middleware_called.clear() + + request = AsyncBoltRequest(body=user_message_event_body, mode="socket_mode") + response = await app.async_dispatch(request) + assert response.status == 200 + assert (await asyncio.wait_for(listener_called.wait(), timeout=0.1)) is True + assert (await asyncio.wait_for(middleware_called.wait(), timeout=0.1)) is True + + @pytest.mark.asyncio + async def test_assistant_custom_middleware_can_short_circuit(self): + app = AsyncApp(client=self.web_client) + assistant = AsyncAssistant() + listener_called = asyncio.Event() + + class BlockingAsyncMiddleware(AsyncMiddleware): + async def async_process( + self, + *, + req: AsyncBoltRequest, + resp: BoltResponse, + next: Callable[[], Awaitable[BoltResponse]], + ) -> Optional[BoltResponse]: + # Intentionally not calling next() to short-circuit + return BoltResponse(status=200) + + @assistant.thread_started(middleware=[BlockingAsyncMiddleware()]) + async def start_thread(say: AsyncSay, context: AsyncBoltContext): + listener_called.set() + + app.assistant(assistant) + + request = AsyncBoltRequest(body=thread_started_event_body, mode="socket_mode") + response = await app.async_dispatch(request) + assert response.status == 200 + await asyncio.sleep(0.1) + assert not listener_called.is_set() def build_payload(event: dict) -> dict: @@ -207,6 +412,25 @@ def build_payload(event: dict) -> dict: ) +user_message_event_body_with_action_token = build_payload( + { + "user": "W222", + "type": "message", + "ts": "1726133700.887259", + "text": "When Slack was released?", + "team": "T111", + "user_team": "T111", + "source_team": "T222", + "user_profile": {}, + "thread_ts": "1726133698.626339", + "parent_user_id": "W222", + "channel": "D111", + "event_ts": "1726133700.887259", + "channel_type": "im", + "assistant_thread": {"action_token": "10647138185092.960436384805.afce3599"}, + } +) + message_changed_event_body = build_payload( { "type": "message", @@ -259,7 +483,7 @@ def build_payload(event: dict) -> dict: "user_profile": {}, "thread_ts": "1726133698.626339", "parent_user_id": "W222", - "channel": "D111", + "channel": "C111", "event_ts": "1726133700.887259", "channel_type": "channel", } @@ -294,7 +518,7 @@ def build_payload(event: dict) -> dict: "reply_users": ["U222", "W111"], "is_locked": False, }, - "channel": "D111", + "channel": "C111", "hidden": True, "ts": "1726133701.028300", "event_ts": "1726133701.028300", diff --git a/tests/scenario_tests_async/test_events_assistant_without_middleware.py b/tests/scenario_tests_async/test_events_assistant_without_middleware.py new file mode 100644 index 000000000..d72b09b04 --- /dev/null +++ b/tests/scenario_tests_async/test_events_assistant_without_middleware.py @@ -0,0 +1,288 @@ +import asyncio + +import pytest +from slack_sdk.web.async_client import AsyncWebClient + +from slack_bolt.async_app import ( + AsyncApp, + AsyncBoltContext, + AsyncBoltRequest, + AsyncGetThreadContext, + AsyncSaveThreadContext, + AsyncSay, + AsyncSetStatus, + AsyncSetSuggestedPrompts, + AsyncSetTitle, +) +from tests.mock_web_api_server import cleanup_mock_web_api_server_async, setup_mock_web_api_server_async +from tests.scenario_tests_async.test_events_assistant import ( + channel_message_changed_event_body, + channel_user_message_event_body, + message_changed_event_body, + thread_context_changed_event_body, + thread_started_event_body, + user_message_event_body, + user_message_event_body_with_assistant_thread, +) +from tests.utils import remove_os_env_temporarily, restore_os_env + + +class TestAsyncEventsAssistantWithoutMiddleware: + valid_token = "xoxb-valid" + mock_api_server_base_url = "http://localhost:8888" + web_client = AsyncWebClient( + token=valid_token, + base_url=mock_api_server_base_url, + ) + + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): + old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server_async(self) + + try: + yield + finally: + cleanup_mock_web_api_server_async(self) + restore_os_env(old_os_env) + + @pytest.mark.asyncio + async def test_thread_started(self): + app = AsyncApp(client=self.web_client) + listener_called = asyncio.Event() + + @app.event("assistant_thread_started") + async def handle_assistant_thread_started( + say: AsyncSay, + set_status: AsyncSetStatus, + set_title: AsyncSetTitle, + set_suggested_prompts: AsyncSetSuggestedPrompts, + get_thread_context: AsyncGetThreadContext, + save_thread_context: AsyncSaveThreadContext, + context: AsyncBoltContext, + ): + assert context.channel_id == "D111" + assert context.thread_ts == "1726133698.626339" + assert say.thread_ts == context.thread_ts + assert set_status is not None + assert set_title is not None + assert set_suggested_prompts is not None + assert get_thread_context is not None + assert save_thread_context is not None + await say("Hi, how can I help you today?") + await set_suggested_prompts( + prompts=[{"title": "What does SLACK stand for?", "message": "What does SLACK stand for?"}] + ) + listener_called.set() + + request = AsyncBoltRequest(body=thread_started_event_body, mode="socket_mode") + response = await app.async_dispatch(request) + assert response.status == 200 + assert (await asyncio.wait_for(listener_called.wait(), timeout=0.1)) is True + + @pytest.mark.asyncio + async def test_thread_context_changed(self): + app = AsyncApp(client=self.web_client) + listener_called = asyncio.Event() + + @app.event("assistant_thread_context_changed") + async def handle_assistant_thread_context_changed( + say: AsyncSay, + set_status: AsyncSetStatus, + set_title: AsyncSetTitle, + set_suggested_prompts: AsyncSetSuggestedPrompts, + get_thread_context: AsyncGetThreadContext, + save_thread_context: AsyncSaveThreadContext, + context: AsyncBoltContext, + ): + assert context.channel_id == "D111" + assert context.thread_ts == "1726133698.626339" + assert say.thread_ts == context.thread_ts + assert set_status is not None + assert set_title is not None + assert set_suggested_prompts is not None + assert get_thread_context is not None + assert save_thread_context is not None + listener_called.set() + + request = AsyncBoltRequest(body=thread_context_changed_event_body, mode="socket_mode") + response = await app.async_dispatch(request) + assert response.status == 200 + assert (await asyncio.wait_for(listener_called.wait(), timeout=0.1)) is True + + @pytest.mark.asyncio + async def test_user_message(self): + app = AsyncApp(client=self.web_client) + listener_called = asyncio.Event() + + @app.message("") + async def handle_message( + say: AsyncSay, + set_status: AsyncSetStatus, + set_title: AsyncSetTitle, + set_suggested_prompts: AsyncSetSuggestedPrompts, + get_thread_context: AsyncGetThreadContext, + save_thread_context: AsyncSaveThreadContext, + context: AsyncBoltContext, + ): + assert context.channel_id == "D111" + assert context.thread_ts == "1726133698.626339" + assert say.thread_ts == context.thread_ts + assert set_status is not None + assert set_title is not None + assert set_suggested_prompts is not None + assert get_thread_context is not None + assert save_thread_context is not None + try: + await set_status("is typing...") + await say("Here you are!") + listener_called.set() + except Exception as e: + await say(f"Oops, something went wrong (error: {e})") + + request = AsyncBoltRequest(body=user_message_event_body, mode="socket_mode") + response = await app.async_dispatch(request) + assert response.status == 200 + assert (await asyncio.wait_for(listener_called.wait(), timeout=0.1)) is True + + @pytest.mark.asyncio + async def test_user_message_with_assistant_thread(self): + app = AsyncApp(client=self.web_client) + listener_called = asyncio.Event() + + @app.message("") + async def handle_message( + say: AsyncSay, + set_status: AsyncSetStatus, + set_title: AsyncSetTitle, + set_suggested_prompts: AsyncSetSuggestedPrompts, + get_thread_context: AsyncGetThreadContext, + save_thread_context: AsyncSaveThreadContext, + context: AsyncBoltContext, + ): + assert context.channel_id == "D111" + assert context.thread_ts == "1726133698.626339" + assert say.thread_ts == context.thread_ts + assert set_status is not None + assert set_title is not None + assert set_suggested_prompts is not None + assert get_thread_context is not None + assert save_thread_context is not None + try: + await set_status("is typing...") + await say("Here you are!") + listener_called.set() + except Exception as e: + await say(f"Oops, something went wrong (error: {e})") + + request = AsyncBoltRequest(body=user_message_event_body_with_assistant_thread, mode="socket_mode") + response = await app.async_dispatch(request) + assert response.status == 200 + assert (await asyncio.wait_for(listener_called.wait(), timeout=0.1)) is True + + @pytest.mark.asyncio + async def test_message_changed(self): + app = AsyncApp(client=self.web_client) + listener_called = asyncio.Event() + + @app.event("message") + async def handle_message_event( + say: AsyncSay, + set_status: AsyncSetStatus, + set_title: AsyncSetTitle, + set_suggested_prompts: AsyncSetSuggestedPrompts, + get_thread_context: AsyncGetThreadContext, + save_thread_context: AsyncSaveThreadContext, + context: AsyncBoltContext, + ): + assert context.thread_ts == "1726133698.626339" + assert say.thread_ts == None + assert set_status is not None + assert set_title is None + assert set_suggested_prompts is None + assert get_thread_context is None + assert save_thread_context is None + listener_called.set() + + request = AsyncBoltRequest(body=message_changed_event_body, mode="socket_mode") + response = await app.async_dispatch(request) + assert response.status == 200 + assert (await asyncio.wait_for(listener_called.wait(), timeout=0.1)) is True + + @pytest.mark.asyncio + async def test_channel_user_message(self): + app = AsyncApp(client=self.web_client) + listener_called = asyncio.Event() + + @app.event("message") + async def handle_message_event( + say: AsyncSay, + set_status: AsyncSetStatus, + set_title: AsyncSetTitle, + set_suggested_prompts: AsyncSetSuggestedPrompts, + get_thread_context: AsyncGetThreadContext, + save_thread_context: AsyncSaveThreadContext, + context: AsyncBoltContext, + ): + assert context.thread_ts == "1726133698.626339" + assert say.thread_ts == None + assert set_status is not None + assert set_title is None + assert set_suggested_prompts is None + assert get_thread_context is None + assert save_thread_context is None + listener_called.set() + + request = AsyncBoltRequest(body=channel_user_message_event_body, mode="socket_mode") + response = await app.async_dispatch(request) + assert response.status == 200 + assert (await asyncio.wait_for(listener_called.wait(), timeout=0.1)) is True + + @pytest.mark.asyncio + async def test_channel_message_changed(self): + app = AsyncApp(client=self.web_client) + listener_called = asyncio.Event() + + @app.event("message") + async def handle_message_event( + say: AsyncSay, + set_status: AsyncSetStatus, + set_title: AsyncSetTitle, + set_suggested_prompts: AsyncSetSuggestedPrompts, + get_thread_context: AsyncGetThreadContext, + save_thread_context: AsyncSaveThreadContext, + context: AsyncBoltContext, + ): + assert context.thread_ts == "1726133698.626339" + assert say.thread_ts == None + assert set_status is not None + assert set_title is None + assert set_suggested_prompts is None + assert get_thread_context is None + assert save_thread_context is None + listener_called.set() + + request = AsyncBoltRequest(body=channel_message_changed_event_body, mode="socket_mode") + response = await app.async_dispatch(request) + assert response.status == 200 + assert (await asyncio.wait_for(listener_called.wait(), timeout=0.1)) is True + + @pytest.mark.asyncio + async def test_assistant_events_conversation_kwargs_disabled(self): + app = AsyncApp(client=self.web_client, attaching_conversation_kwargs_enabled=False) + + listener_called = asyncio.Event() + + @app.event("assistant_thread_started") + async def start_thread(context: AsyncBoltContext): + assert context.get("set_status") is None + assert context.get("set_title") is None + assert context.get("set_suggested_prompts") is None + assert context.get("get_thread_context") is None + assert context.get("save_thread_context") is None + listener_called.set() + + request = AsyncBoltRequest(body=thread_started_event_body, mode="socket_mode") + response = await app.async_dispatch(request) + assert response.status == 200 + assert (await asyncio.wait_for(listener_called.wait(), timeout=0.1)) is True diff --git a/tests/scenario_tests_async/test_events_ignore_self.py b/tests/scenario_tests_async/test_events_ignore_self.py index 14fcb509a..fa398a907 100644 --- a/tests/scenario_tests_async/test_events_ignore_self.py +++ b/tests/scenario_tests_async/test_events_ignore_self.py @@ -11,7 +11,7 @@ assert_auth_test_count_async, setup_mock_web_api_server_async, ) -from tests.utils import remove_os_env_temporarily, restore_os_env, get_event_loop +from tests.utils import remove_os_env_temporarily, restore_os_env class TestAsyncEventsIgnoreSelf: @@ -22,16 +22,14 @@ class TestAsyncEventsIgnoreSelf: base_url=mock_api_server_base_url, ) - @pytest.fixture - def event_loop(self): + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server_async(self) try: - setup_mock_web_api_server_async(self) - loop = get_event_loop() - yield loop - loop.close() - cleanup_mock_web_api_server_async(self) + yield # run the test here finally: + cleanup_mock_web_api_server_async(self) restore_os_env(old_os_env) @pytest.mark.asyncio @@ -91,6 +89,7 @@ async def test_self_events_disabled(self): "type": "message", "channel": "C111", "ts": "1599529504.000400", + "channel_type": "channel", }, "reaction": "heart_eyes", "item_user": "W111", diff --git a/tests/scenario_tests_async/test_events_org_apps.py b/tests/scenario_tests_async/test_events_org_apps.py index 187c59b77..e3706d9c6 100644 --- a/tests/scenario_tests_async/test_events_org_apps.py +++ b/tests/scenario_tests_async/test_events_org_apps.py @@ -20,7 +20,7 @@ assert_auth_test_count_async, setup_mock_web_api_server_async, ) -from tests.utils import remove_os_env_temporarily, restore_os_env, get_event_loop +from tests.utils import remove_os_env_temporarily, restore_os_env valid_token = "xoxb-valid" @@ -57,16 +57,14 @@ class TestAsyncOrgApps: signature_verifier = SignatureVerifier(signing_secret) web_client = AsyncWebClient(token=None, base_url=mock_api_server_base_url) - @pytest.fixture - def event_loop(self): + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server_async(self) try: - setup_mock_web_api_server_async(self) - loop = get_event_loop() - yield loop - loop.close() - cleanup_mock_web_api_server_async(self) + yield # run the test here finally: + cleanup_mock_web_api_server_async(self) restore_os_env(old_os_env) def generate_signature(self, body: str, timestamp: str): diff --git a/tests/scenario_tests_async/test_events_request_verification.py b/tests/scenario_tests_async/test_events_request_verification.py index f314852b0..51ccfcd98 100644 --- a/tests/scenario_tests_async/test_events_request_verification.py +++ b/tests/scenario_tests_async/test_events_request_verification.py @@ -13,7 +13,7 @@ assert_auth_test_count_async, setup_mock_web_api_server_async, ) -from tests.utils import remove_os_env_temporarily, restore_os_env, get_event_loop +from tests.utils import remove_os_env_temporarily, restore_os_env class TestAsyncEventsRequestVerification: @@ -23,16 +23,14 @@ class TestAsyncEventsRequestVerification: signature_verifier = SignatureVerifier(signing_secret) web_client = AsyncWebClient(token=valid_token, base_url=mock_api_server_base_url) - @pytest.fixture - def event_loop(self): + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server_async(self) try: - setup_mock_web_api_server_async(self) - loop = get_event_loop() - yield loop - loop.close() - cleanup_mock_web_api_server_async(self) + yield # run the test here finally: + cleanup_mock_web_api_server_async(self) restore_os_env(old_os_env) def generate_signature(self, body: str, timestamp: str): diff --git a/tests/scenario_tests_async/test_events_say_stream.py b/tests/scenario_tests_async/test_events_say_stream.py new file mode 100644 index 000000000..6abcfad88 --- /dev/null +++ b/tests/scenario_tests_async/test_events_say_stream.py @@ -0,0 +1,235 @@ +import asyncio +import json +from urllib.parse import quote + +import pytest +from slack_sdk.web.async_client import AsyncWebClient + +from slack_bolt.async_app import AsyncApp, AsyncAssistant, AsyncBoltContext, AsyncBoltRequest, AsyncSayStream +from tests.mock_web_api_server import cleanup_mock_web_api_server_async, setup_mock_web_api_server_async +from tests.scenario_tests_async.test_app import app_mention_event_body +from tests.scenario_tests_async.test_events_assistant import thread_started_event_body +from tests.scenario_tests_async.test_events_assistant import user_message_event_body as threaded_user_message_event_body +from tests.scenario_tests_async.test_message_bot import bot_message_event_payload, user_message_event_payload +from tests.scenario_tests_async.test_view_submission import body as view_submission_body +from tests.utils import remove_os_env_temporarily, restore_os_env + + +class TestAsyncEventsSayStream: + valid_token = "xoxb-valid" + mock_api_server_base_url = "http://localhost:8888" + web_client = AsyncWebClient( + token=valid_token, + base_url=mock_api_server_base_url, + ) + + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): + old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server_async(self) + try: + yield + finally: + cleanup_mock_web_api_server_async(self) + restore_os_env(old_os_env) + + @pytest.mark.asyncio + async def test_say_stream_injected_for_app_mention(self): + app = AsyncApp(client=self.web_client) + listener_called = asyncio.Event() + + @app.event("app_mention") + async def handle_mention(say_stream: AsyncSayStream, context: AsyncBoltContext): + assert say_stream is not None + assert isinstance(say_stream, AsyncSayStream) + assert say_stream == context.say_stream + assert say_stream.channel == "C111" + assert say_stream.thread_ts == "1595926230.009600" + assert say_stream.recipient_team_id == context.team_id + assert say_stream.recipient_user_id == context.user_id + listener_called.set() + + request = AsyncBoltRequest(body=app_mention_event_body, mode="socket_mode") + response = await app.async_dispatch(request) + assert response.status == 200 + assert (await asyncio.wait_for(listener_called.wait(), timeout=0.1)) is True + + @pytest.mark.asyncio + async def test_say_stream_with_org_level_install(self): + app = AsyncApp(client=self.web_client) + listener_called = asyncio.Event() + + @app.event("app_mention") + async def handle_mention(say_stream: AsyncSayStream, context: AsyncBoltContext): + assert context.team_id is None + assert context.enterprise_id == "E111" + assert say_stream is not None + assert isinstance(say_stream, AsyncSayStream) + assert say_stream.recipient_team_id == "E111" + listener_called.set() + + request = AsyncBoltRequest(body=org_app_mention_event_body, mode="socket_mode") + response = await app.async_dispatch(request) + assert response.status == 200 + assert (await asyncio.wait_for(listener_called.wait(), timeout=0.1)) is True + + @pytest.mark.asyncio + async def test_say_stream_injected_for_threaded_message(self): + app = AsyncApp(client=self.web_client) + listener_called = asyncio.Event() + + @app.event("message") + async def handle_message(say_stream: AsyncSayStream, context: AsyncBoltContext): + assert say_stream is not None + assert isinstance(say_stream, AsyncSayStream) + assert say_stream == context.say_stream + assert say_stream.channel == "D111" + assert say_stream.thread_ts == "1726133698.626339" + assert say_stream.recipient_team_id == context.team_id + assert say_stream.recipient_user_id == context.user_id + listener_called.set() + + request = AsyncBoltRequest(body=threaded_user_message_event_body, mode="socket_mode") + response = await app.async_dispatch(request) + assert response.status == 200 + assert (await asyncio.wait_for(listener_called.wait(), timeout=0.1)) is True + + @pytest.mark.asyncio + async def test_say_stream_in_user_message(self): + app = AsyncApp(client=self.web_client) + listener_called = asyncio.Event() + + @app.message("") + async def handle_user_message(say_stream: AsyncSayStream, context: AsyncBoltContext): + assert say_stream is not None + assert isinstance(say_stream, AsyncSayStream) + assert say_stream == context.say_stream + assert say_stream.channel == "C111" + assert say_stream.thread_ts == "1610261659.001400" + assert say_stream.recipient_team_id == context.team_id + assert say_stream.recipient_user_id == context.user_id + listener_called.set() + + request = AsyncBoltRequest(body=user_message_event_payload, mode="socket_mode") + response = await app.async_dispatch(request) + assert response.status == 200 + assert (await asyncio.wait_for(listener_called.wait(), timeout=0.1)) is True + + @pytest.mark.asyncio + async def test_say_stream_in_bot_message(self): + app = AsyncApp(client=self.web_client) + listener_called = asyncio.Event() + + @app.message("") + async def handle_user_message(say_stream: AsyncSayStream, context: AsyncBoltContext): + assert say_stream is not None + assert isinstance(say_stream, AsyncSayStream) + assert say_stream == context.say_stream + assert say_stream.channel == "C111" + assert say_stream.thread_ts == "1610261539.000900" + assert say_stream.recipient_team_id == context.team_id + assert say_stream.recipient_user_id == context.user_id + listener_called.set() + + request = AsyncBoltRequest(body=bot_message_event_payload, mode="socket_mode") + response = await app.async_dispatch(request) + assert response.status == 200 + assert (await asyncio.wait_for(listener_called.wait(), timeout=0.1)) is True + + @pytest.mark.asyncio + async def test_say_stream_in_assistant_thread_started(self): + app = AsyncApp(client=self.web_client) + assistant = AsyncAssistant() + listener_called = asyncio.Event() + + @assistant.thread_started + async def start_thread(say_stream: AsyncSayStream, context: AsyncBoltContext): + assert say_stream is not None + assert isinstance(say_stream, AsyncSayStream) + assert say_stream == context.say_stream + assert say_stream.channel == "D111" + assert say_stream.thread_ts == "1726133698.626339" + assert say_stream.recipient_team_id == context.team_id + assert say_stream.recipient_user_id == context.user_id + listener_called.set() + + app.assistant(assistant) + + request = AsyncBoltRequest(body=thread_started_event_body, mode="socket_mode") + response = await app.async_dispatch(request) + assert response.status == 200 + assert (await asyncio.wait_for(listener_called.wait(), timeout=0.1)) is True + + @pytest.mark.asyncio + async def test_say_stream_in_assistant_user_message(self): + app = AsyncApp(client=self.web_client) + assistant = AsyncAssistant() + listener_called = asyncio.Event() + + @assistant.user_message + async def handle_user_message(say_stream: AsyncSayStream, context: AsyncBoltContext): + assert say_stream is not None + assert isinstance(say_stream, AsyncSayStream) + assert say_stream == context.say_stream + assert say_stream.channel == "D111" + assert say_stream.thread_ts == "1726133698.626339" + assert say_stream.recipient_team_id == context.team_id + assert say_stream.recipient_user_id == context.user_id + listener_called.set() + + app.assistant(assistant) + + request = AsyncBoltRequest(body=threaded_user_message_event_body, mode="socket_mode") + response = await app.async_dispatch(request) + assert response.status == 200 + assert (await asyncio.wait_for(listener_called.wait(), timeout=0.1)) is True + + @pytest.mark.asyncio + async def test_say_stream_is_none_for_view_submission(self): + app = AsyncApp(client=self.web_client, request_verification_enabled=False) + listener_called = asyncio.Event() + + @app.view("view-id") + async def handle_view(ack, say_stream, context: AsyncBoltContext): + await ack() + assert say_stream is None + assert context.say_stream is None + listener_called.set() + + request = AsyncBoltRequest( + body=f"payload={quote(json.dumps(view_submission_body))}", + ) + response = await app.async_dispatch(request) + assert response.status == 200 + assert (await asyncio.wait_for(listener_called.wait(), timeout=0.1)) is True + + +org_app_mention_event_body = { + "token": "verification_token", + "team_id": "T111", + "enterprise_id": "E111", + "api_app_id": "A111", + "event": { + "client_msg_id": "9cbd4c5b-7ddf-4ede-b479-ad21fca66d63", + "type": "app_mention", + "text": "<@W111> Hi there!", + "user": "W222", + "ts": "1595926230.009600", + "team": "T111", + "channel": "C111", + "event_ts": "1595926230.009600", + }, + "type": "event_callback", + "event_id": "Ev111", + "event_time": 1595926230, + "authorizations": [ + { + "enterprise_id": "E111", + "team_id": None, + "user_id": "W111", + "is_bot": True, + "is_enterprise_install": True, + } + ], + "is_ext_shared_channel": False, +} diff --git a/tests/scenario_tests_async/test_events_set_status.py b/tests/scenario_tests_async/test_events_set_status.py new file mode 100644 index 000000000..0e5be3349 --- /dev/null +++ b/tests/scenario_tests_async/test_events_set_status.py @@ -0,0 +1,183 @@ +import asyncio +import json +from urllib.parse import quote + +import pytest +from slack_sdk.web.async_client import AsyncWebClient + +from slack_bolt.app.async_app import AsyncApp +from slack_bolt.async_app import AsyncAssistant +from slack_bolt.context.async_context import AsyncBoltContext +from slack_bolt.context.set_status.async_set_status import AsyncSetStatus +from slack_bolt.request.async_request import AsyncBoltRequest +from tests.mock_web_api_server import ( + assert_auth_test_count_async, + assert_received_request_count_async, + cleanup_mock_web_api_server_async, + setup_mock_web_api_server_async, +) +from tests.scenario_tests_async.test_app import app_mention_event_body +from tests.scenario_tests_async.test_events_assistant import thread_started_event_body +from tests.scenario_tests_async.test_events_assistant import user_message_event_body as threaded_user_message_event_body +from tests.scenario_tests_async.test_message_bot import bot_message_event_payload, user_message_event_payload +from tests.scenario_tests_async.test_view_submission import body as view_submission_body +from tests.utils import remove_os_env_temporarily, restore_os_env + + +class TestAsyncEventsSetStatus: + valid_token = "xoxb-valid" + mock_api_server_base_url = "http://localhost:8888" + web_client = AsyncWebClient( + token=valid_token, + base_url=mock_api_server_base_url, + ) + + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): + old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server_async(self) + try: + yield + finally: + cleanup_mock_web_api_server_async(self) + restore_os_env(old_os_env) + + @pytest.mark.asyncio + async def test_set_status_injected_for_app_mention(self): + app = AsyncApp(client=self.web_client) + + @app.event("app_mention") + async def handle_mention(set_status: AsyncSetStatus, context: AsyncBoltContext): + assert set_status is not None + assert isinstance(set_status, AsyncSetStatus) + assert set_status == context.set_status + assert set_status.channel_id == "C111" + assert set_status.thread_ts == "1595926230.009600" + await set_status(status="Thinking...") + + request = AsyncBoltRequest(body=app_mention_event_body, mode="socket_mode") + response = await app.async_dispatch(request) + assert response.status == 200 + await assert_auth_test_count_async(self, 1) + await assert_received_request_count_async(self, path="/assistant.threads.setStatus", min_count=1) + + @pytest.mark.asyncio + async def test_set_status_injected_for_threaded_message(self): + app = AsyncApp(client=self.web_client) + + @app.event("message") + async def handle_message(set_status: AsyncSetStatus, context: AsyncBoltContext): + assert set_status is not None + assert isinstance(set_status, AsyncSetStatus) + assert set_status == context.set_status + assert set_status.channel_id == "D111" + assert set_status.thread_ts == "1726133698.626339" + await set_status(status="Thinking...") + + request = AsyncBoltRequest(body=threaded_user_message_event_body, mode="socket_mode") + response = await app.async_dispatch(request) + assert response.status == 200 + await assert_auth_test_count_async(self, 1) + await assert_received_request_count_async(self, path="/assistant.threads.setStatus", min_count=1) + + @pytest.mark.asyncio + async def test_set_status_in_user_message(self): + app = AsyncApp(client=self.web_client) + + @app.message("") + async def handle_user_message(set_status: AsyncSetStatus, context: AsyncBoltContext): + assert set_status is not None + assert isinstance(set_status, AsyncSetStatus) + assert set_status == context.set_status + assert set_status.channel_id == "C111" + assert set_status.thread_ts == "1610261659.001400" + await set_status(status="Thinking...") + + request = AsyncBoltRequest(body=user_message_event_payload, mode="socket_mode") + response = await app.async_dispatch(request) + assert response.status == 200 + await assert_auth_test_count_async(self, 1) + await assert_received_request_count_async(self, path="/assistant.threads.setStatus", min_count=1) + + @pytest.mark.asyncio + async def test_set_status_in_bot_message(self): + app = AsyncApp(client=self.web_client) + + @app.message("") + async def handle_user_message(set_status: AsyncSetStatus, context: AsyncBoltContext): + assert set_status is not None + assert isinstance(set_status, AsyncSetStatus) + assert set_status == context.set_status + assert set_status.channel_id == "C111" + assert set_status.thread_ts == "1610261539.000900" + await set_status(status="Thinking...") + + request = AsyncBoltRequest(body=bot_message_event_payload, mode="socket_mode") + response = await app.async_dispatch(request) + assert response.status == 200 + await assert_auth_test_count_async(self, 1) + await assert_received_request_count_async(self, path="/assistant.threads.setStatus", min_count=1) + + @pytest.mark.asyncio + async def test_set_status_in_assistant_thread_started(self): + app = AsyncApp(client=self.web_client) + assistant = AsyncAssistant() + + @assistant.thread_started + async def start_thread(set_status: AsyncSetStatus, context: AsyncBoltContext): + assert set_status is not None + assert isinstance(set_status, AsyncSetStatus) + assert set_status == context.set_status + assert set_status.channel_id == "D111" + assert set_status.thread_ts == "1726133698.626339" + await set_status(status="Thinking...") + + app.assistant(assistant) + + request = AsyncBoltRequest(body=thread_started_event_body, mode="socket_mode") + response = await app.async_dispatch(request) + assert response.status == 200 + await assert_auth_test_count_async(self, 1) + await assert_received_request_count_async(self, path="/assistant.threads.setStatus", min_count=1) + + @pytest.mark.asyncio + async def test_set_status_in_assistant_user_message(self): + app = AsyncApp(client=self.web_client) + assistant = AsyncAssistant() + + @assistant.user_message + async def handle_user_message(set_status: AsyncSetStatus, context: AsyncBoltContext): + assert set_status is not None + assert isinstance(set_status, AsyncSetStatus) + assert set_status == context.set_status + assert set_status.channel_id == "D111" + assert set_status.thread_ts == "1726133698.626339" + await set_status(status="Thinking...") + + app.assistant(assistant) + + request = AsyncBoltRequest(body=threaded_user_message_event_body, mode="socket_mode") + response = await app.async_dispatch(request) + assert response.status == 200 + await assert_auth_test_count_async(self, 1) + await assert_received_request_count_async(self, path="/assistant.threads.setStatus", min_count=1) + + @pytest.mark.asyncio + async def test_set_status_is_none_for_view_submission(self): + app = AsyncApp(client=self.web_client, request_verification_enabled=False) + listener_called = asyncio.Event() + + @app.view("view-id") + async def handle_view(ack, set_status, context: AsyncBoltContext): + await ack() + assert set_status is None + assert context.set_status is None + listener_called.set() + + request = AsyncBoltRequest( + body=f"payload={quote(json.dumps(view_submission_body))}", + ) + response = await app.async_dispatch(request) + assert response.status == 200 + await assert_auth_test_count_async(self, 1) + assert listener_called.is_set() diff --git a/tests/scenario_tests_async/test_events_shared_channels.py b/tests/scenario_tests_async/test_events_shared_channels.py index 0112de808..ca43f979a 100644 --- a/tests/scenario_tests_async/test_events_shared_channels.py +++ b/tests/scenario_tests_async/test_events_shared_channels.py @@ -17,7 +17,7 @@ assert_auth_test_count_async, setup_mock_web_api_server_async, ) -from tests.utils import remove_os_env_temporarily, restore_os_env, get_event_loop +from tests.utils import remove_os_env_temporarily, restore_os_env valid_token = "xoxb-valid" @@ -39,16 +39,14 @@ class TestAsyncEventsSharedChannels: signature_verifier = SignatureVerifier(signing_secret) web_client = AsyncWebClient(token=None, base_url=mock_api_server_base_url) - @pytest.fixture - def event_loop(self): + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server_async(self) try: - setup_mock_web_api_server_async(self) - loop = get_event_loop() - yield loop - loop.close() - cleanup_mock_web_api_server_async(self) + yield # run the test here finally: + cleanup_mock_web_api_server_async(self) restore_os_env(old_os_env) def generate_signature(self, body: str, timestamp: str): diff --git a/tests/scenario_tests_async/test_events_socket_mode.py b/tests/scenario_tests_async/test_events_socket_mode.py index 75ab349ad..e3d3fc98f 100644 --- a/tests/scenario_tests_async/test_events_socket_mode.py +++ b/tests/scenario_tests_async/test_events_socket_mode.py @@ -13,7 +13,7 @@ assert_auth_test_count_async, setup_mock_web_api_server_async, ) -from tests.utils import remove_os_env_temporarily, restore_os_env, get_event_loop +from tests.utils import remove_os_env_temporarily, restore_os_env class TestAsyncEvents: @@ -24,16 +24,14 @@ class TestAsyncEvents: base_url=mock_api_server_base_url, ) - @pytest.fixture - def event_loop(self): + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server_async(self) try: - setup_mock_web_api_server_async(self) - loop = get_event_loop() - yield loop - loop.close() - cleanup_mock_web_api_server_async(self) + yield # run the test here finally: + cleanup_mock_web_api_server_async(self) restore_os_env(old_os_env) def build_valid_app_mention_request(self) -> AsyncBoltRequest: diff --git a/tests/scenario_tests_async/test_events_token_revocations.py b/tests/scenario_tests_async/test_events_token_revocations.py index ecadbc53f..0c079eede 100644 --- a/tests/scenario_tests_async/test_events_token_revocations.py +++ b/tests/scenario_tests_async/test_events_token_revocations.py @@ -18,7 +18,7 @@ assert_auth_test_count_async, setup_mock_web_api_server_async, ) -from tests.utils import remove_os_env_temporarily, restore_os_env, get_event_loop +from tests.utils import remove_os_env_temporarily, restore_os_env valid_token = "xoxb-valid" @@ -49,16 +49,14 @@ class TestEventsTokenRevocations: signature_verifier = SignatureVerifier(signing_secret) web_client = AsyncWebClient(token=None, base_url=mock_api_server_base_url) - @pytest.fixture - def event_loop(self): + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server_async(self) try: - setup_mock_web_api_server_async(self) - loop = get_event_loop() - yield loop - loop.close() - cleanup_mock_web_api_server_async(self) + yield # run the test here finally: + cleanup_mock_web_api_server_async(self) restore_os_env(old_os_env) def generate_signature(self, body: str, timestamp: str): diff --git a/tests/scenario_tests_async/test_events_url_verification.py b/tests/scenario_tests_async/test_events_url_verification.py index c095791cd..123fd3cce 100644 --- a/tests/scenario_tests_async/test_events_url_verification.py +++ b/tests/scenario_tests_async/test_events_url_verification.py @@ -12,7 +12,7 @@ assert_auth_test_count_async, setup_mock_web_api_server_async, ) -from tests.utils import remove_os_env_temporarily, restore_os_env, get_event_loop +from tests.utils import remove_os_env_temporarily, restore_os_env class TestAsyncEventsUrlVerification: @@ -22,16 +22,14 @@ class TestAsyncEventsUrlVerification: signature_verifier = SignatureVerifier(signing_secret) web_client = AsyncWebClient(token=valid_token, base_url=mock_api_server_base_url) - @pytest.fixture - def event_loop(self): + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server_async(self) try: - setup_mock_web_api_server_async(self) - loop = get_event_loop() - yield loop - loop.close() - cleanup_mock_web_api_server_async(self) + yield # run the test here finally: + cleanup_mock_web_api_server_async(self) restore_os_env(old_os_env) def generate_signature(self, body: str, timestamp: str): diff --git a/tests/scenario_tests_async/test_function.py b/tests/scenario_tests_async/test_function.py index a2c10950c..ce9080e36 100644 --- a/tests/scenario_tests_async/test_function.py +++ b/tests/scenario_tests_async/test_function.py @@ -1,11 +1,14 @@ import asyncio import json +import re import time import pytest +from unittest.mock import Mock, MagicMock from slack_sdk.signature import SignatureVerifier from slack_sdk.web.async_client import AsyncWebClient +import slack_bolt.listener.asyncio_runner as async_runner_module from slack_bolt.app.async_app import AsyncApp from slack_bolt.request.async_request import AsyncBoltRequest from tests.mock_web_api_server import ( @@ -27,16 +30,14 @@ class TestAsyncFunction: base_url=mock_api_server_base_url, ) - @pytest.fixture - def event_loop(self): + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server_async(self) try: - setup_mock_web_api_server_async(self) - loop = asyncio.get_event_loop() - yield loop - loop.close() - cleanup_mock_web_api_server_async(self) + yield # run the test here finally: + cleanup_mock_web_api_server_async(self) restore_os_env(old_os_env) def generate_signature(self, body: str, timestamp: str): @@ -56,6 +57,10 @@ def build_request_from_body(self, message_body: dict) -> AsyncBoltRequest: timestamp, body = str(int(time.time())), json.dumps(message_body) return AsyncBoltRequest(body=body, headers=self.build_headers(timestamp, body)) + def setup_time_mocks(self, *, monkeypatch: pytest.MonkeyPatch, time_mock, sleep_mock): + monkeypatch.setattr(async_runner_module.time, "time", time_mock) + monkeypatch.setattr(async_runner_module.asyncio, "sleep", sleep_mock) + @pytest.mark.asyncio async def test_mock_server_is_running(self): resp = await self.web_client.api_test() @@ -130,19 +135,93 @@ async def test_auto_acknowledge_false_with_acknowledging(self): await assert_auth_test_count_async(self, 1) @pytest.mark.asyncio - async def test_auto_acknowledge_false_without_acknowledging(self, caplog): + async def test_auto_acknowledge_false_without_acknowledging(self, caplog, monkeypatch): app = AsyncApp( client=self.web_client, signing_secret=self.signing_secret, ) app.function("reverse", auto_acknowledge=False)(just_no_ack) - request = self.build_request_from_body(function_body) + + elapsed_seconds = 0 + + def fake_time(): + return float(elapsed_seconds) + + async def fake_sleep(duration): + nonlocal elapsed_seconds + elapsed_seconds += 1 + + self.setup_time_mocks( + monkeypatch=monkeypatch, + time_mock=fake_time, + sleep_mock=MagicMock(side_effect=fake_sleep), + ) + response = await app.async_dispatch(request) assert response.status == 404 await assert_auth_test_count_async(self, 1) assert f"WARNING {just_no_ack.__name__} didn't call ack()" in caplog.text + @pytest.mark.asyncio + async def test_function_handler_timeout(self, monkeypatch): + timeout = 5 + app = AsyncApp( + client=self.web_client, + signing_secret=self.signing_secret, + ) + app.function("reverse", auto_acknowledge=False, ack_timeout=timeout)(just_no_ack) + request = self.build_request_from_body(function_body) + + elapsed_seconds = 0 + + def fake_time(): + return float(elapsed_seconds) + + async def fake_sleep(duration): + nonlocal elapsed_seconds + elapsed_seconds += 1 + + self.setup_time_mocks( + monkeypatch=monkeypatch, + time_mock=fake_time, + sleep_mock=MagicMock(side_effect=fake_sleep), + ) + + response = await app.async_dispatch(request) + + assert response.status == 404 + await assert_auth_test_count_async(self, 1) + assert elapsed_seconds == timeout + 1, ( + f"Expected handler to time out after {timeout + 1} sleep calls, " f"but it was called {elapsed_seconds} times" + ) + + @pytest.mark.asyncio + async def test_warning_when_timeout_improperly_set(self, caplog): + app = AsyncApp( + client=self.web_client, + signing_secret=self.signing_secret, + ) + app.function("reverse")(just_no_ack) + assert "WARNING" not in caplog.text + + timeout_argument_name = "ack_timeout" + kwargs = {timeout_argument_name: 5} + + callback_id = "reverse1" + app.function(callback_id, **kwargs)(just_no_ack) + assert ( + f'WARNING On @app.function("{callback_id}"), as `auto_acknowledge` is `True`, `{timeout_argument_name}={kwargs[timeout_argument_name]}` you gave will be unused' + in caplog.text + ) + + callback_id = re.compile(r"hello \w+") + app.function(callback_id, **kwargs)(just_no_ack) + assert ( + f"WARNING On @app.function({callback_id}), as `auto_acknowledge` is `True`, `{timeout_argument_name}={kwargs[timeout_argument_name]}` you gave will be unused" + in caplog.text + ) + function_body = { "token": "verification_token", @@ -245,18 +324,22 @@ async def reverse(body, event, client, context, complete, inputs): assert context.client.token == "xwfp-valid" assert client.token == "xwfp-valid" assert complete.client.token == "xwfp-valid" + assert complete.has_been_called() is False await complete( outputs={"reverseString": "olleh"}, ) + assert complete.has_been_called() is True async def reverse_error(body, event, fail): assert body == function_body assert event == function_body["event"] assert fail.function_execution_id == "Fx111" + assert fail.has_been_called() is False await fail( error="there was an error", ) + assert fail.has_been_called() is True async def complete_it(body, event, complete): diff --git a/tests/scenario_tests_async/test_installation_store_authorize.py b/tests/scenario_tests_async/test_installation_store_authorize.py index ad0b24250..bef7d39e0 100644 --- a/tests/scenario_tests_async/test_installation_store_authorize.py +++ b/tests/scenario_tests_async/test_installation_store_authorize.py @@ -18,7 +18,7 @@ assert_auth_test_count_async, setup_mock_web_api_server_async, ) -from tests.utils import remove_os_env_temporarily, restore_os_env, get_event_loop +from tests.utils import remove_os_env_temporarily, restore_os_env valid_token = "xoxb-valid" valid_user_token = "xoxp-valid" @@ -63,16 +63,14 @@ class TestAsyncInstallationStoreAuthorize: base_url=mock_api_server_base_url, ) - @pytest.fixture - def event_loop(self): + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server_async(self) try: - setup_mock_web_api_server_async(self) - loop = get_event_loop() - yield loop - loop.close() - cleanup_mock_web_api_server_async(self) + yield # run the test here finally: + cleanup_mock_web_api_server_async(self) restore_os_env(old_os_env) def generate_signature(self, body: str, timestamp: str): diff --git a/tests/scenario_tests_async/test_lazy.py b/tests/scenario_tests_async/test_lazy.py index 02a2bd0fa..8c4182f45 100644 --- a/tests/scenario_tests_async/test_lazy.py +++ b/tests/scenario_tests_async/test_lazy.py @@ -14,7 +14,7 @@ cleanup_mock_web_api_server_async, setup_mock_web_api_server_async, ) -from tests.utils import remove_os_env_temporarily, restore_os_env, get_event_loop +from tests.utils import remove_os_env_temporarily, restore_os_env class TestAsyncLazy: @@ -27,16 +27,14 @@ class TestAsyncLazy: base_url=mock_api_server_base_url, ) - @pytest.fixture - def event_loop(self): + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server_async(self) try: - setup_mock_web_api_server_async(self) - loop = get_event_loop() - yield loop - loop.close() - cleanup_mock_web_api_server_async(self) + yield # run the test here finally: + cleanup_mock_web_api_server_async(self) restore_os_env(old_os_env) # ---------------- @@ -140,11 +138,11 @@ async def async2(context, say): @app.middleware async def set_ssl_context(context, next_): - from ssl import SSLContext + import ssl context["foo"] = "FOO" # This causes an error when starting lazy listener executions - context["ssl_context"] = SSLContext() + context["ssl_context"] = ssl.create_default_context() await next_() # 2021-12-13 11:52:46 ERROR Failed to run a middleware function (error: cannot pickle 'SSLContext' object) diff --git a/tests/scenario_tests_async/test_listener_middleware.py b/tests/scenario_tests_async/test_listener_middleware.py index 4e6419e96..1b3d9b17d 100644 --- a/tests/scenario_tests_async/test_listener_middleware.py +++ b/tests/scenario_tests_async/test_listener_middleware.py @@ -12,7 +12,7 @@ cleanup_mock_web_api_server_async, setup_mock_web_api_server_async, ) -from tests.utils import remove_os_env_temporarily, restore_os_env, get_event_loop +from tests.utils import remove_os_env_temporarily, restore_os_env class TestAsyncListenerMiddleware: @@ -25,16 +25,14 @@ class TestAsyncListenerMiddleware: base_url=mock_api_server_base_url, ) - @pytest.fixture - def event_loop(self): + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server_async(self) try: - setup_mock_web_api_server_async(self) - loop = get_event_loop() - yield loop - loop.close() - cleanup_mock_web_api_server_async(self) + yield # run the test here finally: + cleanup_mock_web_api_server_async(self) restore_os_env(old_os_env) body = { diff --git a/tests/scenario_tests_async/test_message.py b/tests/scenario_tests_async/test_message.py index cc0fbb8ec..374760323 100644 --- a/tests/scenario_tests_async/test_message.py +++ b/tests/scenario_tests_async/test_message.py @@ -16,7 +16,7 @@ assert_auth_test_count_async, setup_mock_web_api_server_async, ) -from tests.utils import remove_os_env_temporarily, restore_os_env, get_event_loop +from tests.utils import remove_os_env_temporarily, restore_os_env class TestAsyncMessage: @@ -29,16 +29,14 @@ class TestAsyncMessage: base_url=mock_api_server_base_url, ) - @pytest.fixture - def event_loop(self): + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server_async(self) try: - setup_mock_web_api_server_async(self) - loop = get_event_loop() - yield loop - loop.close() - cleanup_mock_web_api_server_async(self) + yield # run the test here finally: + cleanup_mock_web_api_server_async(self) restore_os_env(old_os_env) def generate_signature(self, body: str, timestamp: str): diff --git a/tests/scenario_tests_async/test_message_bot.py b/tests/scenario_tests_async/test_message_bot.py index 8e5e28c87..50f29271c 100644 --- a/tests/scenario_tests_async/test_message_bot.py +++ b/tests/scenario_tests_async/test_message_bot.py @@ -13,7 +13,7 @@ assert_auth_test_count_async, setup_mock_web_api_server_async, ) -from tests.utils import remove_os_env_temporarily, restore_os_env, get_event_loop +from tests.utils import remove_os_env_temporarily, restore_os_env class TestAsyncMessage: @@ -26,16 +26,14 @@ class TestAsyncMessage: base_url=mock_api_server_base_url, ) - @pytest.fixture - def event_loop(self): + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server_async(self) try: - setup_mock_web_api_server_async(self) - loop = get_event_loop() - yield loop - loop.close() - cleanup_mock_web_api_server_async(self) + yield # run the test here finally: + cleanup_mock_web_api_server_async(self) restore_os_env(old_os_env) def generate_signature(self, body: str, timestamp: str): diff --git a/tests/scenario_tests_async/test_message_changed.py b/tests/scenario_tests_async/test_message_changed.py index 15658d636..66468f14a 100644 --- a/tests/scenario_tests_async/test_message_changed.py +++ b/tests/scenario_tests_async/test_message_changed.py @@ -11,7 +11,7 @@ cleanup_mock_web_api_server_async, setup_mock_web_api_server_async, ) -from tests.utils import remove_os_env_temporarily, restore_os_env, get_event_loop +from tests.utils import remove_os_env_temporarily, restore_os_env class TestAsyncMessageChanged: @@ -24,16 +24,14 @@ class TestAsyncMessageChanged: base_url=mock_api_server_base_url, ) - @pytest.fixture - def event_loop(self): + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server_async(self) try: - setup_mock_web_api_server_async(self) - loop = get_event_loop() - yield loop - loop.close() - cleanup_mock_web_api_server_async(self) + yield # run the test here finally: + cleanup_mock_web_api_server_async(self) restore_os_env(old_os_env) def generate_signature(self, body: str, timestamp: str): diff --git a/tests/scenario_tests_async/test_message_deleted.py b/tests/scenario_tests_async/test_message_deleted.py index 09f669d48..d5b6ba80c 100644 --- a/tests/scenario_tests_async/test_message_deleted.py +++ b/tests/scenario_tests_async/test_message_deleted.py @@ -11,7 +11,7 @@ cleanup_mock_web_api_server_async, setup_mock_web_api_server_async, ) -from tests.utils import remove_os_env_temporarily, restore_os_env, get_event_loop +from tests.utils import remove_os_env_temporarily, restore_os_env class TestAsyncMessageDeleted: @@ -24,16 +24,14 @@ class TestAsyncMessageDeleted: base_url=mock_api_server_base_url, ) - @pytest.fixture - def event_loop(self): + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server_async(self) try: - setup_mock_web_api_server_async(self) - loop = get_event_loop() - yield loop - loop.close() - cleanup_mock_web_api_server_async(self) + yield # run the test here finally: + cleanup_mock_web_api_server_async(self) restore_os_env(old_os_env) def generate_signature(self, body: str, timestamp: str): diff --git a/tests/scenario_tests_async/test_message_file_share.py b/tests/scenario_tests_async/test_message_file_share.py index 6f55957ac..f156d9286 100644 --- a/tests/scenario_tests_async/test_message_file_share.py +++ b/tests/scenario_tests_async/test_message_file_share.py @@ -13,7 +13,7 @@ assert_auth_test_count_async, setup_mock_web_api_server_async, ) -from tests.utils import remove_os_env_temporarily, restore_os_env, get_event_loop +from tests.utils import remove_os_env_temporarily, restore_os_env class TestAsyncMessageFileShare: @@ -26,16 +26,14 @@ class TestAsyncMessageFileShare: base_url=mock_api_server_base_url, ) - @pytest.fixture - def event_loop(self): + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server_async(self) try: - setup_mock_web_api_server_async(self) - loop = get_event_loop() - yield loop - loop.close() - cleanup_mock_web_api_server_async(self) + yield # run the test here finally: + cleanup_mock_web_api_server_async(self) restore_os_env(old_os_env) def generate_signature(self, body: str, timestamp: str): diff --git a/tests/scenario_tests_async/test_message_thread_broadcast.py b/tests/scenario_tests_async/test_message_thread_broadcast.py index c3ac6dd01..c15bbdc99 100644 --- a/tests/scenario_tests_async/test_message_thread_broadcast.py +++ b/tests/scenario_tests_async/test_message_thread_broadcast.py @@ -13,7 +13,7 @@ assert_auth_test_count_async, setup_mock_web_api_server_async, ) -from tests.utils import remove_os_env_temporarily, restore_os_env, get_event_loop +from tests.utils import remove_os_env_temporarily, restore_os_env class TestAsyncMessageThreadBroadcast: @@ -26,16 +26,14 @@ class TestAsyncMessageThreadBroadcast: base_url=mock_api_server_base_url, ) - @pytest.fixture - def event_loop(self): + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server_async(self) try: - setup_mock_web_api_server_async(self) - loop = get_event_loop() - yield loop - loop.close() - cleanup_mock_web_api_server_async(self) + yield # run the test here finally: + cleanup_mock_web_api_server_async(self) restore_os_env(old_os_env) def generate_signature(self, body: str, timestamp: str): diff --git a/tests/scenario_tests_async/test_middleware.py b/tests/scenario_tests_async/test_middleware.py index 6272f17e4..f8dfe9623 100644 --- a/tests/scenario_tests_async/test_middleware.py +++ b/tests/scenario_tests_async/test_middleware.py @@ -20,7 +20,7 @@ assert_auth_test_count_async, setup_mock_web_api_server_async, ) -from tests.utils import remove_os_env_temporarily, restore_os_env, get_event_loop +from tests.utils import remove_os_env_temporarily, restore_os_env # Note that async middleware system does not support instance methods n a class. @@ -34,16 +34,14 @@ class TestAsyncMiddleware: base_url=mock_api_server_base_url, ) - @pytest.fixture - def event_loop(self): + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server_async(self) try: - setup_mock_web_api_server_async(self) - loop = get_event_loop() - yield loop - loop.close() - cleanup_mock_web_api_server_async(self) + yield # run the test here finally: + cleanup_mock_web_api_server_async(self) restore_os_env(old_os_env) def build_request(self) -> AsyncBoltRequest: diff --git a/tests/scenario_tests_async/test_shortcut.py b/tests/scenario_tests_async/test_shortcut.py index 9ad4b2b03..bd3c595eb 100644 --- a/tests/scenario_tests_async/test_shortcut.py +++ b/tests/scenario_tests_async/test_shortcut.py @@ -13,7 +13,7 @@ assert_auth_test_count_async, setup_mock_web_api_server_async, ) -from tests.utils import remove_os_env_temporarily, restore_os_env, get_event_loop +from tests.utils import remove_os_env_temporarily, restore_os_env class TestAsyncShortcut: @@ -26,16 +26,14 @@ class TestAsyncShortcut: base_url=mock_api_server_base_url, ) - @pytest.fixture - def event_loop(self): + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server_async(self) try: - setup_mock_web_api_server_async(self) - loop = get_event_loop() - yield loop - loop.close() - cleanup_mock_web_api_server_async(self) + yield # run the test here finally: + cleanup_mock_web_api_server_async(self) restore_os_env(old_os_env) def generate_signature(self, body: str, timestamp: str): diff --git a/tests/scenario_tests_async/test_slash_command.py b/tests/scenario_tests_async/test_slash_command.py index 6c6d9ef88..918ccba87 100644 --- a/tests/scenario_tests_async/test_slash_command.py +++ b/tests/scenario_tests_async/test_slash_command.py @@ -12,7 +12,7 @@ assert_auth_test_count_async, setup_mock_web_api_server_async, ) -from tests.utils import remove_os_env_temporarily, restore_os_env, get_event_loop +from tests.utils import remove_os_env_temporarily, restore_os_env class TestAsyncSlashCommand: @@ -25,16 +25,14 @@ class TestAsyncSlashCommand: base_url=mock_api_server_base_url, ) - @pytest.fixture - def event_loop(self): + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server_async(self) try: - setup_mock_web_api_server_async(self) - loop = get_event_loop() - yield loop - loop.close() - cleanup_mock_web_api_server_async(self) + yield # run the test here finally: + cleanup_mock_web_api_server_async(self) restore_os_env(old_os_env) def generate_signature(self, body: str, timestamp: str): @@ -102,6 +100,35 @@ async def test_failure(self): assert response.status == 404 await assert_auth_test_count_async(self, 1) + @pytest.mark.asyncio + async def test_ssl_check_param_does_not_bypass_request_verification(self): + app = AsyncApp( + client=self.web_client, + signing_secret=self.signing_secret, + ssl_check_enabled=False, + ) + command_called = False + + async def command_handler(ack): + nonlocal command_called + command_called = True + await ack() + + app.command("/hello-world")(command_handler) + + request = AsyncBoltRequest( + body=f"{slash_command_body}&ssl_check=1", + headers={ + "content-type": ["application/x-www-form-urlencoded"], + "x-slack-signature": ["v0=invalid"], + "x-slack-request-timestamp": ["0"], + }, + ) + response = await app.async_dispatch(request) + assert response.status == 401 + assert response.body == """{"error": "invalid request"}""" + assert command_called is False + slash_command_body = ( "token=verification_token" diff --git a/tests/scenario_tests_async/test_ssl_check.py b/tests/scenario_tests_async/test_ssl_check.py index 894bf82c2..ef32bc0dd 100644 --- a/tests/scenario_tests_async/test_ssl_check.py +++ b/tests/scenario_tests_async/test_ssl_check.py @@ -10,7 +10,7 @@ cleanup_mock_web_api_server_async, setup_mock_web_api_server_async, ) -from tests.utils import remove_os_env_temporarily, restore_os_env, get_event_loop +from tests.utils import remove_os_env_temporarily, restore_os_env class TestAsyncSSLCheck: @@ -23,16 +23,14 @@ class TestAsyncSSLCheck: base_url=mock_api_server_base_url, ) - @pytest.fixture - def event_loop(self): + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server_async(self) try: - setup_mock_web_api_server_async(self) - loop = get_event_loop() - yield loop - loop.close() - cleanup_mock_web_api_server_async(self) + yield # run the test here finally: + cleanup_mock_web_api_server_async(self) restore_os_env(old_os_env) def generate_signature(self, body: str, timestamp: str): diff --git a/tests/scenario_tests_async/test_view_closed.py b/tests/scenario_tests_async/test_view_closed.py index a633b0d3b..1b86d22db 100644 --- a/tests/scenario_tests_async/test_view_closed.py +++ b/tests/scenario_tests_async/test_view_closed.py @@ -13,7 +13,7 @@ cleanup_mock_web_api_server_async, setup_mock_web_api_server_async, ) -from tests.utils import remove_os_env_temporarily, restore_os_env, get_event_loop +from tests.utils import remove_os_env_temporarily, restore_os_env class TestAsyncViewClosed: @@ -26,16 +26,14 @@ class TestAsyncViewClosed: base_url=mock_api_server_base_url, ) - @pytest.fixture - def event_loop(self): + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server_async(self) try: - setup_mock_web_api_server_async(self) - loop = get_event_loop() - yield loop - loop.close() - cleanup_mock_web_api_server_async(self) + yield # run the test here finally: + cleanup_mock_web_api_server_async(self) restore_os_env(old_os_env) def generate_signature(self, body: str, timestamp: str): diff --git a/tests/scenario_tests_async/test_view_submission.py b/tests/scenario_tests_async/test_view_submission.py index efb1c25f5..6511243fa 100644 --- a/tests/scenario_tests_async/test_view_submission.py +++ b/tests/scenario_tests_async/test_view_submission.py @@ -13,8 +13,7 @@ assert_auth_test_count_async, setup_mock_web_api_server_async, ) -from tests.utils import remove_os_env_temporarily, restore_os_env, get_event_loop - +from tests.utils import remove_os_env_temporarily, restore_os_env body = { "type": "view_submission", @@ -198,16 +197,14 @@ class TestAsyncViewSubmission: base_url=mock_api_server_base_url, ) - @pytest.fixture - def event_loop(self): + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server_async(self) try: - setup_mock_web_api_server_async(self) - loop = get_event_loop() - yield loop - loop.close() - cleanup_mock_web_api_server_async(self) + yield # run the test here finally: + cleanup_mock_web_api_server_async(self) restore_os_env(old_os_env) def generate_signature(self, body: str, timestamp: str): diff --git a/tests/scenario_tests_async/test_web_client_customization.py b/tests/scenario_tests_async/test_web_client_customization.py index 3cfc5af64..8ed78b2c3 100644 --- a/tests/scenario_tests_async/test_web_client_customization.py +++ b/tests/scenario_tests_async/test_web_client_customization.py @@ -1,4 +1,5 @@ import json +import logging import os from time import time from urllib.parse import quote @@ -15,7 +16,7 @@ assert_auth_test_count_async, setup_mock_web_api_server_async, ) -from tests.utils import remove_os_env_temporarily, restore_os_env, get_event_loop +from tests.utils import remove_os_env_temporarily, restore_os_env class TestWebClientCustomization: @@ -23,21 +24,20 @@ class TestWebClientCustomization: signing_secret = "secret" mock_api_server_base_url = "http://localhost:8888" signature_verifier = SignatureVerifier(signing_secret) + test_logger = logging.getLogger("test.logger") web_client = AsyncWebClient( token=valid_token, base_url=mock_api_server_base_url, ) - @pytest.fixture - def event_loop(self): + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server_async(self) try: - setup_mock_web_api_server_async(self) - loop = get_event_loop() - yield loop - loop.close() - cleanup_mock_web_api_server_async(self) + yield # run the test here finally: + cleanup_mock_web_api_server_async(self) restore_os_env(old_os_env) def generate_signature(self, body: str, timestamp: str): @@ -87,6 +87,74 @@ async def listener(ack, client): assert response.body == "" await assert_auth_test_count_async(self, 1) + def test_web_client_logger_is_default_app_logger(self): + app = AsyncApp(token=self.valid_token, signing_secret=self.signing_secret) + assert app.client.logger == app.logger + + def test_web_client_logger_is_app_logger(self): + app = AsyncApp(token=self.valid_token, signing_secret=self.signing_secret, logger=self.test_logger) + assert app.client.logger == app.logger + assert app.client.logger == self.test_logger + + @pytest.mark.asyncio + async def test_default_web_client_uses_bolt_framework_logger(self): + app = AsyncApp(token=self.valid_token, signing_secret=self.signing_secret) + app.client.base_url = self.mock_api_server_base_url + + @app.action("a") + async def listener(ack, client: AsyncWebClient): + assert client.logger == app.logger + await ack() + + request = self.build_valid_request() + response = await app.async_dispatch(request) + assert response.status == 200 + assert response.body == "" + await assert_auth_test_count_async(self, 1) + + @pytest.mark.asyncio + async def test_default_web_client_uses_bolt_app_custom_logger(self): + app = AsyncApp( + token=self.valid_token, + signing_secret=self.signing_secret, + logger=self.test_logger, + ) + app.client.base_url = self.mock_api_server_base_url + + assert app.client.logger == app.logger + + @app.action("a") + async def listener(ack, client: AsyncWebClient): + assert client.logger == app.logger + assert client.logger == self.test_logger + await ack() + + request = self.build_valid_request() + response = await app.async_dispatch(request) + assert response.status == 200 + assert response.body == "" + await assert_auth_test_count_async(self, 1) + + @pytest.mark.asyncio + async def test_custom_web_client_logger_is_used_instead_of_bolt_app_logger(self): + web_client = AsyncWebClient(token=self.valid_token, base_url=self.mock_api_server_base_url, logger=self.test_logger) + app = AsyncApp( + client=web_client, + signing_secret=self.signing_secret, + ) + + @app.action("a") + async def listener(ack, client: AsyncWebClient): + assert client.logger == self.test_logger + assert app.logger != self.test_logger + await ack() + + request = self.build_valid_request() + response = await app.async_dispatch(request) + assert response.status == 200 + assert response.body == "" + await assert_auth_test_count_async(self, 1) + block_actions_body = { "type": "block_actions", diff --git a/tests/scenario_tests_async/test_workflow_steps.py b/tests/scenario_tests_async/test_workflow_steps.py index ea9766361..a99dedbfe 100644 --- a/tests/scenario_tests_async/test_workflow_steps.py +++ b/tests/scenario_tests_async/test_workflow_steps.py @@ -21,7 +21,7 @@ assert_auth_test_count_async, setup_mock_web_api_server_async, ) -from tests.utils import remove_os_env_temporarily, restore_os_env, get_event_loop +from tests.utils import remove_os_env_temporarily, restore_os_env class TestAsyncWorkflowSteps: @@ -34,16 +34,14 @@ class TestAsyncWorkflowSteps: base_url=mock_api_server_base_url, ) - @pytest.fixture - def event_loop(self): + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server_async(self) try: - setup_mock_web_api_server_async(self) - loop = get_event_loop() - yield loop - loop.close() - cleanup_mock_web_api_server_async(self) + yield # run the test here finally: + cleanup_mock_web_api_server_async(self) restore_os_env(old_os_env) def generate_signature(self, body: str, timestamp: str): diff --git a/tests/scenario_tests_async/test_workflow_steps_decorator_simple.py b/tests/scenario_tests_async/test_workflow_steps_decorator_simple.py index f404bf947..1224949ae 100644 --- a/tests/scenario_tests_async/test_workflow_steps_decorator_simple.py +++ b/tests/scenario_tests_async/test_workflow_steps_decorator_simple.py @@ -21,7 +21,7 @@ cleanup_mock_web_api_server_async, setup_mock_web_api_server_async, ) -from tests.utils import remove_os_env_temporarily, restore_os_env, get_event_loop +from tests.utils import remove_os_env_temporarily, restore_os_env class TestAsyncWorkflowStepsDecorator: @@ -34,19 +34,16 @@ class TestAsyncWorkflowStepsDecorator: base_url=mock_api_server_base_url, ) - @pytest.fixture - def event_loop(self): + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server_async(self) try: - setup_mock_web_api_server_async(self) self.app = AsyncApp(client=self.web_client, signing_secret=self.signing_secret) self.app.step(copy_review_step) - - loop = get_event_loop() - yield loop - loop.close() - cleanup_mock_web_api_server_async(self) + yield # run the test here finally: + cleanup_mock_web_api_server_async(self) restore_os_env(old_os_env) def generate_signature(self, body: str, timestamp: str): diff --git a/tests/scenario_tests_async/test_workflow_steps_decorator_with_args.py b/tests/scenario_tests_async/test_workflow_steps_decorator_with_args.py index 02d0ad8c7..53bec512d 100644 --- a/tests/scenario_tests_async/test_workflow_steps_decorator_with_args.py +++ b/tests/scenario_tests_async/test_workflow_steps_decorator_with_args.py @@ -22,7 +22,7 @@ cleanup_mock_web_api_server_async, setup_mock_web_api_server_async, ) -from tests.utils import remove_os_env_temporarily, restore_os_env, get_event_loop +from tests.utils import remove_os_env_temporarily, restore_os_env class TestAsyncWorkflowStepsDecorator: @@ -35,19 +35,16 @@ class TestAsyncWorkflowStepsDecorator: base_url=mock_api_server_base_url, ) - @pytest.fixture - def event_loop(self): + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server_async(self) try: - setup_mock_web_api_server_async(self) self.app = AsyncApp(client=self.web_client, signing_secret=self.signing_secret) self.app.step(copy_review_step) - - loop = get_event_loop() - yield loop - loop.close() - cleanup_mock_web_api_server_async(self) + yield # run the test here finally: + cleanup_mock_web_api_server_async(self) restore_os_env(old_os_env) def generate_signature(self, body: str, timestamp: str): diff --git a/tests/slack_bolt/context/test_complete.py b/tests/slack_bolt/context/test_complete.py index a920c41eb..63a1d9f04 100644 --- a/tests/slack_bolt/context/test_complete.py +++ b/tests/slack_bolt/context/test_complete.py @@ -30,3 +30,12 @@ def test_complete_no_function_execution_id(self): with pytest.raises(ValueError): complete(outputs={"key": "value"}) + + def test_has_been_called_false_initially(self): + complete = Complete(client=self.web_client, function_execution_id="fn1111") + assert complete.has_been_called() is False + + def test_has_been_called_true_after_complete(self): + complete = Complete(client=self.web_client, function_execution_id="fn1111") + complete(outputs={"key": "value"}) + assert complete.has_been_called() is True diff --git a/tests/slack_bolt/context/test_fail.py b/tests/slack_bolt/context/test_fail.py index e4704d376..14348281f 100644 --- a/tests/slack_bolt/context/test_fail.py +++ b/tests/slack_bolt/context/test_fail.py @@ -30,3 +30,12 @@ def test_fail_no_function_execution_id(self): with pytest.raises(ValueError): fail(error="there was an error") + + def test_has_been_called_false_initially(self): + fail = Fail(client=self.web_client, function_execution_id="fn1111") + assert fail.has_been_called() is False + + def test_has_been_called_true_after_fail(self): + fail = Fail(client=self.web_client, function_execution_id="fn1111") + fail(error="there was an error") + assert fail.has_been_called() is True diff --git a/tests/slack_bolt/context/test_say.py b/tests/slack_bolt/context/test_say.py index 9e465e5d5..6ca1fc96a 100644 --- a/tests/slack_bolt/context/test_say.py +++ b/tests/slack_bolt/context/test_say.py @@ -3,10 +3,7 @@ from slack_sdk.web import SlackResponse from slack_bolt import Say -from tests.mock_web_api_server import ( - setup_mock_web_api_server, - cleanup_mock_web_api_server, -) +from tests.mock_web_api_server import cleanup_mock_web_api_server, setup_mock_web_api_server class TestSay: @@ -24,6 +21,11 @@ def test_say(self): response: SlackResponse = say(text="Hi there!") assert response.status_code == 200 + def test_say_markdown_text(self): + say = Say(client=self.web_client, channel="C111") + response: SlackResponse = say(markdown_text="**Greetings!**") + assert response.status_code == 200 + def test_say_unfurl_options(self): say = Say(client=self.web_client, channel="C111") response: SlackResponse = say(text="Hi there!", unfurl_media=True, unfurl_links=True) diff --git a/tests/slack_bolt/context/test_say_stream.py b/tests/slack_bolt/context/test_say_stream.py new file mode 100644 index 000000000..04a52e419 --- /dev/null +++ b/tests/slack_bolt/context/test_say_stream.py @@ -0,0 +1,108 @@ +import pytest +from unittest.mock import patch, MagicMock + +from slack_sdk import WebClient + +from slack_bolt.context.say_stream.say_stream import SayStream + + +class TestSayStream: + def setup_method(self): + self.web_client = WebClient(token="xoxb-valid") + + def test_missing_channel_raises(self): + say_stream = SayStream(client=self.web_client, channel=None, thread_ts="111.222") + with pytest.raises(ValueError, match="channel"): + say_stream() + + def test_missing_thread_ts_raises(self): + say_stream = SayStream(client=self.web_client, channel="C111", thread_ts=None) + with pytest.raises(ValueError, match="thread_ts"): + say_stream() + + def test_default_params(self): + say_stream = SayStream( + client=self.web_client, + channel="C111", + recipient_team_id="T111", + recipient_user_id="U111", + thread_ts="111.222", + ) + with patch.object(self.web_client, "chat_stream", return_value=MagicMock()) as mock_chat_stream: + say_stream() + mock_chat_stream.assert_called_once_with( + channel="C111", + recipient_team_id="T111", + recipient_user_id="U111", + thread_ts="111.222", + icon_emoji=None, + icon_url=None, + username=None, + ) + + def test_parameter_overrides(self): + say_stream = SayStream( + client=self.web_client, + channel="C111", + recipient_team_id="T111", + recipient_user_id="U111", + thread_ts="111.222", + ) + with patch.object(self.web_client, "chat_stream", return_value=MagicMock()) as mock_chat_stream: + say_stream(channel="C222", thread_ts="333.444", recipient_team_id="T222", recipient_user_id="U222") + mock_chat_stream.assert_called_once_with( + channel="C222", + recipient_team_id="T222", + recipient_user_id="U222", + thread_ts="333.444", + icon_emoji=None, + icon_url=None, + username=None, + ) + + def test_buffer_size_overrides(self): + say_stream = SayStream( + client=self.web_client, + channel="C111", + recipient_team_id="T111", + recipient_user_id="U111", + thread_ts="111.222", + ) + with patch.object(self.web_client, "chat_stream", return_value=MagicMock()) as mock_chat_stream: + say_stream( + buffer_size=100, + channel="C222", + thread_ts="333.444", + recipient_team_id="T222", + recipient_user_id="U222", + ) + mock_chat_stream.assert_called_once_with( + buffer_size=100, + channel="C222", + recipient_team_id="T222", + recipient_user_id="U222", + thread_ts="333.444", + icon_emoji=None, + icon_url=None, + username=None, + ) + + def test_authorship_overrides(self): + say_stream = SayStream( + client=self.web_client, + channel="C111", + recipient_team_id="T111", + recipient_user_id="U111", + thread_ts="111.222", + ) + with patch.object(self.web_client, "chat_stream", return_value=MagicMock()) as mock_chat_stream: + say_stream(icon_emoji=":maple_leaf:", username="Charlie Brown") + mock_chat_stream.assert_called_once_with( + channel="C111", + recipient_team_id="T111", + recipient_user_id="U111", + thread_ts="111.222", + icon_emoji=":maple_leaf:", + icon_url=None, + username="Charlie Brown", + ) diff --git a/tests/slack_bolt/context/test_set_status.py b/tests/slack_bolt/context/test_set_status.py new file mode 100644 index 000000000..bb5807e96 --- /dev/null +++ b/tests/slack_bolt/context/test_set_status.py @@ -0,0 +1,47 @@ +import pytest +from slack_sdk import WebClient +from slack_sdk.web import SlackResponse + +from slack_bolt.context.set_status import SetStatus +from tests.mock_web_api_server import cleanup_mock_web_api_server, setup_mock_web_api_server + + +class TestSetStatus: + def setup_method(self): + setup_mock_web_api_server(self) + valid_token = "xoxb-valid" + mock_api_server_base_url = "http://localhost:8888" + self.web_client = WebClient(token=valid_token, base_url=mock_api_server_base_url) + + def teardown_method(self): + cleanup_mock_web_api_server(self) + + def test_set_status(self): + set_status = SetStatus(client=self.web_client, channel_id="C111", thread_ts="123.123") + response: SlackResponse = set_status("Thinking...") + assert response.status_code == 200 + + def test_set_status_loading_messages(self): + set_status = SetStatus(client=self.web_client, channel_id="C111", thread_ts="123.123") + response: SlackResponse = set_status( + status="Thinking...", + loading_messages=[ + "Sitting...", + "Waiting...", + ], + ) + assert response.status_code == 200 + + def test_set_status_authorship(self): + set_status = SetStatus(client=self.web_client, channel_id="C111", thread_ts="123.123") + response: SlackResponse = set_status( + status="Thinking...", + icon_emoji=":maple_leaf:", + username="Charlie Brown", + ) + assert response.status_code == 200 + + def test_set_status_invalid(self): + set_status = SetStatus(client=self.web_client, channel_id="C111", thread_ts="123.123") + with pytest.raises(TypeError): + set_status() diff --git a/tests/slack_bolt/context/test_set_suggested_prompts.py b/tests/slack_bolt/context/test_set_suggested_prompts.py new file mode 100644 index 000000000..a743d5656 --- /dev/null +++ b/tests/slack_bolt/context/test_set_suggested_prompts.py @@ -0,0 +1,80 @@ +from unittest.mock import MagicMock, patch + +import pytest +from slack_sdk import WebClient +from slack_sdk.web import SlackResponse + +from slack_bolt.context.set_suggested_prompts import SetSuggestedPrompts +from tests.mock_web_api_server import cleanup_mock_web_api_server, setup_mock_web_api_server + + +class TestSetSuggestedPrompts: + def setup_method(self): + setup_mock_web_api_server(self) + valid_token = "xoxb-valid" + mock_api_server_base_url = "http://localhost:8888" + self.web_client = WebClient(token=valid_token, base_url=mock_api_server_base_url) + + def teardown_method(self): + cleanup_mock_web_api_server(self) + + def test_set_suggested_prompts(self): + set_suggested_prompts = SetSuggestedPrompts(client=self.web_client, channel_id="C111", thread_ts="123.123") + response: SlackResponse = set_suggested_prompts(prompts=["One", "Two"]) + assert response.status_code == 200 + + def test_set_suggested_prompts_objects(self): + set_suggested_prompts = SetSuggestedPrompts(client=self.web_client, channel_id="C111", thread_ts="123.123") + response: SlackResponse = set_suggested_prompts( + prompts=[ + "One", + {"title": "Two", "message": "What's before addition?"}, + ], + ) + assert response.status_code == 200 + + def test_set_suggested_prompts_without_thread_ts(self): + set_suggested_prompts = SetSuggestedPrompts(client=self.web_client, channel_id="C111") + with patch.object( + self.web_client, self.web_client.assistant_threads_setSuggestedPrompts.__name__, return_value=MagicMock() + ) as mock_api: + set_suggested_prompts(prompts=["One", "Two"]) + mock_api.assert_called_once_with( + channel_id="C111", + thread_ts=None, + prompts=[{"title": "One", "message": "One"}, {"title": "Two", "message": "Two"}], + title=None, + ) + + def test_set_suggested_prompts_thread_ts_override(self): + # The call-time thread_ts must win over the stored one + set_suggested_prompts = SetSuggestedPrompts(client=self.web_client, channel_id="C111", thread_ts="999.999") + with patch.object( + self.web_client, self.web_client.assistant_threads_setSuggestedPrompts.__name__, return_value=MagicMock() + ) as mock_api: + set_suggested_prompts(prompts=["One", "Two"], thread_ts="123.123") + mock_api.assert_called_once_with( + channel_id="C111", + thread_ts="123.123", + prompts=[{"title": "One", "message": "One"}, {"title": "Two", "message": "Two"}], + title=None, + ) + + def test_set_suggested_prompts_thread_ts_override_falsy(self): + # An explicitly passed falsy thread_ts must be forwarded, not swallowed by the stored value + set_suggested_prompts = SetSuggestedPrompts(client=self.web_client, channel_id="C111", thread_ts="123.123") + with patch.object( + self.web_client, self.web_client.assistant_threads_setSuggestedPrompts.__name__, return_value=MagicMock() + ) as mock_api: + set_suggested_prompts(prompts=["One", "Two"], thread_ts="") + mock_api.assert_called_once_with( + channel_id="C111", + thread_ts="", + prompts=[{"title": "One", "message": "One"}, {"title": "Two", "message": "Two"}], + title=None, + ) + + def test_set_suggested_prompts_invalid(self): + set_suggested_prompts = SetSuggestedPrompts(client=self.web_client, channel_id="C111", thread_ts="123.123") + with pytest.raises(TypeError): + set_suggested_prompts() diff --git a/tests/slack_bolt/logger/test_unmatched_suggestions.py b/tests/slack_bolt/logger/test_unmatched_suggestions.py index 2c0c82b99..b470fa061 100644 --- a/tests/slack_bolt/logger/test_unmatched_suggestions.py +++ b/tests/slack_bolt/logger/test_unmatched_suggestions.py @@ -22,8 +22,7 @@ def test_block_actions(self): "block_id": "b", "action_id": "action-id-value", } - assert ( - f"""Unhandled request ({filtered_body}) + assert f"""Unhandled request ({filtered_body}) --- [Suggestion] You can handle this type of event with the following listener function: @@ -31,9 +30,7 @@ def test_block_actions(self): def handle_some_action(ack, body, logger): ack() logger.info(body) -""" - == message - ) +""" == message def test_attachment_actions(self): req: BoltRequest = BoltRequest(body=attachment_actions, mode="socket_mode") @@ -49,8 +46,7 @@ def test_attachment_actions(self): } ], } - assert ( - f"""Unhandled request ({filtered_body}) + assert f"""Unhandled request ({filtered_body}) --- [Suggestion] You can handle this type of event with the following listener function: @@ -58,9 +54,7 @@ def test_attachment_actions(self): def handle_some_action(ack, body, logger): ack() logger.info(body) -""" - == message - ) +""" == message def test_app_mention_event(self): req: BoltRequest = BoltRequest(body=app_mention_event, mode="socket_mode") @@ -69,17 +63,14 @@ def test_app_mention_event(self): "event": {"type": "app_mention"}, } message = warning_unhandled_request(req) - assert ( - f"""Unhandled request ({filtered_body}) + assert f"""Unhandled request ({filtered_body}) --- [Suggestion] You can handle this type of event with the following listener function: @app.event("app_mention") def handle_app_mention_events(body, logger): logger.info(body) -""" - == message - ) +""" == message def test_function_event(self): req: BoltRequest = BoltRequest(body=function_event, mode="socket_mode") @@ -88,8 +79,7 @@ def test_function_event(self): "event": {"type": "function_executed"}, } message = warning_unhandled_request(req) - assert ( - f"""Unhandled request ({filtered_body}) + assert f"""Unhandled request ({filtered_body}) --- [Suggestion] You can handle this type of event with the following listener function: @@ -104,9 +94,7 @@ def handle_some_function(ack, body, complete, fail, logger): except Exception as e: error = f"Failed to handle a function request (error: {{e}})" fail(error=error) -""" - == message - ) +""" == message def test_commands(self): req: BoltRequest = BoltRequest(body=slash_command, mode="socket_mode") @@ -115,8 +103,7 @@ def test_commands(self): "type": None, "command": "/start-conv", } - assert ( - f"""Unhandled request ({filtered_body}) + assert f"""Unhandled request ({filtered_body}) --- [Suggestion] You can handle this type of event with the following listener function: @@ -124,9 +111,7 @@ def test_commands(self): def handle_some_command(ack, body, logger): ack() logger.info(body) -""" - == message - ) +""" == message def test_shortcut(self): req: BoltRequest = BoltRequest(body=global_shortcut, mode="socket_mode") @@ -135,8 +120,7 @@ def test_shortcut(self): "type": "shortcut", "callback_id": "test-shortcut", } - assert ( - f"""Unhandled request ({filtered_body}) + assert f"""Unhandled request ({filtered_body}) --- [Suggestion] You can handle this type of event with the following listener function: @@ -144,9 +128,7 @@ def test_shortcut(self): def handle_shortcuts(ack, body, logger): ack() logger.info(body) -""" - == message - ) +""" == message req: BoltRequest = BoltRequest(body=message_shortcut, mode="socket_mode") message = warning_unhandled_request(req) @@ -154,8 +136,7 @@ def handle_shortcuts(ack, body, logger): "type": "message_action", "callback_id": "test-shortcut", } - assert ( - f"""Unhandled request ({filtered_body}) + assert f"""Unhandled request ({filtered_body}) --- [Suggestion] You can handle this type of event with the following listener function: @@ -163,9 +144,7 @@ def handle_shortcuts(ack, body, logger): def handle_shortcuts(ack, body, logger): ack() logger.info(body) -""" - == message - ) +""" == message def test_view(self): req: BoltRequest = BoltRequest(body=view_submission, mode="socket_mode") @@ -174,8 +153,7 @@ def test_view(self): "type": "view_submission", "view": {"type": "modal", "callback_id": "view-id"}, } - assert ( - f"""Unhandled request ({filtered_body}) + assert f"""Unhandled request ({filtered_body}) --- [Suggestion] You can handle this type of event with the following listener function: @@ -183,9 +161,7 @@ def test_view(self): def handle_view_submission_events(ack, body, logger): ack() logger.info(body) -""" - == message - ) +""" == message req: BoltRequest = BoltRequest(body=view_closed, mode="socket_mode") message = warning_unhandled_request(req) @@ -193,8 +169,7 @@ def handle_view_submission_events(ack, body, logger): "type": "view_closed", "view": {"type": "modal", "callback_id": "view-id"}, } - assert ( - f"""Unhandled request ({filtered_body}) + assert f"""Unhandled request ({filtered_body}) --- [Suggestion] You can handle this type of event with the following listener function: @@ -202,9 +177,7 @@ def handle_view_submission_events(ack, body, logger): def handle_view_closed_events(ack, body, logger): ack() logger.info(body) -""" - == message - ) +""" == message def test_block_suggestion(self): req: BoltRequest = BoltRequest(body=block_suggestion, mode="socket_mode") @@ -216,17 +189,14 @@ def test_block_suggestion(self): "action_id": "the-id", "value": "search word", } - assert ( - f"""Unhandled request ({filtered_body}) + assert f"""Unhandled request ({filtered_body}) --- [Suggestion] You can handle this type of event with the following listener function: @app.options("the-id") def handle_some_options(ack): ack(options=[ ... ]) -""" - == message - ) +""" == message def test_dialog_suggestion(self): req: BoltRequest = BoltRequest(body=dialog_suggestion, mode="socket_mode") @@ -236,17 +206,14 @@ def test_dialog_suggestion(self): "callback_id": "the-id", "value": "search keyword", } - assert ( - f"""Unhandled request ({filtered_body}) + assert f"""Unhandled request ({filtered_body}) --- [Suggestion] You can handle this type of event with the following listener function: @app.options({{"type": "dialog_suggestion", "callback_id": "the-id"}}) def handle_some_options(ack): ack(options=[ ... ]) -""" - == message - ) +""" == message def test_step(self): req: BoltRequest = BoltRequest(body=step_edit_payload, mode="socket_mode") @@ -255,8 +222,7 @@ def test_step(self): "type": "workflow_step_edit", "callback_id": "copy_review", } - assert ( - f"""Unhandled request ({filtered_body}) + assert f"""Unhandled request ({filtered_body}) --- [Suggestion] You can handle this type of event with the following listener function: @@ -269,17 +235,14 @@ def test_step(self): ) # Pass Step to set up listeners app.step(ws) -""" - == message - ) +""" == message req: BoltRequest = BoltRequest(body=step_save_payload, mode="socket_mode") message = warning_unhandled_request(req) filtered_body = { "type": "view_submission", "view": {"type": "workflow_step", "callback_id": "copy_review"}, } - assert ( - f"""Unhandled request ({filtered_body}) + assert f"""Unhandled request ({filtered_body}) --- [Suggestion] You can handle this type of event with the following listener function: @@ -292,17 +255,14 @@ def test_step(self): ) # Pass Step to set up listeners app.step(ws) -""" - == message - ) +""" == message req: BoltRequest = BoltRequest(body=step_execute_payload, mode="socket_mode") message = warning_unhandled_request(req) filtered_body = { "type": "event_callback", "event": {"type": "workflow_step_execute"}, } - assert ( - f"""Unhandled request ({filtered_body}) + assert f"""Unhandled request ({filtered_body}) --- [Suggestion] You can handle this type of event with the following listener function: @@ -315,9 +275,7 @@ def test_step(self): ) # Pass Step to set up listeners app.step(ws) -""" - == message - ) +""" == message block_actions = { diff --git a/docs/static/.nojekyll b/tests/slack_bolt/middleware/attaching_conversation_kwargs/__init__.py similarity index 100% rename from docs/static/.nojekyll rename to tests/slack_bolt/middleware/attaching_conversation_kwargs/__init__.py diff --git a/tests/slack_bolt/middleware/attaching_conversation_kwargs/test_attaching_conversation_kwargs.py b/tests/slack_bolt/middleware/attaching_conversation_kwargs/test_attaching_conversation_kwargs.py new file mode 100644 index 000000000..3f46a6b67 --- /dev/null +++ b/tests/slack_bolt/middleware/attaching_conversation_kwargs/test_attaching_conversation_kwargs.py @@ -0,0 +1,197 @@ +from slack_sdk import WebClient + +from slack_bolt.middleware.attaching_conversation_kwargs import AttachingConversationKwargs +from slack_bolt.request import BoltRequest +from slack_bolt.response import BoltResponse +from tests.scenario_tests.test_events_assistant import ( + build_payload, + thread_started_event_body, + user_message_event_body, + channel_user_message_event_body, +) + + +def next(): + return BoltResponse(status=200) + + +ASSISTANT_KWARGS = ("say", "set_title", "set_suggested_prompts", "get_thread_context", "save_thread_context") + +# A top-level DM (not in a thread) is not an assistant thread, but set_suggested_prompts is still attached. +top_level_im_message_event_body = build_payload( + { + "user": "W222", + "type": "message", + "ts": "1726133700.887259", + "text": "A top-level DM, not in a thread", + "channel": "D111", + "event_ts": "1726133700.887259", + "channel_type": "im", + } +) + +# A bot-authored top-level DM is also in scope: set_suggested_prompts is attached for any IM message. +bot_im_message_event_body = build_payload( + { + "type": "message", + "ts": "1726133700.887259", + "text": "A DM authored by a bot", + "user": "UB111", + "bot_id": "B111", + "app_id": "A222", + "channel": "D111", + "event_ts": "1726133700.887259", + "channel_type": "im", + } +) + +# A file_share DM is in scope too (subtype "file_share" passes is_im_message_event). +file_share_im_message_event_body = build_payload( + { + "user": "W222", + "type": "message", + "subtype": "file_share", + "ts": "1726133700.887259", + "text": "uploaded a file", + "channel": "D111", + "event_ts": "1726133700.887259", + "channel_type": "im", + } +) + +# Opening the Messages tab of App Home is in scope for set_suggested_prompts. +app_home_opened_messages_event_body = build_payload( + { + "type": "app_home_opened", + "user": "W222", + "channel": "D111", + "tab": "messages", + "event_ts": "1726133700.887259", + } +) + +# Opening the Home tab is NOT in scope: set_suggested_prompts should not be attached. +app_home_opened_home_event_body = build_payload( + { + "type": "app_home_opened", + "user": "W222", + "channel": "D111", + "tab": "home", + "event_ts": "1726133700.887259", + } +) + + +class TestAttachingConversationKwargs: + def test_assistant_event_attaches_kwargs(self): + middleware = AttachingConversationKwargs() + req = BoltRequest(body=thread_started_event_body, mode="socket_mode") + req.context["client"] = WebClient(token="xoxb-test") + + resp = middleware.process(req=req, resp=BoltResponse(status=404), next=next) + + assert resp.status == 200 + for key in ASSISTANT_KWARGS: + assert key in req.context, f"{key} should be set on context" + assert req.context["say"].thread_ts == "1726133698.626339" + assert "say_stream" in req.context + assert "set_status" in req.context + + def test_user_message_event_attaches_kwargs(self): + middleware = AttachingConversationKwargs() + req = BoltRequest(body=user_message_event_body, mode="socket_mode") + req.context["client"] = WebClient(token="xoxb-test") + + resp = middleware.process(req=req, resp=BoltResponse(status=404), next=next) + + assert resp.status == 200 + for key in ASSISTANT_KWARGS: + assert key in req.context, f"{key} should be set on context" + assert req.context["say"].thread_ts == "1726133698.626339" + assert "say_stream" in req.context + assert "set_status" in req.context + + def test_top_level_dm_attaches_suggested_prompts_but_not_set_title(self): + middleware = AttachingConversationKwargs() + req = BoltRequest(body=top_level_im_message_event_body, mode="socket_mode") + req.context["client"] = WebClient(token="xoxb-test") + + resp = middleware.process(req=req, resp=BoltResponse(status=404), next=next) + + assert resp.status == 200 + assert "set_suggested_prompts" in req.context + assert "set_title" not in req.context + assert "say" not in req.context + assert "get_thread_context" not in req.context + assert "save_thread_context" not in req.context + assert "say_stream" in req.context + assert "set_status" in req.context + + def test_bot_dm_attaches_suggested_prompts(self): + middleware = AttachingConversationKwargs() + req = BoltRequest(body=bot_im_message_event_body, mode="socket_mode") + req.context["client"] = WebClient(token="xoxb-test") + + resp = middleware.process(req=req, resp=BoltResponse(status=404), next=next) + + assert resp.status == 200 + assert "set_suggested_prompts" in req.context + + def test_file_share_dm_attaches_suggested_prompts(self): + middleware = AttachingConversationKwargs() + req = BoltRequest(body=file_share_im_message_event_body, mode="socket_mode") + req.context["client"] = WebClient(token="xoxb-test") + + resp = middleware.process(req=req, resp=BoltResponse(status=404), next=next) + + assert resp.status == 200 + assert "set_suggested_prompts" in req.context + + def test_app_home_opened_messages_tab_attaches_suggested_prompts(self): + middleware = AttachingConversationKwargs() + req = BoltRequest(body=app_home_opened_messages_event_body, mode="socket_mode") + req.context["client"] = WebClient(token="xoxb-test") + + resp = middleware.process(req=req, resp=BoltResponse(status=404), next=next) + + assert resp.status == 200 + assert "set_suggested_prompts" in req.context + assert "say" not in req.context + assert "set_title" not in req.context + assert "get_thread_context" not in req.context + assert "save_thread_context" not in req.context + + def test_app_home_opened_home_tab_does_not_attach_suggested_prompts(self): + middleware = AttachingConversationKwargs() + req = BoltRequest(body=app_home_opened_home_event_body, mode="socket_mode") + req.context["client"] = WebClient(token="xoxb-test") + + resp = middleware.process(req=req, resp=BoltResponse(status=404), next=next) + + assert resp.status == 200 + assert "set_suggested_prompts" not in req.context + + def test_non_assistant_event_does_not_attach_kwargs(self): + middleware = AttachingConversationKwargs() + req = BoltRequest(body=channel_user_message_event_body, mode="socket_mode") + req.context["client"] = WebClient(token="xoxb-test") + + resp = middleware.process(req=req, resp=BoltResponse(status=404), next=next) + + assert resp.status == 200 + for key in ASSISTANT_KWARGS: + assert key not in req.context, f"{key} should not be set on context" + assert "say_stream" in req.context + assert "set_status" in req.context + + def test_non_event_does_not_attach_kwargs(self): + middleware = AttachingConversationKwargs() + req = BoltRequest(body="payload={}", headers={}) + + resp = middleware.process(req=req, resp=BoltResponse(status=404), next=next) + + assert resp.status == 200 + for key in ASSISTANT_KWARGS: + assert key not in req.context, f"{key} should not be set on context" + assert "say_stream" not in req.context + assert "set_status" not in req.context diff --git a/tests/slack_bolt/middleware/request_verification/test_request_verification.py b/tests/slack_bolt/middleware/request_verification/test_request_verification.py index 2c9adea43..53af43bf3 100644 --- a/tests/slack_bolt/middleware/request_verification/test_request_verification.py +++ b/tests/slack_bolt/middleware/request_verification/test_request_verification.py @@ -1,5 +1,6 @@ from time import time +import pytest from slack_sdk.signature import SignatureVerifier from slack_bolt.middleware import RequestVerification @@ -45,3 +46,36 @@ def test_invalid(self): resp = middleware.process(req=req, resp=resp, next=next) assert resp.status == 401 assert resp.body == """{"error": "invalid request"}""" + + def test_ssl_check_param_requires_valid_signature(self): + middleware = RequestVerification(signing_secret=self.signing_secret) + req = BoltRequest( + body="token=random&ssl_check=1", + headers={ + "content-type": ["application/x-www-form-urlencoded"], + "x-slack-signature": ["v0=invalid"], + "x-slack-request-timestamp": ["0"], + }, + ) + resp = BoltResponse(status=404) + resp = middleware.process(req=req, resp=resp, next=next) + assert resp.status == 401 + assert resp.body == """{"error": "invalid request"}""" + + def test_empty_signing_secret_does_not_raise_on_init(self): + RequestVerification(signing_secret="") + + def test_socket_mode_request_skips_verification_without_signing_secret(self): + middleware = RequestVerification(signing_secret="") + req = BoltRequest(mode="socket_mode", body="payload={}", headers={}) + resp = BoltResponse(status=404, body="default") + resp = middleware.process(req=req, resp=resp, next=next) + assert resp.status == 200 + assert resp.body == "next" + + def test_http_request_with_empty_signing_secret_raises(self): + middleware = RequestVerification(signing_secret="") + req = BoltRequest(body="payload={}", headers={}) + resp = BoltResponse(status=404) + with pytest.raises(ValueError): + middleware.process(req=req, resp=resp, next=next) diff --git a/tests/slack_bolt/request/test_internals.py b/tests/slack_bolt/request/test_internals.py index 752fa6d2d..31ac35bdd 100644 --- a/tests/slack_bolt/request/test_internals.py +++ b/tests/slack_bolt/request/test_internals.py @@ -13,6 +13,7 @@ extract_actor_team_id, extract_actor_user_id, extract_function_execution_id, + extract_thread_ts, ) @@ -111,6 +112,196 @@ def teardown_method(self): }, ] + thread_ts_event_requests = [ + { + "event": { + "type": "app_mention", + "channel": "C111", + "user": "U111", + "ts": "123.420", + "thread_ts": "123.456", + }, + }, + { + "event": { + "type": "message", + "channel": "C111", + "user": "U111", + "ts": "123.420", + "thread_ts": "123.456", + }, + }, + { + "event": { + "type": "message", + "subtype": "bot_message", + "channel": "C111", + "bot_id": "B111", + "ts": "123.420", + "thread_ts": "123.456", + }, + }, + { + "event": { + "type": "message", + "subtype": "file_share", + "channel": "C111", + "user": "U111", + "ts": "123.420", + "thread_ts": "123.456", + }, + }, + { + "event": { + "type": "message", + "subtype": "thread_broadcast", + "channel": "C111", + "user": "U111", + "ts": "123.420", + "thread_ts": "123.456", + "root": {"thread_ts": "123.420"}, + }, + }, + { + "event": { + "type": "link_shared", + "channel": "C111", + "user": "U111", + "thread_ts": "123.456", + "links": [{"url": "https://example.com"}], + }, + }, + { + "event": { + "type": "message", + "subtype": "message_changed", + "channel": "C111", + "message": { + "type": "message", + "user": "U111", + "text": "edited", + "ts": "123.420", + "thread_ts": "123.456", + }, + }, + }, + { + "event": { + "type": "message", + "subtype": "message_changed", + "channel": "C111", + "message": { + "type": "message", + "user": "U111", + "text": "edited", + "ts": "123.420", + "thread_ts": "123.456", + }, + "previous_message": { + "type": "message", + "user": "U111", + "text": "deleted", + "ts": "123.420", + "thread_ts": "123.420", + }, + }, + }, + { + "event": { + "type": "message", + "subtype": "message_deleted", + "channel": "C111", + "previous_message": { + "type": "message", + "user": "U111", + "text": "deleted", + "ts": "123.420", + "thread_ts": "123.456", + }, + }, + }, + { + "event": { + "type": "assistant_thread_started", + "assistant_thread": { + "user_id": "U123ABC456", + "context": { + "channel_id": "C123ABC456", + "team_id": "T123ABC456", + "enterprise_id": "E123ABC456", + }, + "channel_id": "D123ABC456", + "thread_ts": "123.456", + }, + "event_ts": "1715873754.429808", + }, + }, + { + "event": { + "type": "assistant_thread_context_changed", + "assistant_thread": { + "user_id": "U123ABC456", + "context": { + "channel_id": "C123ABC456", + "team_id": "T123ABC456", + "enterprise_id": "E123ABC456", + }, + "channel_id": "D123ABC456", + "thread_ts": "123.456", + }, + "event_ts": "17298244.022142", + }, + }, + { + "event": { + "type": "message", + "subtype": "message_changed", + "message": { + "text": "Chats from 2024-09-28", + "subtype": "assistant_app_thread", + "user": "U123456ABCD", + "type": "message", + "team": "T123456ABCD", + "thread_ts": "123.456", + "reply_count": 1, + "ts": "123.420", + }, + "channel": "D987654ABCD", + "hidden": True, + "ts": "123.420", + "event_ts": "123.420", + "channel_type": "im", + }, + }, + ] + + no_thread_ts_requests = [ + { + "event": { + "type": "reaction_added", + "user": "U111", + "reaction": "thumbsup", + "item": {"type": "message", "channel": "C111", "ts": "123.420"}, + }, + }, + { + "event": { + "type": "channel_created", + "channel": {"id": "C222", "name": "test", "created": 1678455198}, + }, + }, + { + "event": { + "type": "message", + "channel": "C111", + "user": "U111", + "text": "hello", + "ts": "123.420", + }, + }, + {}, + ] + slack_connect_authorizations = [ { "enterprise_id": "INSTALLED_ENTERPRISE_ID", @@ -337,6 +528,16 @@ def test_function_inputs_extraction(self): inputs = extract_function_inputs(req) assert inputs == {"customer_id": "Ux111"} + def test_extract_thread_ts(self): + for req in self.thread_ts_event_requests: + thread_ts = extract_thread_ts(req) + assert thread_ts == "123.456", f"Expected thread_ts for {req}" + + def test_extract_thread_ts_fail(self): + for req in self.no_thread_ts_requests: + thread_ts = extract_thread_ts(req) + assert thread_ts is None, f"Expected None for {req}" + def test_is_enterprise_install_extraction(self): for req in self.requests: should_be_false = extract_is_enterprise_install(req) @@ -1047,3 +1248,30 @@ def test_slack_connect_patterns(self): assert extract_actor_enterprise_id(request) == actor_enterprise_id assert extract_actor_team_id(request) == actor_team_id assert extract_actor_user_id(request) == actor_user_id + + def test_extraction_functions_invalid_dict_keys(self): + invalid_payloads = { + "event": {"event": "some_event_type"}, + "user": {"user": "U12345"}, + "user_missing_team_id": {"user": {"id": "U12345"}}, + "team": {"team": "T12345"}, + "view": {"view": "V12345"}, + "view_missing_team_id": {"view": {"id": "V12345"}}, + "message": {"message": "some text"}, + "item": {"item": "item_id"}, + "function_data": {"function_data": "fd_123"}, + "assistant_thread": {"assistant_thread": "at_123"}, + "previous_message": {"previous_message": "old_msg"}, + } + + for _, payload in invalid_payloads.items(): + # We only verify no TypeError/AttributeError is raised and that functions which + # would try to subscript the string value return None instead of crashing. + extract_enterprise_id(payload) + extract_team_id(payload) + extract_user_id(payload) + extract_channel_id(payload) + extract_thread_ts(payload) + extract_function_execution_id(payload) + extract_function_bot_access_token(payload) + extract_function_inputs(payload) diff --git a/tests/slack_bolt/request/test_payload_utils.py b/tests/slack_bolt/request/test_payload_utils.py new file mode 100644 index 000000000..576cb390d --- /dev/null +++ b/tests/slack_bolt/request/test_payload_utils.py @@ -0,0 +1,408 @@ +from slack_bolt.request.payload_utils import ( + is_event, + is_message_event, + is_any_im_message_event, + is_im_message_event, + is_assistant_event, + is_assistant_thread_started_event, + is_assistant_thread_context_changed_event, + is_app_home_opened_event, + is_user_message_event_in_assistant_thread, + is_bot_message_event_in_assistant_thread, + is_other_message_sub_event_in_assistant_thread, +) +from tests.scenario_tests.test_events_assistant import ( + build_payload, + thread_started_event_body, + thread_context_changed_event_body, + user_message_event_body, + user_message_event_body_with_assistant_thread, + message_changed_event_body, + channel_user_message_event_body, + channel_message_changed_event_body, +) +from tests.scenario_tests.test_message_bot import ( + bot_message_event_payload, + classic_bot_message_event_payload, +) +from tests.scenario_tests.test_message_deleted import event_payload as message_deleted_channel_body +from tests.scenario_tests.test_events_ignore_self import event_body as reaction_added_event_body +from tests.scenario_tests.test_block_actions import body as block_actions_body + +file_share_im_message_body = build_payload( + { + "user": "W222", + "type": "message", + "subtype": "file_share", + "ts": "1726133700.887259", + "text": "uploaded a file", + "files": [ + { + "id": "F111", + "created": 1726133700, + "name": "test.png", + "title": "test.png", + "mimetype": "image/png", + "filetype": "png", + "user": "W222", + "size": 12345, + "mode": "hosted", + "is_external": False, + "is_public": False, + "url_private": "https://files.slack.com/files-pri/T111-F111/test.png", + "permalink": "https://example.slack.com/files/W222/F111/test.png", + } + ], + "upload": True, + "display_as_bot": False, + "thread_ts": "1726133698.626339", + "channel": "D111", + "event_ts": "1726133700.887259", + "channel_type": "im", + } +) + +bot_im_thread_message_body = build_payload( + { + "type": "message", + "ts": "1726133700.887259", + "text": "Here is your answer", + "user": "UB111", + "bot_id": "B111", + "app_id": "A222", + "bot_profile": { + "id": "B111", + "deleted": False, + "name": "assistant-app", + "updated": 1726133600, + "app_id": "A222", + "team_id": "T111", + }, + "thread_ts": "1726133698.626339", + "channel": "D111", + "event_ts": "1726133700.887259", + "channel_type": "im", + } +) + +im_message_no_thread_ts_body = build_payload( + { + "user": "W222", + "type": "message", + "ts": "1726133700.887259", + "text": "A top-level DM, not in a thread", + "channel": "D111", + "event_ts": "1726133700.887259", + "channel_type": "im", + } +) + +app_home_opened_messages_body = build_payload( + { + "type": "app_home_opened", + "user": "W222", + "channel": "D111", + "tab": "messages", + "event_ts": "1726133700.887259", + } +) + +app_home_opened_home_body = build_payload( + { + "type": "app_home_opened", + "user": "W222", + "channel": "D111", + "tab": "home", + "event_ts": "1726133700.887259", + } +) + +slash_command_body = { + "token": "verification_token", + "command": "/test", + "text": "hello", + "user_id": "U111", + "user_name": "primary-owner", + "channel_id": "C111", + "channel_name": "test-channel", + "team_id": "T111", + "team_domain": "test-domain", + "api_app_id": "A111", + "is_enterprise_install": "false", + "response_url": "https://hooks.slack.com/commands/T111/111/xxx", + "trigger_id": "111.222.xxx", +} + + +class TestPayloadUtils: + def test_is_message_event(self): + positives = { + "user_message_im": user_message_event_body, + "user_message_im_with_assistant_thread": user_message_event_body_with_assistant_thread, + "message_changed_im": message_changed_event_body, + "channel_user_message": channel_user_message_event_body, + "channel_message_changed": channel_message_changed_event_body, + "bot_message_channel": bot_message_event_payload, + "classic_bot_message_channel": classic_bot_message_event_payload, + "message_deleted_channel": message_deleted_channel_body, + "file_share_im": file_share_im_message_body, + "bot_im_thread": bot_im_thread_message_body, + "im_no_thread_ts": im_message_no_thread_ts_body, + } + negatives = { + "thread_started": thread_started_event_body, + "thread_context_changed": thread_context_changed_event_body, + "reaction_added": reaction_added_event_body, + "block_actions": block_actions_body, + "slash_command": slash_command_body, + "empty_dict": {}, + } + for key, body in positives.items(): + assert is_message_event(body), f"{key} should be recognized as a message event" + for key, body in negatives.items(): + assert not is_message_event(body), f"{key} should NOT be recognized as a message event" + + def test_is_any_im_message_event(self): + positives = { + "user_message_im": user_message_event_body, + "user_message_im_with_assistant_thread": user_message_event_body_with_assistant_thread, + "message_changed_im": message_changed_event_body, + "file_share_im": file_share_im_message_body, + "bot_im_thread": bot_im_thread_message_body, + "im_no_thread_ts": im_message_no_thread_ts_body, + } + negatives = { + "channel_user_message": channel_user_message_event_body, + "channel_message_changed": channel_message_changed_event_body, + "bot_message_channel": bot_message_event_payload, + "classic_bot_message_channel": classic_bot_message_event_payload, + "message_deleted_channel": message_deleted_channel_body, + "thread_started": thread_started_event_body, + "thread_context_changed": thread_context_changed_event_body, + "reaction_added": reaction_added_event_body, + "block_actions": block_actions_body, + "slash_command": slash_command_body, + } + for key, body in positives.items(): + assert is_any_im_message_event(body), f"{key} should pass {is_any_im_message_event.__name__}" + for key, body in negatives.items(): + assert not is_any_im_message_event(body), f"{key} should NOT pass {is_any_im_message_event.__name__}" + + def test_is_im_message_event(self): + # subtype must be None or "file_share" to pass + positives = { + "user_message_im": user_message_event_body, + "user_message_im_with_assistant_thread": user_message_event_body_with_assistant_thread, + "file_share_im": file_share_im_message_body, + "bot_im_thread": bot_im_thread_message_body, + "im_no_thread_ts": im_message_no_thread_ts_body, + } + negatives = { + "message_changed_im": message_changed_event_body, + "channel_user_message": channel_user_message_event_body, + "channel_message_changed": channel_message_changed_event_body, + "classic_bot_message_channel": classic_bot_message_event_payload, + "bot_message_channel": bot_message_event_payload, + "message_deleted_channel": message_deleted_channel_body, + "thread_started": thread_started_event_body, + "thread_context_changed": thread_context_changed_event_body, + "reaction_added": reaction_added_event_body, + "block_actions": block_actions_body, + } + for key, body in positives.items(): + assert is_im_message_event(body), f"{key} should pass {is_im_message_event.__name__}" + for key, body in negatives.items(): + assert not is_im_message_event(body), f"{key} should NOT pass {is_im_message_event.__name__}" + + def test_is_event(self): + positives = { + "thread_started": thread_started_event_body, + "thread_context_changed": thread_context_changed_event_body, + "user_message_im": user_message_event_body, + "user_message_im_with_assistant_thread": user_message_event_body_with_assistant_thread, + "message_changed_im": message_changed_event_body, + "channel_user_message": channel_user_message_event_body, + "channel_message_changed": channel_message_changed_event_body, + "bot_message_channel": bot_message_event_payload, + "classic_bot_message_channel": classic_bot_message_event_payload, + "message_deleted_channel": message_deleted_channel_body, + "reaction_added": reaction_added_event_body, + "file_share_im": file_share_im_message_body, + "bot_im_thread": bot_im_thread_message_body, + "im_no_thread_ts": im_message_no_thread_ts_body, + } + negatives = { + "block_actions": block_actions_body, + "slash_command": slash_command_body, + "empty_dict": {}, + } + for key, body in positives.items(): + assert is_event(body), f"{key} should be recognized as an event" + for key, body in negatives.items(): + assert not is_event(body), f"{key} should NOT be recognized as an event" + + def test_is_user_message_event_in_assistant_thread(self): + # Requires: is_im_message_event + thread_ts present + bot_id absent + positives = { + "user_message_im": user_message_event_body, + "user_message_im_with_assistant_thread": user_message_event_body_with_assistant_thread, + "file_share_im": file_share_im_message_body, + } + negatives = { + "bot_im_thread": bot_im_thread_message_body, + "im_no_thread_ts": im_message_no_thread_ts_body, + "message_changed_im": message_changed_event_body, + "channel_user_message": channel_user_message_event_body, + "channel_message_changed": channel_message_changed_event_body, + "bot_message_channel": bot_message_event_payload, + "thread_started": thread_started_event_body, + "thread_context_changed": thread_context_changed_event_body, + "reaction_added": reaction_added_event_body, + "block_actions": block_actions_body, + } + for key, body in positives.items(): + assert is_user_message_event_in_assistant_thread( + body + ), f"{key} should pass {is_user_message_event_in_assistant_thread.__name__}" + for key, body in negatives.items(): + assert not is_user_message_event_in_assistant_thread( + body + ), f"{key} should NOT pass {is_user_message_event_in_assistant_thread.__name__}" + + def test_is_bot_message_event_in_assistant_thread(self): + positives = { + "bot_im_thread": bot_im_thread_message_body, + } + negatives = { + "user_message_im": user_message_event_body, + "file_share_im": file_share_im_message_body, + "im_no_thread_ts": im_message_no_thread_ts_body, + "message_changed_im": message_changed_event_body, + "channel_user_message": channel_user_message_event_body, + "bot_message_channel": bot_message_event_payload, + "classic_bot_message_channel": classic_bot_message_event_payload, + "thread_started": thread_started_event_body, + "thread_context_changed": thread_context_changed_event_body, + "reaction_added": reaction_added_event_body, + "block_actions": block_actions_body, + } + for key, body in positives.items(): + assert is_bot_message_event_in_assistant_thread( + body + ), f"{key} should pass {is_bot_message_event_in_assistant_thread.__name__}" + for key, body in negatives.items(): + assert not is_bot_message_event_in_assistant_thread( + body + ), f"{key} should NOT pass {is_bot_message_event_in_assistant_thread.__name__}" + + def test_is_bot_message_user_message_asymmetry(self): + assert is_user_message_event_in_assistant_thread(file_share_im_message_body) + assert not is_bot_message_event_in_assistant_thread(file_share_im_message_body) + + assert is_bot_message_event_in_assistant_thread(bot_im_thread_message_body) + assert not is_user_message_event_in_assistant_thread(bot_im_thread_message_body) + + def test_is_other_message_sub_event_in_assistant_thread(self): + positives = { + "message_changed_im": message_changed_event_body, + } + negatives = { + "user_message_im": user_message_event_body, + "bot_im_thread": bot_im_thread_message_body, + "im_no_thread_ts": im_message_no_thread_ts_body, + "channel_message_changed": channel_message_changed_event_body, + "channel_user_message": channel_user_message_event_body, + "message_deleted_channel": message_deleted_channel_body, + "thread_started": thread_started_event_body, + "reaction_added": reaction_added_event_body, + "block_actions": block_actions_body, + } + for key, body in positives.items(): + assert is_other_message_sub_event_in_assistant_thread( + body + ), f"{key} should pass {is_other_message_sub_event_in_assistant_thread.__name__}" + for key, body in negatives.items(): + assert not is_other_message_sub_event_in_assistant_thread( + body + ), f"{key} should NOT pass {is_other_message_sub_event_in_assistant_thread.__name__}" + + def test_is_assistant_event(self): + positives = { + "thread_started": thread_started_event_body, + "thread_context_changed": thread_context_changed_event_body, + "user_message_im": user_message_event_body, + "user_message_im_with_assistant_thread": user_message_event_body_with_assistant_thread, + "file_share_im": file_share_im_message_body, + "bot_im_thread": bot_im_thread_message_body, + } + negatives = { + "message_changed_im": message_changed_event_body, + "im_no_thread_ts": im_message_no_thread_ts_body, + "channel_user_message": channel_user_message_event_body, + "channel_message_changed": channel_message_changed_event_body, + "bot_message_channel": bot_message_event_payload, + "classic_bot_message_channel": classic_bot_message_event_payload, + "message_deleted_channel": message_deleted_channel_body, + "reaction_added": reaction_added_event_body, + "block_actions": block_actions_body, + } + for key, body in positives.items(): + assert is_assistant_event(body), f"{key} should pass {is_assistant_event.__name__}" + for key, body in negatives.items(): + assert not is_assistant_event(body), f"{key} should NOT pass {is_assistant_event.__name__}" + + def test_is_assistant_thread_started_event(self): + assert is_assistant_thread_started_event(thread_started_event_body) + + negatives = { + "thread_context_changed": thread_context_changed_event_body, + "user_message_im": user_message_event_body, + "message_changed_im": message_changed_event_body, + "bot_im_thread": bot_im_thread_message_body, + "channel_user_message": channel_user_message_event_body, + "reaction_added": reaction_added_event_body, + "block_actions": block_actions_body, + } + for key, body in negatives.items(): + assert not is_assistant_thread_started_event( + body + ), f"{key} should NOT pass {is_assistant_thread_started_event.__name__}" + + def test_is_assistant_thread_context_changed_event(self): + assert is_assistant_thread_context_changed_event(thread_context_changed_event_body) + + negatives = { + "thread_started": thread_started_event_body, + "user_message_im": user_message_event_body, + "message_changed_im": message_changed_event_body, + "bot_im_thread": bot_im_thread_message_body, + "channel_user_message": channel_user_message_event_body, + "reaction_added": reaction_added_event_body, + "block_actions": block_actions_body, + } + for key, body in negatives.items(): + assert not is_assistant_thread_context_changed_event( + body + ), f"{key} should NOT pass {is_assistant_thread_context_changed_event.__name__}" + + def test_is_app_home_opened_event(self): + assert is_app_home_opened_event(app_home_opened_messages_body) + assert is_app_home_opened_event(app_home_opened_home_body) + + assert is_app_home_opened_event(app_home_opened_messages_body, tab="messages") + assert not is_app_home_opened_event(app_home_opened_home_body, tab="messages") + + negatives = { + "thread_started": thread_started_event_body, + "thread_context_changed": thread_context_changed_event_body, + "user_message_im": user_message_event_body, + "channel_user_message": channel_user_message_event_body, + "reaction_added": reaction_added_event_body, + "block_actions": block_actions_body, + "empty_dict": {}, + } + for key, body in negatives.items(): + assert not is_app_home_opened_event(body), f"{key} should NOT pass {is_app_home_opened_event.__name__}" + assert not is_app_home_opened_event( + body, tab="messages" + ), f"{key} should NOT pass {is_app_home_opened_event.__name__} with tab='messages'" diff --git a/tests/slack_bolt_async/authorization/test_async_authorize.py b/tests/slack_bolt_async/authorization/test_async_authorize.py index f978ebfa7..d98a1062f 100644 --- a/tests/slack_bolt_async/authorization/test_async_authorize.py +++ b/tests/slack_bolt_async/authorization/test_async_authorize.py @@ -21,7 +21,7 @@ assert_auth_test_count_async, setup_mock_web_api_server_async, ) -from tests.utils import remove_os_env_temporarily, restore_os_env, get_event_loop +from tests.utils import remove_os_env_temporarily, restore_os_env class TestAsyncAuthorize: @@ -30,16 +30,14 @@ class TestAsyncAuthorize: base_url=mock_api_server_base_url, ) - @pytest.fixture - def event_loop(self): + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server_async(self) try: - setup_mock_web_api_server_async(self) - loop = get_event_loop() - yield loop - loop.close() - cleanup_mock_web_api_server_async(self) + yield # run the test here finally: + cleanup_mock_web_api_server_async(self) restore_os_env(old_os_env) @pytest.mark.asyncio diff --git a/tests/slack_bolt_async/context/test_async_complete.py b/tests/slack_bolt_async/context/test_async_complete.py index f2fd115ec..4277d4218 100644 --- a/tests/slack_bolt_async/context/test_async_complete.py +++ b/tests/slack_bolt_async/context/test_async_complete.py @@ -7,20 +7,23 @@ setup_mock_web_api_server, cleanup_mock_web_api_server, ) +from tests.utils import remove_os_env_temporarily, restore_os_env class TestAsyncComplete: - @pytest.fixture - def event_loop(self): + + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): + old_os_env = remove_os_env_temporarily() setup_mock_web_api_server(self) valid_token = "xoxb-valid" mock_api_server_base_url = "http://localhost:8888" - self.web_client = AsyncWebClient(token=valid_token, base_url=mock_api_server_base_url) - - loop = asyncio.get_event_loop() - yield loop - loop.close() - cleanup_mock_web_api_server(self) + try: + self.web_client = AsyncWebClient(token=valid_token, base_url=mock_api_server_base_url) + yield # run the test here + finally: + cleanup_mock_web_api_server(self) + restore_os_env(old_os_env) @pytest.mark.asyncio async def test_complete(self): @@ -36,3 +39,14 @@ async def test_complete_no_function_execution_id(self): with pytest.raises(ValueError): await complete(outputs={"key": "value"}) + + @pytest.mark.asyncio + async def test_has_been_called_false_initially(self): + complete = AsyncComplete(client=self.web_client, function_execution_id="fn1111") + assert complete.has_been_called() is False + + @pytest.mark.asyncio + async def test_has_been_called_true_after_complete(self): + complete = AsyncComplete(client=self.web_client, function_execution_id="fn1111") + await complete(outputs={"key": "value"}) + assert complete.has_been_called() is True diff --git a/tests/slack_bolt_async/context/test_async_fail.py b/tests/slack_bolt_async/context/test_async_fail.py index 854bc7521..d344a6c95 100644 --- a/tests/slack_bolt_async/context/test_async_fail.py +++ b/tests/slack_bolt_async/context/test_async_fail.py @@ -7,20 +7,22 @@ setup_mock_web_api_server, cleanup_mock_web_api_server, ) +from tests.utils import remove_os_env_temporarily, restore_os_env class TestAsyncFail: - @pytest.fixture - def event_loop(self): + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): + old_os_env = remove_os_env_temporarily() setup_mock_web_api_server(self) valid_token = "xoxb-valid" mock_api_server_base_url = "http://localhost:8888" - self.web_client = AsyncWebClient(token=valid_token, base_url=mock_api_server_base_url) - - loop = asyncio.get_event_loop() - yield loop - loop.close() - cleanup_mock_web_api_server(self) + try: + self.web_client = AsyncWebClient(token=valid_token, base_url=mock_api_server_base_url) + yield # run the test here + finally: + cleanup_mock_web_api_server(self) + restore_os_env(old_os_env) @pytest.mark.asyncio async def test_fail(self): @@ -36,3 +38,14 @@ async def test_fail_no_function_execution_id(self): with pytest.raises(ValueError): await fail(error="there was an error") + + @pytest.mark.asyncio + async def test_has_been_called_false_initially(self): + fail = AsyncFail(client=self.web_client, function_execution_id="fn1111") + assert fail.has_been_called() is False + + @pytest.mark.asyncio + async def test_has_been_called_true_after_fail(self): + fail = AsyncFail(client=self.web_client, function_execution_id="fn1111") + await fail(error="there was an error") + assert fail.has_been_called() is True diff --git a/tests/slack_bolt_async/context/test_async_respond.py b/tests/slack_bolt_async/context/test_async_respond.py index eba44d6a0..b47ef1056 100644 --- a/tests/slack_bolt_async/context/test_async_respond.py +++ b/tests/slack_bolt_async/context/test_async_respond.py @@ -1,6 +1,6 @@ import pytest -from tests.utils import get_event_loop +from tests.utils import remove_os_env_temporarily, restore_os_env from slack_bolt.context.respond.async_respond import AsyncRespond from tests.mock_web_api_server import ( cleanup_mock_web_api_server_async, @@ -9,13 +9,16 @@ class TestAsyncRespond: - @pytest.fixture - def event_loop(self): + + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): + old_os_env = remove_os_env_temporarily() setup_mock_web_api_server_async(self) - loop = get_event_loop() - yield loop - loop.close() - cleanup_mock_web_api_server_async(self) + try: + yield # run the test here + finally: + cleanup_mock_web_api_server_async(self) + restore_os_env(old_os_env) @pytest.mark.asyncio async def test_respond(self): diff --git a/tests/slack_bolt_async/context/test_async_say.py b/tests/slack_bolt_async/context/test_async_say.py index 77ac0cc0e..d8d63ae8a 100644 --- a/tests/slack_bolt_async/context/test_async_say.py +++ b/tests/slack_bolt_async/context/test_async_say.py @@ -2,26 +2,25 @@ from slack_sdk.web.async_client import AsyncWebClient from slack_sdk.web.async_slack_response import AsyncSlackResponse -from tests.utils import get_event_loop from slack_bolt.context.say.async_say import AsyncSay -from tests.mock_web_api_server import ( - cleanup_mock_web_api_server_async, - setup_mock_web_api_server_async, -) +from tests.mock_web_api_server import cleanup_mock_web_api_server_async, setup_mock_web_api_server_async +from tests.utils import remove_os_env_temporarily, restore_os_env class TestAsyncSay: - @pytest.fixture - def event_loop(self): + + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): + old_os_env = remove_os_env_temporarily() setup_mock_web_api_server_async(self) valid_token = "xoxb-valid" mock_api_server_base_url = "http://localhost:8888" - self.web_client = AsyncWebClient(token=valid_token, base_url=mock_api_server_base_url) - - loop = get_event_loop() - yield loop - loop.close() - cleanup_mock_web_api_server_async(self) + try: + self.web_client = AsyncWebClient(token=valid_token, base_url=mock_api_server_base_url) + yield # run the test here + finally: + cleanup_mock_web_api_server_async(self) + restore_os_env(old_os_env) @pytest.mark.asyncio async def test_say(self): @@ -29,6 +28,12 @@ async def test_say(self): response: AsyncSlackResponse = await say(text="Hi there!") assert response.status_code == 200 + @pytest.mark.asyncio + async def test_say_markdown_text(self): + say = AsyncSay(client=self.web_client, channel="C111") + response: AsyncSlackResponse = await say(markdown_text="**Greetings!**") + assert response.status_code == 200 + @pytest.mark.asyncio async def test_say_unfurl_options(self): say = AsyncSay(client=self.web_client, channel="C111") diff --git a/tests/slack_bolt_async/context/test_async_say_stream.py b/tests/slack_bolt_async/context/test_async_say_stream.py new file mode 100644 index 000000000..7ac084044 --- /dev/null +++ b/tests/slack_bolt_async/context/test_async_say_stream.py @@ -0,0 +1,141 @@ +import pytest +from unittest.mock import patch, MagicMock + +from slack_sdk.web.async_client import AsyncWebClient + +from slack_bolt.context.say_stream.async_say_stream import AsyncSayStream +from tests.utils import remove_os_env_temporarily, restore_os_env + + +class TestAsyncSayStream: + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): + old_os_env = remove_os_env_temporarily() + try: + self.web_client = AsyncWebClient(token="xoxb-valid") + yield + finally: + restore_os_env(old_os_env) + + @pytest.mark.asyncio + async def test_missing_channel_raises(self): + say_stream = AsyncSayStream(client=self.web_client, channel=None, thread_ts="111.222") + with pytest.raises(ValueError, match="channel"): + await say_stream() + + @pytest.mark.asyncio + async def test_missing_thread_ts_raises(self): + say_stream = AsyncSayStream(client=self.web_client, channel="C111", thread_ts=None) + with pytest.raises(ValueError, match="thread_ts"): + await say_stream() + + @pytest.mark.asyncio + async def test_default_params(self): + say_stream = AsyncSayStream( + client=self.web_client, + channel="C111", + recipient_team_id="T111", + recipient_user_id="U111", + thread_ts="111.222", + ) + mock_chat_stream = MagicMock() + + async def fake_chat_stream(**kwargs): + return mock_chat_stream(**kwargs) + + with patch.object(self.web_client, "chat_stream", side_effect=fake_chat_stream): + await say_stream() + mock_chat_stream.assert_called_once_with( + channel="C111", + recipient_team_id="T111", + recipient_user_id="U111", + thread_ts="111.222", + icon_emoji=None, + icon_url=None, + username=None, + ) + + @pytest.mark.asyncio + async def test_parameter_overrides(self): + say_stream = AsyncSayStream( + client=self.web_client, + channel="C111", + recipient_team_id="T111", + recipient_user_id="U111", + thread_ts="111.222", + ) + mock_chat_stream = MagicMock() + + async def fake_chat_stream(**kwargs): + return mock_chat_stream(**kwargs) + + with patch.object(self.web_client, "chat_stream", side_effect=fake_chat_stream): + await say_stream(channel="C222", thread_ts="333.444", recipient_team_id="T222", recipient_user_id="U222") + mock_chat_stream.assert_called_once_with( + channel="C222", + recipient_team_id="T222", + recipient_user_id="U222", + thread_ts="333.444", + icon_emoji=None, + icon_url=None, + username=None, + ) + + @pytest.mark.asyncio + async def test_buffer_size_overrides(self): + say_stream = AsyncSayStream( + client=self.web_client, + channel="C111", + recipient_team_id="T111", + recipient_user_id="U111", + thread_ts="111.222", + ) + mock_chat_stream = MagicMock() + + async def fake_chat_stream(**kwargs): + return mock_chat_stream(**kwargs) + + with patch.object(self.web_client, "chat_stream", side_effect=fake_chat_stream): + await say_stream( + buffer_size=100, + channel="C222", + thread_ts="333.444", + recipient_team_id="T222", + recipient_user_id="U222", + ) + mock_chat_stream.assert_called_once_with( + buffer_size=100, + channel="C222", + recipient_team_id="T222", + recipient_user_id="U222", + thread_ts="333.444", + icon_emoji=None, + icon_url=None, + username=None, + ) + + @pytest.mark.asyncio + async def test_authorship_overrides(self): + say_stream = AsyncSayStream( + client=self.web_client, + channel="C111", + recipient_team_id="T111", + recipient_user_id="U111", + thread_ts="111.222", + ) + mock_chat_stream = MagicMock() + + async def fake_chat_stream(**kwargs): + return mock_chat_stream(**kwargs) + + with patch.object(self.web_client, "chat_stream", side_effect=fake_chat_stream): + await say_stream(icon_emoji=":maple_leaf:", username="Charlie Brown") + mock_chat_stream.assert_called_once_with( + channel="C111", + recipient_team_id="T111", + recipient_user_id="U111", + thread_ts="111.222", + icon_emoji=":maple_leaf:", + icon_url=None, + username="Charlie Brown", + ) diff --git a/tests/slack_bolt_async/context/test_async_set_status.py b/tests/slack_bolt_async/context/test_async_set_status.py new file mode 100644 index 000000000..bcf1fcf19 --- /dev/null +++ b/tests/slack_bolt_async/context/test_async_set_status.py @@ -0,0 +1,57 @@ +import pytest +from slack_sdk.web.async_client import AsyncWebClient +from slack_sdk.web.async_slack_response import AsyncSlackResponse + +from slack_bolt.context.set_status.async_set_status import AsyncSetStatus +from tests.mock_web_api_server import cleanup_mock_web_api_server_async, setup_mock_web_api_server_async +from tests.utils import remove_os_env_temporarily, restore_os_env + + +class TestAsyncSetStatus: + + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): + old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server_async(self) + valid_token = "xoxb-valid" + mock_api_server_base_url = "http://localhost:8888" + try: + self.web_client = AsyncWebClient(token=valid_token, base_url=mock_api_server_base_url) + yield # run the test here + finally: + cleanup_mock_web_api_server_async(self) + restore_os_env(old_os_env) + + @pytest.mark.asyncio + async def test_set_status(self): + set_status = AsyncSetStatus(client=self.web_client, channel_id="C111", thread_ts="123.123") + response: AsyncSlackResponse = await set_status("Thinking...") + assert response.status_code == 200 + + @pytest.mark.asyncio + async def test_set_status_loading_messages(self): + set_status = AsyncSetStatus(client=self.web_client, channel_id="C111", thread_ts="123.123") + response: AsyncSlackResponse = await set_status( + status="Thinking...", + loading_messages=[ + "Sitting...", + "Waiting...", + ], + ) + assert response.status_code == 200 + + @pytest.mark.asyncio + async def test_set_status_authorship(self): + set_status = AsyncSetStatus(client=self.web_client, channel_id="C111", thread_ts="123.123") + response: AsyncSlackResponse = await set_status( + status="Thinking...", + icon_emoji=":maple_leaf:", + username="Charlie Brown", + ) + assert response.status_code == 200 + + @pytest.mark.asyncio + async def test_set_status_invalid(self): + set_status = AsyncSetStatus(client=self.web_client, channel_id="C111", thread_ts="123.123") + with pytest.raises(TypeError): + await set_status() diff --git a/tests/slack_bolt_async/context/test_async_set_suggested_prompts.py b/tests/slack_bolt_async/context/test_async_set_suggested_prompts.py new file mode 100644 index 000000000..7a92ff2a3 --- /dev/null +++ b/tests/slack_bolt_async/context/test_async_set_suggested_prompts.py @@ -0,0 +1,108 @@ +import asyncio +from unittest.mock import MagicMock, patch + +import pytest +from slack_sdk.web.async_client import AsyncWebClient +from slack_sdk.web.async_slack_response import AsyncSlackResponse + +from slack_bolt.context.set_suggested_prompts.async_set_suggested_prompts import AsyncSetSuggestedPrompts +from tests.mock_web_api_server import cleanup_mock_web_api_server, setup_mock_web_api_server +from tests.utils import remove_os_env_temporarily, restore_os_env + + +class TestAsyncSetSuggestedPrompts: + + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): + old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server(self) + valid_token = "xoxb-valid" + mock_api_server_base_url = "http://localhost:8888" + try: + self.web_client = AsyncWebClient(token=valid_token, base_url=mock_api_server_base_url) + yield # run the test here + finally: + cleanup_mock_web_api_server(self) + restore_os_env(old_os_env) + + @pytest.mark.asyncio + async def test_set_suggested_prompts(self): + set_suggested_prompts = AsyncSetSuggestedPrompts(client=self.web_client, channel_id="C111", thread_ts="123.123") + response: AsyncSlackResponse = await set_suggested_prompts(prompts=["One", "Two"]) + assert response.status_code == 200 + + @pytest.mark.asyncio + async def test_set_suggested_prompts_objects(self): + set_suggested_prompts = AsyncSetSuggestedPrompts(client=self.web_client, channel_id="C111", thread_ts="123.123") + response: AsyncSlackResponse = await set_suggested_prompts( + prompts=[ + "One", + {"title": "Two", "message": "What's before addition?"}, + ], + ) + assert response.status_code == 200 + + @pytest.mark.asyncio + async def test_set_suggested_prompts_without_thread_ts(self): + set_suggested_prompts = AsyncSetSuggestedPrompts(client=self.web_client, channel_id="C111") + mock_api = MagicMock() + + async def fake_api(**kwargs): + return mock_api(**kwargs) + + with patch.object( + self.web_client, self.web_client.assistant_threads_setSuggestedPrompts.__name__, side_effect=fake_api + ): + await set_suggested_prompts(prompts=["One", "Two"]) + mock_api.assert_called_once_with( + channel_id="C111", + thread_ts=None, + prompts=[{"title": "One", "message": "One"}, {"title": "Two", "message": "Two"}], + title=None, + ) + + @pytest.mark.asyncio + async def test_set_suggested_prompts_thread_ts_override(self): + # The call-time thread_ts must win over the stored one + set_suggested_prompts = AsyncSetSuggestedPrompts(client=self.web_client, channel_id="C111", thread_ts="999.999") + mock_api = MagicMock() + + async def fake_api(**kwargs): + return mock_api(**kwargs) + + with patch.object( + self.web_client, self.web_client.assistant_threads_setSuggestedPrompts.__name__, side_effect=fake_api + ): + await set_suggested_prompts(prompts=["One", "Two"], thread_ts="123.123") + mock_api.assert_called_once_with( + channel_id="C111", + thread_ts="123.123", + prompts=[{"title": "One", "message": "One"}, {"title": "Two", "message": "Two"}], + title=None, + ) + + @pytest.mark.asyncio + async def test_set_suggested_prompts_thread_ts_override_falsy(self): + # An explicitly passed falsy thread_ts must be forwarded, not swallowed by the stored value + set_suggested_prompts = AsyncSetSuggestedPrompts(client=self.web_client, channel_id="C111", thread_ts="123.123") + mock_api = MagicMock() + + async def fake_api(**kwargs): + return mock_api(**kwargs) + + with patch.object( + self.web_client, self.web_client.assistant_threads_setSuggestedPrompts.__name__, side_effect=fake_api + ): + await set_suggested_prompts(prompts=["One", "Two"], thread_ts="") + mock_api.assert_called_once_with( + channel_id="C111", + thread_ts="", + prompts=[{"title": "One", "message": "One"}, {"title": "Two", "message": "Two"}], + title=None, + ) + + @pytest.mark.asyncio + async def test_set_suggested_prompts_invalid(self): + set_suggested_prompts = AsyncSetSuggestedPrompts(client=self.web_client, channel_id="C111", thread_ts="123.123") + with pytest.raises(TypeError): + await set_suggested_prompts() diff --git a/tests/slack_bolt_async/logger/test_unmatched_suggestions.py b/tests/slack_bolt_async/logger/test_unmatched_suggestions.py index 93343c4a2..d8c659892 100644 --- a/tests/slack_bolt_async/logger/test_unmatched_suggestions.py +++ b/tests/slack_bolt_async/logger/test_unmatched_suggestions.py @@ -22,8 +22,7 @@ def test_block_actions(self): "block_id": "b", "action_id": "action-id-value", } - assert ( - f"""Unhandled request ({filtered_body}) + assert f"""Unhandled request ({filtered_body}) --- [Suggestion] You can handle this type of event with the following listener function: @@ -31,9 +30,7 @@ def test_block_actions(self): async def handle_some_action(ack, body, logger): await ack() logger.info(body) -""" - == message - ) +""" == message def test_attachment_actions(self): req: AsyncBoltRequest = AsyncBoltRequest(body=attachment_actions, mode="socket_mode") @@ -49,8 +46,7 @@ def test_attachment_actions(self): } ], } - assert ( - f"""Unhandled request ({filtered_body}) + assert f"""Unhandled request ({filtered_body}) --- [Suggestion] You can handle this type of event with the following listener function: @@ -58,9 +54,7 @@ def test_attachment_actions(self): async def handle_some_action(ack, body, logger): await ack() logger.info(body) -""" - == message - ) +""" == message def test_app_mention_event(self): req: AsyncBoltRequest = AsyncBoltRequest(body=app_mention_event, mode="socket_mode") @@ -69,17 +63,14 @@ def test_app_mention_event(self): "event": {"type": "app_mention"}, } message = warning_unhandled_request(req) - assert ( - f"""Unhandled request ({filtered_body}) + assert f"""Unhandled request ({filtered_body}) --- [Suggestion] You can handle this type of event with the following listener function: @app.event("app_mention") async def handle_app_mention_events(body, logger): logger.info(body) -""" - == message - ) +""" == message def test_function_event(self): req: AsyncBoltRequest = AsyncBoltRequest(body=function_event, mode="socket_mode") @@ -88,8 +79,7 @@ def test_function_event(self): "event": {"type": "function_executed"}, } message = warning_unhandled_request(req) - assert ( - f"""Unhandled request ({filtered_body}) + assert f"""Unhandled request ({filtered_body}) --- [Suggestion] You can handle this type of event with the following listener function: @@ -104,9 +94,7 @@ async def handle_some_function(ack, body, complete, fail, logger): except Exception as e: error = f"Failed to handle a function request (error: {{e}})" await fail(error=error) -""" - == message - ) +""" == message def test_commands(self): req: AsyncBoltRequest = AsyncBoltRequest(body=slash_command, mode="socket_mode") @@ -115,8 +103,7 @@ def test_commands(self): "type": None, "command": "/start-conv", } - assert ( - f"""Unhandled request ({filtered_body}) + assert f"""Unhandled request ({filtered_body}) --- [Suggestion] You can handle this type of event with the following listener function: @@ -124,9 +111,7 @@ def test_commands(self): async def handle_some_command(ack, body, logger): await ack() logger.info(body) -""" - == message - ) +""" == message def test_shortcut(self): req: AsyncBoltRequest = AsyncBoltRequest(body=global_shortcut, mode="socket_mode") @@ -135,8 +120,7 @@ def test_shortcut(self): "type": "shortcut", "callback_id": "test-shortcut", } - assert ( - f"""Unhandled request ({filtered_body}) + assert f"""Unhandled request ({filtered_body}) --- [Suggestion] You can handle this type of event with the following listener function: @@ -144,9 +128,7 @@ def test_shortcut(self): async def handle_shortcuts(ack, body, logger): await ack() logger.info(body) -""" - == message - ) +""" == message req: AsyncBoltRequest = AsyncBoltRequest(body=message_shortcut, mode="socket_mode") message = warning_unhandled_request(req) @@ -154,8 +136,7 @@ async def handle_shortcuts(ack, body, logger): "type": "message_action", "callback_id": "test-shortcut", } - assert ( - f"""Unhandled request ({filtered_body}) + assert f"""Unhandled request ({filtered_body}) --- [Suggestion] You can handle this type of event with the following listener function: @@ -163,9 +144,7 @@ async def handle_shortcuts(ack, body, logger): async def handle_shortcuts(ack, body, logger): await ack() logger.info(body) -""" - == message - ) +""" == message def test_view(self): req: AsyncBoltRequest = AsyncBoltRequest(body=view_submission, mode="socket_mode") @@ -174,8 +153,7 @@ def test_view(self): "type": "view_submission", "view": {"type": "modal", "callback_id": "view-id"}, } - assert ( - f"""Unhandled request ({filtered_body}) + assert f"""Unhandled request ({filtered_body}) --- [Suggestion] You can handle this type of event with the following listener function: @@ -183,9 +161,7 @@ def test_view(self): async def handle_view_submission_events(ack, body, logger): await ack() logger.info(body) -""" - == message - ) +""" == message req: AsyncBoltRequest = AsyncBoltRequest(body=view_closed, mode="socket_mode") message = warning_unhandled_request(req) @@ -193,8 +169,7 @@ async def handle_view_submission_events(ack, body, logger): "type": "view_closed", "view": {"type": "modal", "callback_id": "view-id"}, } - assert ( - f"""Unhandled request ({filtered_body}) + assert f"""Unhandled request ({filtered_body}) --- [Suggestion] You can handle this type of event with the following listener function: @@ -202,9 +177,7 @@ async def handle_view_submission_events(ack, body, logger): async def handle_view_closed_events(ack, body, logger): await ack() logger.info(body) -""" - == message - ) +""" == message def test_block_suggestion(self): req: AsyncBoltRequest = AsyncBoltRequest(body=block_suggestion, mode="socket_mode") @@ -216,17 +189,14 @@ def test_block_suggestion(self): "action_id": "the-id", "value": "search word", } - assert ( - f"""Unhandled request ({filtered_body}) + assert f"""Unhandled request ({filtered_body}) --- [Suggestion] You can handle this type of event with the following listener function: @app.options("the-id") async def handle_some_options(ack): await ack(options=[ ... ]) -""" - == message - ) +""" == message def test_dialog_suggestion(self): req: AsyncBoltRequest = AsyncBoltRequest(body=dialog_suggestion, mode="socket_mode") @@ -236,17 +206,14 @@ def test_dialog_suggestion(self): "callback_id": "the-id", "value": "search keyword", } - assert ( - f"""Unhandled request ({filtered_body}) + assert f"""Unhandled request ({filtered_body}) --- [Suggestion] You can handle this type of event with the following listener function: @app.options({{"type": "dialog_suggestion", "callback_id": "the-id"}}) async def handle_some_options(ack): await ack(options=[ ... ]) -""" - == message - ) +""" == message def test_step(self): req: AsyncBoltRequest = AsyncBoltRequest(body=step_edit_payload, mode="socket_mode") @@ -255,8 +222,7 @@ def test_step(self): "type": "workflow_step_edit", "callback_id": "copy_review", } - assert ( - f"""Unhandled request ({filtered_body}) + assert f"""Unhandled request ({filtered_body}) --- [Suggestion] You can handle this type of event with the following listener function: @@ -269,17 +235,14 @@ def test_step(self): ) # Pass Step to set up listeners app.step(ws) -""" - == message - ) +""" == message req: AsyncBoltRequest = AsyncBoltRequest(body=step_save_payload, mode="socket_mode") message = warning_unhandled_request(req) filtered_body = { "type": "view_submission", "view": {"type": "workflow_step", "callback_id": "copy_review"}, } - assert ( - f"""Unhandled request ({filtered_body}) + assert f"""Unhandled request ({filtered_body}) --- [Suggestion] You can handle this type of event with the following listener function: @@ -292,17 +255,14 @@ def test_step(self): ) # Pass Step to set up listeners app.step(ws) -""" - == message - ) +""" == message req: AsyncBoltRequest = AsyncBoltRequest(body=step_execute_payload, mode="socket_mode") message = warning_unhandled_request(req) filtered_body = { "type": "event_callback", "event": {"type": "workflow_step_execute"}, } - assert ( - f"""Unhandled request ({filtered_body}) + assert f"""Unhandled request ({filtered_body}) --- [Suggestion] You can handle this type of event with the following listener function: @@ -315,9 +275,7 @@ def test_step(self): ) # Pass Step to set up listeners app.step(ws) -""" - == message - ) +""" == message block_actions = { diff --git a/tests/slack_bolt_async/middleware/attaching_conversation_kwargs/__init__.py b/tests/slack_bolt_async/middleware/attaching_conversation_kwargs/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/slack_bolt_async/middleware/attaching_conversation_kwargs/test_async_attaching_conversation_kwargs.py b/tests/slack_bolt_async/middleware/attaching_conversation_kwargs/test_async_attaching_conversation_kwargs.py new file mode 100644 index 000000000..c7da4ce93 --- /dev/null +++ b/tests/slack_bolt_async/middleware/attaching_conversation_kwargs/test_async_attaching_conversation_kwargs.py @@ -0,0 +1,209 @@ +import pytest +from slack_sdk.web.async_client import AsyncWebClient + +from slack_bolt.middleware.attaching_conversation_kwargs.async_attaching_conversation_kwargs import ( + AsyncAttachingConversationKwargs, +) +from slack_bolt.request.async_request import AsyncBoltRequest +from slack_bolt.response import BoltResponse +from tests.scenario_tests_async.test_events_assistant import ( + build_payload, + thread_started_event_body, + user_message_event_body, + channel_user_message_event_body, +) + + +async def next(): + return BoltResponse(status=200) + + +ASSISTANT_KWARGS = ("say", "set_title", "set_suggested_prompts", "get_thread_context", "save_thread_context") + +# A top-level DM (not in a thread) is not an assistant thread, but set_suggested_prompts is still attached. +top_level_im_message_event_body = build_payload( + { + "user": "W222", + "type": "message", + "ts": "1726133700.887259", + "text": "A top-level DM, not in a thread", + "channel": "D111", + "event_ts": "1726133700.887259", + "channel_type": "im", + } +) + +# A bot-authored top-level DM is also in scope: set_suggested_prompts is attached for any IM message. +bot_im_message_event_body = build_payload( + { + "type": "message", + "ts": "1726133700.887259", + "text": "A DM authored by a bot", + "user": "UB111", + "bot_id": "B111", + "app_id": "A222", + "channel": "D111", + "event_ts": "1726133700.887259", + "channel_type": "im", + } +) + +# A file_share DM is in scope too (subtype "file_share" passes is_im_message_event). +file_share_im_message_event_body = build_payload( + { + "user": "W222", + "type": "message", + "subtype": "file_share", + "ts": "1726133700.887259", + "text": "uploaded a file", + "channel": "D111", + "event_ts": "1726133700.887259", + "channel_type": "im", + } +) + +# Opening the Messages tab of App Home is in scope for set_suggested_prompts. +app_home_opened_messages_event_body = build_payload( + { + "type": "app_home_opened", + "user": "W222", + "channel": "D111", + "tab": "messages", + "event_ts": "1726133700.887259", + } +) + +# Opening the Home tab is NOT in scope: set_suggested_prompts should not be attached. +app_home_opened_home_event_body = build_payload( + { + "type": "app_home_opened", + "user": "W222", + "channel": "D111", + "tab": "home", + "event_ts": "1726133700.887259", + } +) + + +class TestAsyncAttachingConversationKwargs: + @pytest.mark.asyncio + async def test_assistant_event_attaches_kwargs(self): + middleware = AsyncAttachingConversationKwargs() + req = AsyncBoltRequest(body=thread_started_event_body, mode="socket_mode") + req.context["client"] = AsyncWebClient(token="xoxb-test") + + resp = await middleware.async_process(req=req, resp=BoltResponse(status=404), next=next) + + assert resp.status == 200 + for key in ASSISTANT_KWARGS: + assert key in req.context, f"{key} should be set on context" + assert req.context["say"].thread_ts == "1726133698.626339" + assert "say_stream" in req.context + assert "set_status" in req.context + + @pytest.mark.asyncio + async def test_user_message_event_attaches_kwargs(self): + middleware = AsyncAttachingConversationKwargs() + req = AsyncBoltRequest(body=user_message_event_body, mode="socket_mode") + req.context["client"] = AsyncWebClient(token="xoxb-test") + + resp = await middleware.async_process(req=req, resp=BoltResponse(status=404), next=next) + + assert resp.status == 200 + for key in ASSISTANT_KWARGS: + assert key in req.context, f"{key} should be set on context" + assert req.context["say"].thread_ts == "1726133698.626339" + assert "say_stream" in req.context + assert "set_status" in req.context + + @pytest.mark.asyncio + async def test_top_level_dm_attaches_suggested_prompts_but_not_set_title(self): + middleware = AsyncAttachingConversationKwargs() + req = AsyncBoltRequest(body=top_level_im_message_event_body, mode="socket_mode") + req.context["client"] = AsyncWebClient(token="xoxb-test") + + resp = await middleware.async_process(req=req, resp=BoltResponse(status=404), next=next) + + assert resp.status == 200 + assert "set_suggested_prompts" in req.context + assert "set_title" not in req.context + assert "say" not in req.context + assert "get_thread_context" not in req.context + assert "save_thread_context" not in req.context + assert "say_stream" in req.context + assert "set_status" in req.context + + @pytest.mark.asyncio + async def test_bot_dm_attaches_suggested_prompts(self): + middleware = AsyncAttachingConversationKwargs() + req = AsyncBoltRequest(body=bot_im_message_event_body, mode="socket_mode") + req.context["client"] = AsyncWebClient(token="xoxb-test") + + resp = await middleware.async_process(req=req, resp=BoltResponse(status=404), next=next) + + assert resp.status == 200 + assert "set_suggested_prompts" in req.context + + @pytest.mark.asyncio + async def test_file_share_dm_attaches_suggested_prompts(self): + middleware = AsyncAttachingConversationKwargs() + req = AsyncBoltRequest(body=file_share_im_message_event_body, mode="socket_mode") + req.context["client"] = AsyncWebClient(token="xoxb-test") + + resp = await middleware.async_process(req=req, resp=BoltResponse(status=404), next=next) + + assert resp.status == 200 + assert "set_suggested_prompts" in req.context + + @pytest.mark.asyncio + async def test_app_home_opened_messages_tab_attaches_suggested_prompts(self): + middleware = AsyncAttachingConversationKwargs() + req = AsyncBoltRequest(body=app_home_opened_messages_event_body, mode="socket_mode") + req.context["client"] = AsyncWebClient(token="xoxb-test") + + resp = await middleware.async_process(req=req, resp=BoltResponse(status=404), next=next) + + assert resp.status == 200 + assert "set_suggested_prompts" in req.context + assert "say" not in req.context + assert "set_title" not in req.context + assert "get_thread_context" not in req.context + assert "save_thread_context" not in req.context + + @pytest.mark.asyncio + async def test_app_home_opened_home_tab_does_not_attach_suggested_prompts(self): + middleware = AsyncAttachingConversationKwargs() + req = AsyncBoltRequest(body=app_home_opened_home_event_body, mode="socket_mode") + req.context["client"] = AsyncWebClient(token="xoxb-test") + + resp = await middleware.async_process(req=req, resp=BoltResponse(status=404), next=next) + + assert resp.status == 200 + assert "set_suggested_prompts" not in req.context + + @pytest.mark.asyncio + async def test_non_assistant_event_does_not_attach_kwargs(self): + middleware = AsyncAttachingConversationKwargs() + req = AsyncBoltRequest(body=channel_user_message_event_body, mode="socket_mode") + req.context["client"] = AsyncWebClient(token="xoxb-test") + + resp = await middleware.async_process(req=req, resp=BoltResponse(status=404), next=next) + + assert resp.status == 200 + for key in ASSISTANT_KWARGS: + assert key not in req.context, f"{key} should not be set on context" + assert "say_stream" in req.context + assert "set_status" in req.context + + @pytest.mark.asyncio + async def test_non_event_does_not_attach_kwargs(self): + middleware = AsyncAttachingConversationKwargs() + req = AsyncBoltRequest(body="payload={}", headers={}) + + resp = await middleware.async_process(req=req, resp=BoltResponse(status=404), next=next) + + assert resp.status == 200 + for key in ASSISTANT_KWARGS: + assert key not in req.context, f"{key} should not be set on context" + assert "say_stream" not in req.context + assert "set_status" not in req.context diff --git a/tests/slack_bolt_async/middleware/authorization/test_single_team_authorization.py b/tests/slack_bolt_async/middleware/authorization/test_single_team_authorization.py index e90eae5c8..0ddb6281d 100644 --- a/tests/slack_bolt_async/middleware/authorization/test_single_team_authorization.py +++ b/tests/slack_bolt_async/middleware/authorization/test_single_team_authorization.py @@ -11,7 +11,7 @@ cleanup_mock_web_api_server_async, setup_mock_web_api_server_async, ) -from tests.utils import remove_os_env_temporarily, restore_os_env, get_event_loop +from tests.utils import remove_os_env_temporarily, restore_os_env async def next(): @@ -19,18 +19,16 @@ async def next(): class TestSingleTeamAuthorization: - mock_api_server_base_url = "http://localhost:8888" - @pytest.fixture - def event_loop(self): + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): old_os_env = remove_os_env_temporarily() + setup_mock_web_api_server_async(self) try: - setup_mock_web_api_server_async(self) - loop = get_event_loop() - yield loop - loop.close() - cleanup_mock_web_api_server_async(self) + self.mock_api_server_base_url = "http://localhost:8888" + yield # run the test here finally: + cleanup_mock_web_api_server_async(self) restore_os_env(old_os_env) @pytest.mark.asyncio diff --git a/tests/slack_bolt_async/middleware/request_verification/test_request_verification.py b/tests/slack_bolt_async/middleware/request_verification/test_request_verification.py index 0b05079a9..126b04f94 100644 --- a/tests/slack_bolt_async/middleware/request_verification/test_request_verification.py +++ b/tests/slack_bolt_async/middleware/request_verification/test_request_verification.py @@ -1,7 +1,6 @@ from time import time import pytest -from tests.utils import get_event_loop from slack_sdk.signature import SignatureVerifier from slack_bolt.middleware.request_verification.async_request_verification import ( @@ -32,12 +31,6 @@ def build_headers(self, timestamp: str, body: str): "x-slack-request-timestamp": [timestamp], } - @pytest.fixture - def event_loop(self): - loop = get_event_loop() - yield loop - loop.close() - @pytest.mark.asyncio async def test_valid(self): middleware = AsyncRequestVerification(signing_secret="secret") @@ -57,3 +50,39 @@ async def test_invalid(self): resp = await middleware.async_process(req=req, resp=resp, next=next) assert resp.status == 401 assert resp.body == """{"error": "invalid request"}""" + + @pytest.mark.asyncio + async def test_ssl_check_param_requires_valid_signature(self): + middleware = AsyncRequestVerification(signing_secret="secret") + req = AsyncBoltRequest( + body="token=random&ssl_check=1", + headers={ + "content-type": ["application/x-www-form-urlencoded"], + "x-slack-signature": ["v0=invalid"], + "x-slack-request-timestamp": ["0"], + }, + ) + resp = BoltResponse(status=404) + resp = await middleware.async_process(req=req, resp=resp, next=next) + assert resp.status == 401 + assert resp.body == """{"error": "invalid request"}""" + + def test_empty_signing_secret_does_not_raise_on_init(self): + AsyncRequestVerification(signing_secret="") + + @pytest.mark.asyncio + async def test_socket_mode_request_skips_verification_without_signing_secret(self): + middleware = AsyncRequestVerification(signing_secret="") + req = AsyncBoltRequest(mode="socket_mode", body="payload={}", headers={}) + resp = BoltResponse(status=404, body="default") + resp = await middleware.async_process(req=req, resp=resp, next=next) + assert resp.status == 200 + assert resp.body == "next" + + @pytest.mark.asyncio + async def test_http_request_with_empty_signing_secret_raises(self): + middleware = AsyncRequestVerification(signing_secret="") + req = AsyncBoltRequest(body="payload={}", headers={}) + resp = BoltResponse(status=404) + with pytest.raises(ValueError): + await middleware.async_process(req=req, resp=resp, next=next) diff --git a/tests/slack_bolt_async/oauth/test_async_oauth_flow.py b/tests/slack_bolt_async/oauth/test_async_oauth_flow.py index 1a7b89552..5714e1a6a 100644 --- a/tests/slack_bolt_async/oauth/test_async_oauth_flow.py +++ b/tests/slack_bolt_async/oauth/test_async_oauth_flow.py @@ -3,7 +3,7 @@ from urllib.parse import quote import pytest -from tests.utils import get_event_loop +from tests.utils import remove_os_env_temporarily, restore_os_env from slack_sdk.oauth.installation_store import FileInstallationStore from slack_sdk.oauth.state_store import FileOAuthStateStore from slack_sdk.oauth.state_store.async_state_store import AsyncOAuthStateStore @@ -30,15 +30,17 @@ class TestAsyncOAuthFlow: - mock_api_server_base_url = "http://localhost:8888" - @pytest.fixture - def event_loop(self): + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): + old_os_env = remove_os_env_temporarily() setup_mock_web_api_server_async(self) - loop = get_event_loop() - yield loop - loop.close() - cleanup_mock_web_api_server_async(self) + try: + self.mock_api_server_base_url = "http://localhost:8888" + yield # run the test here + finally: + cleanup_mock_web_api_server_async(self) + restore_os_env(old_os_env) def next(self): pass diff --git a/tests/slack_bolt_async/oauth/test_async_oauth_flow_sqlite3.py b/tests/slack_bolt_async/oauth/test_async_oauth_flow_sqlite3.py index 238ce8873..00300929f 100644 --- a/tests/slack_bolt_async/oauth/test_async_oauth_flow_sqlite3.py +++ b/tests/slack_bolt_async/oauth/test_async_oauth_flow_sqlite3.py @@ -1,7 +1,6 @@ import pytest from slack_sdk.web.async_client import AsyncWebClient -from tests.utils import get_event_loop from slack_bolt import BoltResponse from slack_bolt.oauth.async_callback_options import ( AsyncFailureArgs, @@ -17,15 +16,15 @@ class TestAsyncOAuthFlowSQLite3: - mock_api_server_base_url = "http://localhost:8888" - @pytest.fixture - def event_loop(self): + @pytest.fixture(scope="function", autouse=True) + def setup_teardown(self): setup_mock_web_api_server_async(self) - loop = get_event_loop() - yield loop - loop.close() - cleanup_mock_web_api_server_async(self) + try: + self.mock_api_server_base_url = "http://localhost:8888" + yield # run the test here + finally: + cleanup_mock_web_api_server_async(self) def next(self): pass diff --git a/tests/utils.py b/tests/utils.py index eb9759c5d..e06d0f861 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -13,13 +13,3 @@ def remove_os_env_temporarily() -> dict: def restore_os_env(old_env: dict) -> None: os.environ.update(old_env) - - -def get_event_loop(): - try: - return asyncio.get_event_loop() - except RuntimeError as ex: - if "There is no current event loop in thread" in str(ex): - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - return loop