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/.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/contributing.md b/.github/contributing.md index 3b1a5378f..8d8d668a6 100644 --- a/.github/contributing.md +++ b/.github/contributing.md @@ -35,7 +35,7 @@ Issues labelled `good first contribution`. For your contribution to be accepted: -- [x] You must have signed the [Contributor License Agreement (CLA)](https://cla-assistant.io/slackapi/bolt-python). +- [x] You must have signed the [Contributor License Agreement (CLA)](https://cla.salesforce.com/sign-cla). - [x] The test suite must be complete and pass. - [x] The changes must be approved by code review. - [x] Commits should be atomic and messages must be descriptive. Related issues should be mentioned by Issue number. @@ -57,4 +57,4 @@ If the contribution doesn't meet the above criteria, you may fail our automated ## Maintainers -There are more details about processes and workflow in the [Maintainer's Guide](./maintainers_guide.md). \ No newline at end of file +There are more details about processes and workflow in the [Maintainer's Guide](./maintainers_guide.md). diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..774d13833 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,18 @@ +# Please see the documentation for all configuration options: +# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +version: 2 +updates: + - package-ecosystem: "pip" + directory: "/" + 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" diff --git a/.github/maintainers_guide.md b/.github/maintainers_guide.md index 6d3ab7ff1..f8edeeabd 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 Travis CI builds for testing with various major versions. https://github.com/slackapi/bolt-python/blob/main/.travis.yml +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: -``` -$ 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 -e ".[adapter]" -$ pip install -e ".[adapter_testing]" +$ pip install -r requirements/adapter.txt +$ pip install -r requirements/adapter_testing.txt # Set required env variables $ export SLACK_SIGNING_SECRET=*** @@ -94,112 +94,130 @@ $ ngrok http 3000 --subdomain {your-domain} #### Develop Locally If you want to test the package locally you can. + 1. Build the package locally - - Run - ```bash + - Run + ```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 +### Generate API reference documents -### Releasing - -#### Generate API documents - -```bash +```sh ./scripts/generate_api_docs.sh ``` +### Releasing + #### test.pypi.org deployment -##### $HOME/.pypirc +[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. -``` -[testpypi] -username: {your username} -password: {your password} -``` +You can deploy this project to TestPyPI using GitHub Actions. +To deploy using GitHub Actions: -#### Development Deployment +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 -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` +Alternatively, you can deploy from your local machine with: -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 +```sh +./scripts/deploy_to_test_pypi.sh +``` -3. (Slack Internal) Communicate the release internally +#### Development Deployment +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). + +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 #### Production Deployment -1. Create the commit for the release: - - Bump the version number in adherence to [Semantic Versioning](http://semver.org/) in `slack_bolt/version.py` - - 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-release` - - `git commit -m 'version 1.2.3'` - - `git push {your-fork} v1.2.3-release` - - Merge in release PR after getting an approval from at least one maintainer. - - Create a git tag for the release. For example `git tag v1.2.3`. - - 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` - - Create a GitHub release - https://github.com/slackapi/bolt-python/releases - - ```markdown - ## New Features - - ### Awesome Feature 1 - - Description here. - - ### Awesome Feature 2 - - Description here. - - ## Changes - - * #123 Make it better - thanks @SlackHQ - * #123 Fix something wrong - thanks @seratch - ``` - -3. (Slack Internal) Communicate the release internally - - Include a link to the GitHub release - -4. Make announcements - - #tools-bolt in community.slack.com - -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 @@ -228,6 +246,10 @@ with labels. An issue should have **one** of the following labels applied: `bug` Issues are closed when a resolution has been reached. If for any reason a closed issue seems relevant once again, reopening is great and better than creating a duplicate issue. +## Managing Documentation + +See the [`/docs/README.md`](../docs/README.md) file for documentation instructions. + ## Everything else When in doubt, find the other maintainers and ask. diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 6ae896f2b..4dcfd152a 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,6 +1,12 @@ -(Describe the goal of this PR. Mention any related Issue numbers) +## Summary -### Category (place an `x` in each of the `[ ]`) + + +### Testing + + + +### Category * [ ] `slack_bolt.App` and/or its core components * [ ] `slack_bolt.async_app.AsyncApp` and/or its core components @@ -8,7 +14,7 @@ * [ ] Document pages under `/docs` * [ ] Others -## Requirements (place an `x` in each `[ ]`) +## Requirements Please read the [Contributing guidelines](https://github.com/slackapi/bolt-python/blob/main/.github/contributing.md) and [Code of Conduct](https://slackhq.github.io/code-of-conduct) before creating this issue or pull request. By submitting, you are agreeing to those rules. 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..6d504ea83 --- /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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - name: Set up Python ${{ env.LATEST_SUPPORTED_PY }} + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - name: Set up Python ${{ env.LATEST_SUPPORTED_PY }} + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: ${{ env.LATEST_SUPPORTED_PY }} + - name: Install synchronous dependencies + run: | + pip install -U pip + pip install -U . + pip install -r requirements/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.txt + pip install -r requirements/adapter.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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: ${{ matrix.python-version }} + - name: Install synchronous dependencies + run: | + pip install -U pip + pip install . + pip install -r requirements/testing_without_asyncio.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.txt + pip install -r requirements/adapter_testing.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.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/testing.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@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0 + with: + directory: ./reports/ + fail_ci_if_error: true + 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - name: Set up Python ${{ env.LATEST_SUPPORTED_PY }} + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: ${{ env.LATEST_SUPPORTED_PY }} + - 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@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.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@af78098f536edbc4de71162a307590698245be95 # v3.0.1 + 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 9fa9e3953..000000000 --- a/.github/workflows/codecov.yml +++ /dev/null @@ -1,42 +0,0 @@ -# TODO: This CI job hangs as of April 2023 -name: Run codecov - -on: - push: - branches: [main] - pull_request: - -jobs: - build: - runs-on: ubuntu-latest - timeout-minutes: 10 - strategy: - matrix: - python-version: ["3.11"] - env: - # default: multiprocessing - # threading is more stable on GitHub Actions - BOLT_PYTHON_MOCK_SERVER_MODE: threading - BOLT_PYTHON_CODECOV_RUNNING: "1" - steps: - - uses: actions/checkout@v3 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} -# - name: Install dependencies -# run: | -# python setup.py install -# pip install -U pip -# pip install -e ".[async]" -# pip install -e ".[adapter]" -# pip install -e ".[testing]" -# pip install -e ".[adapter_testing]" -# - name: Run all tests for codecov -# run: | -# pytest --cov=./slack_bolt/ --cov-report=xml -# - name: Upload coverage to Codecov -# uses: codecov/codecov-action@v3 -# with: -# fail_ci_if_error: true -# verbose: true diff --git a/.github/workflows/dependencies.yml b/.github/workflows/dependencies.yml new file mode 100644 index 000000000..9666057aa --- /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@ffa630c65fa7e0ecfa0625b5ceda64399aea1b36 # v3.0.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/flake8.yml b/.github/workflows/flake8.yml deleted file mode 100644 index f0eb9c5e0..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@v3 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - - name: Run flake8 verification - run: | - ./scripts/run_flake8.sh diff --git a/.github/workflows/pypi-release.yml b/.github/workflows/pypi-release.yml new file mode 100644 index 000000000..dfc224c83 --- /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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ github.event.release.tag_name || github.ref }} + persist-credentials: false + + - name: Set up Python + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: "3.x" + + - name: Build release distributions + run: | + scripts/build_pypi_package.sh + + - name: Persist dist folder + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + 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@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0 + 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@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0 diff --git a/.github/workflows/pytype.yml b/.github/workflows/pytype.yml deleted file mode 100644 index 5aaa5029a..000000000 --- a/.github/workflows/pytype.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: Run pytype 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@v3 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - - name: Run pytype verification - run: | - ./scripts/run_pytype.sh diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml deleted file mode 100644 index 11a38d743..000000000 --- a/.github/workflows/tests.yml +++ /dev/null @@ -1,84 +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"] - env: - # default: multiprocessing - # threading is more stable on GitHub Actions - BOLT_PYTHON_MOCK_SERVER_MODE: threading - steps: - - uses: actions/checkout@v3 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python setup.py install - pip install -U pip - pip install -e ".[testing_without_asyncio]" - - name: Run tests without aiohttp - run: | - pytest tests/slack_bolt/ - pytest tests/scenario_tests/ - - name: Run tests for Socket Mode adapters - run: | - pip install -e ".[adapter]" - pip install -e ".[adapter_testing]" - pytest tests/adapter_tests/socket_mode/ - - 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 3.x) - run: | - pytest tests/adapter_tests/falcon/ - - name: Run tests for HTTP Mode adapters (Falcon 2.x) - run: | - # Falcon 2.x does not support Python 3.11 or newer - # See also: https://github.com/slackapi/bolt-python/issues/757 - if [ ${{ matrix.python-version }} != "3.11" ]; then - pip install "falcon<3" - pytest tests/adapter_tests/falcon/ - fi - - 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: Run tests for HTTP Mode adapters (asyncio-based libraries) - run: | - pip install -e ".[async]" - # Falcon supports Python 3.11 since its v3.1.1 - pip install "falcon>=3.1.1,<4" - 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 cf6864ab3..c29bface2 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@v4.0.0 + - uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.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..892a858e7 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,246 @@ +# 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.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.txt` -- async runtime deps (`aiohttp`, `websockets`) +- `adapter.txt` -- all framework adapter deps (Flask, Django, FastAPI, etc.) +- `testing.txt` -- test runner deps (`pytest`, `pytest-asyncio`, includes `async.txt`) +- `testing_without_asyncio.txt` -- test deps without async (`pytest`, `pytest-cov`) +- `adapter_testing.txt` -- adapter-specific test deps (`moto`, `boddle`, `sanic-testing`) +- `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). + +## 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 b7751c094..39747df40 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,25 @@ -# Bolt ![Bolt logo](docs/assets/bolt-logo.svg) for Python - -[![Python Version][python-version]][pypi-url] -[![pypi package][pypi-image]][pypi-url] -[![Build Status][build-image]][build-url] -[![Codecov][codecov-image]][codecov-url] - -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. The Python module documents are available [here](https://slack.dev/bolt-python/api-docs/slack_bolt/). +

Bolt Bolt logo for Python

+ +

+ + PyPI - Version + + Codecov + + Pepy Total Downloads +
+ + Python Versions + + Documentation +

+ +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 @@ -51,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 @@ -82,33 +91,36 @@ 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 -# Listen for an event from the Events API -app.event(event_type)(fn) - -# Convenience method to listen to only `message` events using a string or re.Pattern -app.message([pattern ,])(fn) - # Listen for an action from a Block Kit element (buttons, select menus, date pickers, etc) app.action(action_id)(fn) # Listen for dialog submissions app.action({"callback_id": callbackId})(fn) -# Listen for a global or message shortcuts -app.shortcut(callback_id)(fn) - # Listen for slash commands app.command(command_name)(fn) -# Listen for view_submission modal events -app.view(callback_id)(fn) +# Listen for an event from the Events API +app.event(event_type)(fn) + +# Listen for a custom step execution from a workflow +app.function(callback_id)(fn) + +# Convenience method to listen to only `message` events using a string or re.Pattern +app.message([pattern ,])(fn) # Listen for options requests (from select menus with an external data source) app.options(action_id)(fn) + +# Listen for a global or message shortcuts +app.shortcut(callback_id)(fn) + +# Listen for view_submission modal events +app.view(callback_id)(fn) ``` The recommended way to use these methods are decorators: @@ -126,20 +138,22 @@ 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://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://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. ## Creating an async app 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 @@ -178,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://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://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: @@ -188,8 +202,6 @@ If you otherwise get stuck, we're here to help. The following are the best ways [pypi-image]: https://badge.fury.io/py/slack-bolt.svg [pypi-url]: https://pypi.org/project/slack-bolt/ -[build-image]: https://github.com/slackapi/bolt-python/workflows/CI%20Build/badge.svg -[build-url]: https://github.com/slackapi/bolt-python/actions?query=workflow%3A%22CI+Build%22 [codecov-image]: https://codecov.io/gh/slackapi/bolt-python/branch/main/graph/badge.svg [codecov-url]: https://codecov.io/gh/slackapi/bolt-python [python-version]: https://img.shields.io/pypi/pyversions/slack-bolt.svg diff --git a/docs/.gitignore b/docs/.gitignore deleted file mode 100644 index ee9abd8bf..000000000 --- a/docs/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -_site -Gemfile.lock -.env -.jekyll-metadata -.vscode/ -.bundle/ -vendor/ -.ruby-version diff --git a/docs/.markdownlint.yml b/docs/.markdownlint.yml deleted file mode 100644 index 4780b8f6b..000000000 --- a/docs/.markdownlint.yml +++ /dev/null @@ -1,198 +0,0 @@ -# Default state for all rules -default: true - -# Path to configuration file to extend -extends: null - -# MD001/heading-increment/header-increment - Heading levels should only increment by one level at a time -MD001: false - -# MD002/first-heading-h1/first-header-h1 - First heading should be a top-level heading -MD002: - # Heading level - level: 1 - -# MD003/heading-style/header-style - Heading style -MD003: - # Heading style - style: "consistent" - -# MD004/ul-style - Unordered list style -MD004: - # List style - style: "consistent" - -# MD005/list-indent - Inconsistent indentation for list items at the same level -MD005: true - -# MD006/ul-start-left - Consider starting bulleted lists at the beginning of the line -MD006: true - -# MD007/ul-indent - Unordered list indentation -MD007: false - -# MD009/no-trailing-spaces - Trailing spaces -MD009: - # Spaces for line break - br_spaces: 2 - # Allow spaces for empty lines in list items - list_item_empty_lines: false - # Include unnecessary breaks - strict: false - -# MD010/no-hard-tabs - Hard tabs -MD010: - # Include code blocks - code_blocks: true - # Fenced code languages to ignore - ignore_code_languages: [] - # Number of spaces for each hard tab - spaces_per_tab: 1 - -# MD011/no-reversed-links - Reversed link syntax -MD011: true - -# MD012/no-multiple-blanks - Multiple consecutive blank lines -MD012: - # Consecutive blank lines - maximum: 1 - -# MD013/line-length - Line length -MD013: false - -# MD014/commands-show-output - Dollar signs used before commands without showing output -MD014: true - -# MD018/no-missing-space-atx - No space after hash on atx style heading -MD018: true - -# MD019/no-multiple-space-atx - Multiple spaces after hash on atx style heading -MD019: true - -# MD020/no-missing-space-closed-atx - No space inside hashes on closed atx style heading -MD020: true - -# MD021/no-multiple-space-closed-atx - Multiple spaces inside hashes on closed atx style heading -MD021: true - -# MD022/blanks-around-headings/blanks-around-headers - Headings should be surrounded by blank lines -MD022: false - -# MD023/heading-start-left/header-start-left - Headings must start at the beginning of the line -MD023: true - -# MD024/no-duplicate-heading/no-duplicate-header - Multiple headings with the same content -MD024: false - -# MD025/single-title/single-h1 - Multiple top-level headings in the same document -MD025: false - -# MD026/no-trailing-punctuation - Trailing punctuation in heading -MD026: - # Punctuation characters - punctuation: ".,;:!ใ€‚๏ผŒ๏ผ›๏ผš๏ผ" - -# MD027/no-multiple-space-blockquote - Multiple spaces after blockquote symbol -MD027: true - -# MD028/no-blanks-blockquote - Blank line inside blockquote -MD028: true - -# MD029/ol-prefix - Ordered list item prefix -MD029: false - -# MD030/list-marker-space - Spaces after list markers -MD030: false - -# MD031/blanks-around-fences - Fenced code blocks should be surrounded by blank lines -MD031: - # Include list items - list_items: true - -# MD032/blanks-around-lists - Lists should be surrounded by blank lines -MD032: true - -# MD033/no-inline-html - Inline HTML -MD033: false - -# MD034/no-bare-urls - Bare URL used -MD034: true - -# MD035/hr-style - Horizontal rule style -MD035: - # Horizontal rule style - style: "consistent" - -# MD036/no-emphasis-as-heading/no-emphasis-as-header - Emphasis used instead of a heading -MD036: false - # Punctuation characters - # punctuation: ".,;:!?ใ€‚๏ผŒ๏ผ›๏ผš๏ผ๏ผŸ" - -# MD037/no-space-in-emphasis - Spaces inside emphasis markers -MD037: true - -# MD038/no-space-in-code - Spaces inside code span elements -MD038: true - -# MD039/no-space-in-links - Spaces inside link text -MD039: true - -# MD040/fenced-code-language - Fenced code blocks should have a language specified -MD040: false - -# MD041/first-line-heading/first-line-h1 - First line in a file should be a top-level heading -MD041: - # Heading level - level: 1 - # RegExp for matching title in front matter - front_matter_title: "^\\s*title\\s*[:=]" - -# MD042/no-empty-links - No empty links -MD042: true - -# MD043/required-headings/required-headers - Required heading structure -MD043: false - -# MD044/proper-names - Proper names should have the correct capitalization -MD044: - # List of proper names - names: [] - # Include code blocks - code_blocks: true - # Include HTML elements - html_elements: true - -# MD045/no-alt-text - Images should have alternate text (alt text) -MD045: true - -# MD046/code-block-style - Code block style -MD046: - # Block style - style: "consistent" - -# MD047/single-trailing-newline - Files should end with a single newline character -MD047: true - -# MD048/code-fence-style - Code fence style -MD048: - # Code fence style - style: "consistent" - -# MD049/emphasis-style - Emphasis style should be consistent -MD049: - # Emphasis style should be consistent - style: "consistent" - -# MD050/strong-style - Strong style should be consistent -MD050: - # Strong style should be consistent - style: "consistent" - -# MD051/link-fragments - Link fragments should be valid -MD051: false - -# MD052/reference-links-images - Reference links and images should use a label that is defined -MD052: true - -# MD053/link-image-reference-definitions - Link and image reference definitions should be needed -MD053: true diff --git a/docs/Gemfile b/docs/Gemfile deleted file mode 100644 index 180806c7a..000000000 --- a/docs/Gemfile +++ /dev/null @@ -1,4 +0,0 @@ -source 'https://rubygems.org' -gem 'github-pages', group: :jekyll_plugins -gem 'dotenv' -gem 'webrick' diff --git a/docs/_basic/ja_listening_actions.md b/docs/_basic/ja_listening_actions.md deleted file mode 100644 index b468fdd42..000000000 --- a/docs/_basic/ja_listening_actions.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -title: ใ‚ขใ‚ฏใ‚ทใƒงใƒณใฎใƒชใ‚นใƒ‹ใƒณใ‚ฐ -lang: ja-jp -slug: action-listening -order: 5 ---- - -
-Bolt ใ‚ขใƒ—ใƒชใฏ `action` ใƒกใ‚ฝใƒƒใƒ‰ใ‚’็”จใ„ใฆใ€ใƒœใ‚ฟใƒณใฎใ‚ฏใƒชใƒƒใ‚ฏใ€ใƒกใƒ‹ใƒฅใƒผใฎ้ธๆŠžใ€ใƒกใƒƒใ‚ปใƒผใ‚ธใ‚ทใƒงใƒผใƒˆใ‚ซใƒƒใƒˆใชใฉใฎใƒฆใƒผใ‚ถใƒผใฎใ‚ขใ‚ฏใ‚ทใƒงใƒณใ‚’ใƒชใƒƒใ‚นใƒณใ™ใ‚‹ใ“ใจใŒใงใใพใ™ใ€‚ - -ใ‚ขใ‚ฏใ‚ทใƒงใƒณใฏ `str` ๅž‹ใพใŸใฏ `re.Pattern` ๅž‹ใฎ `action_id` ใงใƒ•ใ‚ฃใƒซใ‚ฟใƒชใƒณใ‚ฐใงใใพใ™ใ€‚`action_id` ใฏใ€Slack ใƒ—ใƒฉใƒƒใƒˆใƒ•ใ‚ฉใƒผใƒ ไธŠใฎใ‚คใƒณใ‚ฟใƒฉใ‚ฏใƒ†ใ‚ฃใƒ–ใ‚ณใƒณใƒใƒผใƒใƒณใƒˆใ‚’ๅŒบๅˆฅใ™ใ‚‹ไธ€ๆ„ใฎ่ญ˜ๅˆฅๅญใจใ—ใฆๆฉŸ่ƒฝใ—ใพใ™ใ€‚ - -`action()` ใ‚’ไฝฟใฃใŸใ™ในใฆใฎไพ‹ใง `ack()` ใŒไฝฟ็”จใ•ใ‚Œใฆใ„ใ‚‹ใ“ใจใซๆณจ็›ฎใ—ใฆใใ ใ•ใ„ใ€‚ใ‚ขใ‚ฏใ‚ทใƒงใƒณใฎใƒชใ‚นใƒŠใƒผๅ†…ใงใฏใ€Slack ใ‹ใ‚‰ใฎใƒชใ‚ฏใ‚จใ‚นใƒˆใ‚’ๅ—ไฟกใ—ใŸใ“ใจใ‚’็ขบ่ชใ™ใ‚‹ใŸใ‚ใซใ€`ack()` ้–ขๆ•ฐใ‚’ๅ‘ผใณๅ‡บใ™ๅฟ…่ฆใŒใ‚ใ‚Šใพใ™ใ€‚ใ“ใ‚Œใซใคใ„ใฆใฏใ€[ใƒชใ‚ฏใ‚จใ‚นใƒˆใฎ็ขบ่ช](#acknowledge)ใ‚ปใ‚ฏใ‚ทใƒงใƒณใง่ชฌๆ˜Žใ—ใฆใ„ใพใ™ใ€‚ - -
- -
-ๆŒ‡ๅฎšๅฏ่ƒฝใชๅผ•ๆ•ฐใฎไธ€่ฆงใฏใƒขใ‚ธใƒฅใƒผใƒซใƒ‰ใ‚ญใƒฅใƒกใƒณใƒˆใ‚’ๅ‚่€ƒใซใ—ใฆใใ ใ•ใ„ใ€‚ -```python -# 'approve_button' ใจใ„ใ† action_id ใฎใƒ–ใƒญใƒƒใ‚ฏใ‚จใƒฌใƒกใƒณใƒˆใŒใƒˆใƒชใ‚ฌใƒผใ•ใ‚Œใ‚‹ใŸใณใซใ€ใ“ใฎใƒชใ‚นใƒŠใƒผใŒๅ‘ผใณๅ‡บใ•ใ›ใ‚Œใ‚‹ -@app.action("approve_button") -def update_message(ack): - ack() - # ใ‚ขใ‚ฏใ‚ทใƒงใƒณใธใฎๅๅฟœใจใ—ใฆใƒกใƒƒใ‚ปใƒผใ‚ธใ‚’ๆ›ดๆ–ฐ -``` -
- -
- -

ๅˆถ็ด„ไป˜ใใ‚ชใƒ–ใ‚ธใ‚งใ‚ฏใƒˆใ‚’ไฝฟ็”จใ—ใŸใ‚ขใ‚ฏใ‚ทใƒงใƒณใฎใƒชใ‚นใƒ‹ใƒณใ‚ฐ

-
- -
- -ๅˆถ็ด„ไป˜ใใฎใ‚ชใƒ–ใ‚ธใ‚งใ‚ฏใƒˆใ‚’ไฝฟ็”จใ™ใ‚‹ใจใ€`callback_id`ใ€`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"], - ) -``` - -
diff --git a/docs/_basic/ja_listening_events.md b/docs/_basic/ja_listening_events.md deleted file mode 100644 index 0954c2ce7..000000000 --- a/docs/_basic/ja_listening_events.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -title: ใ‚คใƒ™ใƒณใƒˆใฎใƒชใ‚นใƒ‹ใƒณใ‚ฐ -lang: ja-jp -slug: event-listening -order: 3 ---- - -
- -`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}") -``` -
diff --git a/docs/_basic/ja_listening_messages.md b/docs/_basic/ja_listening_messages.md deleted file mode 100644 index f27fbec9c..000000000 --- a/docs/_basic/ja_listening_messages.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -title: ใƒกใƒƒใ‚ปใƒผใ‚ธใฎใƒชใ‚นใƒ‹ใƒณใ‚ฐ -lang: ja-jp -slug: message-listening -order: 1 ---- - -
- -[ใ‚ใชใŸใฎใ‚ขใƒ—ใƒชใŒใ‚ขใ‚ฏใ‚ปใ‚นๆจฉ้™ใ‚’ๆŒใค](https://api.slack.com/messaging/retrieving#permissions)ใƒกใƒƒใ‚ปใƒผใ‚ธใฎๆŠ•็จฟใ‚คใƒ™ใƒณใƒˆใ‚’ใƒชใƒƒใ‚นใƒณใ™ใ‚‹ใซใฏ `message()` ใƒกใ‚ฝใƒƒใƒ‰ใ‚’ๅˆฉ็”จใ—ใพใ™ใ€‚ใ“ใฎใƒกใ‚ฝใƒƒใƒ‰ใฏ `type` ใŒ `message` ใงใฏใชใ„ใ‚คใƒ™ใƒณใƒˆใ‚’ๅ‡ฆ็†ๅฏพ่ฑกใ‹ใ‚‰้™คๅค–ใ—ใพใ™ใ€‚ - -`message()` ใฎๅผ•ๆ•ฐใซใฏ `str` ๅž‹ใพใŸใฏ `re.Pattern` ใ‚ชใƒ–ใ‚ธใ‚งใ‚ฏใƒˆใ‚’ๆŒ‡ๅฎšใงใใพใ™ใ€‚ใ“ใฎๆกไปถใฎใƒ‘ใ‚ฟใƒผใƒณใซไธ€่‡ดใ—ใชใ„ใƒกใƒƒใ‚ปใƒผใ‚ธใฏ้™คๅค–ใ•ใ‚Œใพใ™ใ€‚ -
- -
-ๆŒ‡ๅฎšๅฏ่ƒฝใชๅผ•ๆ•ฐใฎไธ€่ฆงใฏใƒขใ‚ธใƒฅใƒผใƒซใƒ‰ใ‚ญใƒฅใƒกใƒณใƒˆใ‚’ๅ‚่€ƒใซใ—ใฆใใ ใ•ใ„ใ€‚ -```python -# '๐Ÿ‘‹' ใŒๅซใพใ‚Œใ‚‹ใ™ในใฆใฎใƒกใƒƒใ‚ปใƒผใ‚ธใซไธ€่‡ด -@app.message(":wave:") -def say_hello(message, say): - user = message['user'] - say(f"Hi there, <@{user}>!") -``` -
- -
- -

ๆญฃ่ฆ่กจ็พใƒ‘ใ‚ฟใƒผใƒณใฎๅˆฉ็”จ

-
- -
- -ๆ–‡ๅญ—ๅˆ—ใฎไปฃใ‚ใ‚Šใซ `re.compile()` ใƒกใ‚ฝใƒƒใƒ‰ใ‚’ไฝฟ็”จใ™ใ‚Œใฐใ€ใ‚ˆใ‚Š็ดฐใ‚„ใ‹ใชๆกไปถๆŒ‡ๅฎšใŒใงใใพใ™ใ€‚ - -
- -```python -import re - -@app.message(re.compile("(hi|hello|hey)")) -def say_hello_regex(say, context): - # ๆญฃ่ฆ่กจ็พใฎใƒžใƒƒใƒ็ตๆžœใฏ context.matches ใซ่จญๅฎšใ•ใ‚Œใ‚‹ - greeting = context['matches'][0] - say(f"{greeting}, how are you?") -``` - -
diff --git a/docs/_basic/ja_publishing_views.md b/docs/_basic/ja_publishing_views.md deleted file mode 100644 index 280643482..000000000 --- a/docs/_basic/ja_publishing_views.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -title: ใƒ›ใƒผใƒ ใ‚ฟใƒ–ใฎๆ›ดๆ–ฐ -lang: ja-jp -slug: app-home -order: 13 ---- - -
-ใƒ›ใƒผใƒ ใ‚ฟใƒ–ใฏใ€ใ‚ตใ‚คใƒ‰ใƒใƒผใ‚„ๆคœ็ดข็”ป้ขใ‹ใ‚‰ใ‚ขใ‚ฏใ‚ปใ‚นๅฏ่ƒฝใชใ‚ตใƒผใƒ•ใ‚งใ‚นใ‚จใƒชใ‚ขใงใ™ใ€‚ใ‚ขใƒ—ใƒชใฏใ“ใฎใ‚จใƒชใ‚ขใ‚’ไฝฟใฃใฆใƒฆใƒผใ‚ถใƒผใ”ใจใฎใƒ“ใƒฅใƒผใ‚’่กจ็คบใ™ใ‚‹ใ“ใจใŒใงใใพใ™ใ€‚ใ‚ขใƒ—ใƒช่จญๅฎšใƒšใƒผใ‚ธใง 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/_basic/ja_responding_actions.md b/docs/_basic/ja_responding_actions.md deleted file mode 100644 index dd974182c..000000000 --- a/docs/_basic/ja_responding_actions.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -title: ใ‚ขใ‚ฏใ‚ทใƒงใƒณใธใฎๅฟœ็ญ” -lang: ja-jp -slug: action-respond -order: 6 ---- - -
- -ใ‚ขใ‚ฏใ‚ทใƒงใƒณใธใฎๅฟœ็ญ”ใซใฏใ€ไธปใซ 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']}>") -``` - -
diff --git a/docs/_basic/ja_web_api.md b/docs/_basic/ja_web_api.md deleted file mode 100644 index 7c5ab21fc..000000000 --- a/docs/_basic/ja_web_api.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -title: Web API ใฎไฝฟใ„ๆ–น -lang: ja-jp -slug: web-api -order: 4 ---- - -
-`app.client`ใ€ใพใŸใฏใƒŸใƒ‰ใƒซใ‚ฆใ‚งใ‚ขใƒปใƒชใ‚นใƒŠใƒผใฎๅผ•ๆ•ฐ `client` ใจใ—ใฆ Bolt ใ‚ขใƒ—ใƒชใซๆไพ›ใ•ใ‚Œใฆใ„ใ‚‹ [`WebClient`](https://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/_basic/listening_actions.md b/docs/_basic/listening_actions.md deleted file mode 100644 index 23312a8e3..000000000 --- a/docs/_basic/listening_actions.md +++ /dev/null @@ -1,56 +0,0 @@ ---- -title: Listening to actions -lang: en -slug: action-listening -order: 5 ---- - -
-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](#acknowledge). - -
- -
-Refer to the module document 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 `callback_id`s, `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"], - ) -``` - -
diff --git a/docs/_basic/listening_messages.md b/docs/_basic/listening_messages.md deleted file mode 100644 index f48aa5e96..000000000 --- a/docs/_basic/listening_messages.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -title: Listening to messages -lang: en -slug: message-listening -order: 1 ---- - -
- -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`. - -`message()` accepts an argument of type `str` or `re.Pattern` object that filters out any messages that don't match the pattern. - -
- -
-Refer to the module document to learn the available listener arguments. -```python -# This will match any message that contains ๐Ÿ‘‹ -@app.message(":wave:") -def say_hello(message, say): - user = message['user'] - say(f"Hi there, <@{user}>!") -``` -
- -
- -

Using a regular expression pattern

-
- -
- -The `re.compile()` method can be used instead of a string for more granular matching. - -
- -```python -import re - -@app.message(re.compile("(hi|hello|hey)")) -def say_hello_regex(say, context): - # regular expression matches are inside of context.matches - greeting = context['matches'][0] - say(f"{greeting}, how are you?") -``` - -
diff --git a/docs/_basic/responding_actions.md b/docs/_basic/responding_actions.md deleted file mode 100644 index 30219a098..000000000 --- a/docs/_basic/responding_actions.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -title: Responding to actions -lang: en -slug: action-respond -order: 6 ---- - -
- -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 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']}>") -``` - -
diff --git a/docs/_basic/sending_messages.md b/docs/_basic/sending_messages.md deleted file mode 100644 index adc5a5526..000000000 --- a/docs/_basic/sending_messages.md +++ /dev/null @@ -1,60 +0,0 @@ ---- -title: Sending messages -lang: en -slug: message-sending -order: 2 ---- - -
- -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](#web-api). - -
- -
-Refer to the module document 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" - ) -``` - -
diff --git a/docs/_basic/web_api.md b/docs/_basic/web_api.md deleted file mode 100644 index 4e7c6e3c8..000000000 --- a/docs/_basic/web_api.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -title: Using the Web API -lang: en -slug: web-api -order: 4 ---- - -
-You can call [any Web API method](https://api.slack.com/methods) using the [`WebClient`](https://slack.dev/python-slack-sdk/basic_usage.html) 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. - -
- -
-Refer to the module document 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" - ) -``` -
\ No newline at end of file diff --git a/docs/_config.yml b/docs/_config.yml deleted file mode 100644 index 1ed696b75..000000000 --- a/docs/_config.yml +++ /dev/null @@ -1,72 +0,0 @@ -# For technical reasons, this file is *NOT* reloaded automatically when you use -# 'bundle exec jekyll serve'. If you change this file, please restart the server process. -title: Bolt -description: >- - A framework that makes Slack app development fast and straight-forward. - With a single interface for Slackโ€™s Web API, Events API, and interactive features, - Bolt gives you the full power of the Slack platform out of the box. -baseurl: /bolt-python -url: https://slack.dev - -collections: - basic: - output: false - steps: - output: false - advanced: - output: false - tutorials: - output: true - permalink: /tutorials/:slug - future: - output: true - permalink: /future/:slug - -defaults: - - scope: - path: "" - values: - layout: "default" - -# Translation strings used in templates - they are typically used using t[page.lang] -# so it's important to have corresponding strings for each translated language -t: - en: - basic: Basic concepts - steps: Workflow steps - advanced: Advanced concepts - start: Getting started - contribute: Contributing - beta: BETA - legacy: LEGACY - ja-jp: - basic: ๅŸบๆœฌ็š„ใชๆฆ‚ๅฟต - steps: ใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผใ‚นใƒ†ใƒƒใƒ— - advanced: ๅฟœ็”จใ‚ณใƒณใ‚ปใƒ—ใƒˆ - start: Bolt ๅ…ฅ้–€ใ‚ฌใ‚คใƒ‰ - contribute: ่ฒข็Œฎ - beta: BETA - legacy: LEGACY - -# Metadata -repo_name: bolt-python -github_username: SlackAPI - -code_of_conduct_url: https://slackhq.github.io/code-of-conduct -cla_url: https://cla-assistant.io/slackapi/bolt-python - -google_analytics: UA-56978219-13 -google_tag_manager: GTM-KFZ5MK7 - -# Build settings -markdown: kramdown -kramdown: - parse_block_html: true - syntax_highlighter_opts: - block: - line_numbers: true -plugins: - - jemoji - - jekyll-redirect-from - -repository: slackapi/bolt-python diff --git a/docs/_future/concepts.md b/docs/_future/concepts.md deleted file mode 100644 index 74c83aed1..000000000 --- a/docs/_future/concepts.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -title: Slack developer beta -lang: en -slug: concepts -order: 0 -layout: future -permalink: /future/concepts ---- -# Slack developer beta concepts BETA - -
- -This page contains all the concepts that are necessary to allow you to use the next-gen Slack features in Python. - -

- -Our next-generation platform is currently in beta. Your feedback is most welcome - all feedback will help shape the future platform experience! -

-
diff --git a/docs/_future/deploy_your_app.md b/docs/_future/deploy_your_app.md deleted file mode 100644 index ed114a163..000000000 --- a/docs/_future/deploy_your_app.md +++ /dev/null @@ -1,16 +0,0 @@ ---- - -title: Deploy your app -lang: en -slug: deploy-your-app -order: 6 -layout: tutorial -permalink: /tutorial/deploy-your-app ---- -# Deploy your app BETA - -
-Instructions for deploying your next-generation Bolt Python application to third-party infrastructure are coming soon! Stay tuned. - -

Our next-generation platform is currently in beta. Your feedback is most welcome - all feedback will help shape the future platform experience!

-
diff --git a/docs/_future/getting_started_future.md b/docs/_future/getting_started_future.md deleted file mode 100644 index d33658314..000000000 --- a/docs/_future/getting_started_future.md +++ /dev/null @@ -1,145 +0,0 @@ ---- -title: Getting started -order: 1 -slug: getting-started-future -lang: en -layout: tutorial -permalink: /tutorial/getting-started-future ---- -## Getting started BETA - -
-This guide will cover how to get started with your next-gen platform using Bolt for Python, by setting up the Slack CLI and installing the required dependencies. - -Find out about the next-generation platform on Slack's official introduction page. -
- ---- - -### Limitations - -Bolt for Python supports app development using next-gen platform features like Functions, Workflows and tools such as the Slack CLI alongside all current generally available Slack Platform features. - -#### We do not yet support - -- Deployment to secure and managed Slack infrastructure. -- Datastores API Datastores functionality. - -> ๐Ÿ’ก If you'd like to deploy your app with Slack infrastructure, consider building your next-generation application with the Deno Slack API. You can get started with that here. - ---- - -### Setting up {#setting-up} - -#### Slack CLI {#setting-up-cli} - -To build a next-generation app with Bolt for Python, you'll need to get the Slack CLI. - -Install the Slack CLI by following this Quickstart. Since we won't be using Deno to build our next-generation app, you can skip any instructions related to installing Deno or creating an app using a Deno template. Once you've logged into the CLI using `slack login` and verified your login using `slack auth list`, you can proceed with the instructions in this guide. - -#### Dependencies {#setting-up-dependencies} - -Once the CLI is set up, make sure your machine has the most recent version of Python installed. You can install Python through a package manager (such as Homebrew for macOS) or directly from the website. - ---- - -### Create a new app {#create-app} - -Before you start developing with Bolt, you'll want to create a Slack app. - -To create the app, you'll run the following command: - -```bash -slack create my-app -t slack-samples/bolt-python-starter-template -b future -``` - -This command creates an app through the CLI by cloning a specified template. In this case, the template is the Bolt for Python Starter Template on the `future` branch. This starter template includes a "Hello World" example that demonstrates how to use built-in and custom Functions, Triggers and Workflows. - -Once the app is successfully created, you should see a message like this: - -```text -โœจ my-app successfully created - -๐Ÿงญ Explore your project's README.md for documentation and code samples, and at any time run slack help to display a list of available commands - -๐Ÿง‘โ€๐Ÿš€ Follow the steps below to try out your new project - -1๏ธโƒฃ Change into your project directory with: cd my-app - -2๏ธโƒฃ Develop locally and see changes in real-time with: slack run - -3๏ธโƒฃ When you're ready to deploy for production use: slack deploy - -๐Ÿ”” If you leave the workspace, you wonโ€™t be able to manage any apps youโ€™ve deployed to it. Apps you deploy will belong to the workspace even if you leave the workspace -``` - ---- - -### Set up your trigger {#setup-trigger} - -As mentioned, this app comes with pre-existing functionality - it uses Functions, Workflows and a Link Trigger that will allow users in Slack to initiate the functionality provided by the app. Let's run a command to initialize that Link Trigger via the CLI. - -First, make sure you're in the project directory in your command line: `cd my-app` - -Then, run the following command to create a Trigger: - -```bash -slack triggers create --trigger-def "triggers/sample-trigger.json" -``` - -The above command will create a Link Trigger for the selected workspace. Make sure to select the workspace you want. Once the trigger is successfully created, you should see an output like this: - -```bash -โšก Trigger created - Trigger ID: [ID] - Trigger Type: shortcut - Trigger Name: Sample Trigger - URL: https://slack.com/shortcuts/[ID]/[Some ID] -``` - -The provided URL can be pasted into Slack; Slack will unfurl it into a button that users can interact with to initiate your app's functionality! Copy this URL and save it somewhere; you'll need it for later. - ---- - -### Run your app {#run-your-app} - -Now that your app and Trigger are successfully created, let's try running it! - -```bash -# install the required project dependencies -pip install -r requirements.txt - -# start a local development server -slack run -``` - -Executing `pip install -r requirements.txt` installs all the project requirements to your machine. - -Executing `slack run` starts a local development server, syncing changes to your workspace's development version of your app. - -You'll be prompted to select a workspace to install the app to—select the development instance of your workspace (you'll know it's the development version because the name has the string `(dev)` appended). - -> ๐Ÿ’ก If you don't see the workspace you'd like to use in the list, you can `CTRL + C` out of the `slack run` command and run `slack auth login`. This will allow you to authenticate in your desired workspace to have it show up in the list for `slack run`. - -You'll see an output in your Terminal to indicate your app is running, similar to what you would see with any other Bolt for Python app. You can search for the `โšก๏ธ Bolt app is running! โšก๏ธ` message to make sure that your app has successfully started up. - -### Trigger your app's workflow {#trigger-workflow} - -With your app running, access your workspace and paste the URL from the Trigger you created in the [previous step](/bolt-python/tutorial/getting-started-future#setup-trigger) into a message in a public channel. - -> ๐Ÿ’ก App Triggers are automatically saved as a channel bookmark under "Workflows" for easy access. - -Send the message and click the "Run" button that appears. A modal will appear prompting you to enter information to greet someone in your Slack workspace. Fill out the requested information. - -![Hello World modal](https://slack.dev/bolt-js/assets/hello-world-modal.png "Hello World modal") - -Then, submit the form. In the specified channel submitted in the form, you should receive a message from the app tagging the submitted user. The message will also contain a randomly generated greeting and the message you wrote in the form. - -The full app flow can be seen here: -![Hello World app](https://slack.dev/bolt-js/assets/hello-world-demo.gif "Hello World app") - ---- - -### Next steps {#next-steps} - -Now we have a working instance of a next-generation app in your workspace and you've seen it in action! You can explore on your own and dive into the code yourself here or continue your learning journey by diving into [App Manifests](/bolt-python/future/concepts#manifests) or looking into adding more [Functions](/bolt-python/future/concepts#functions), [Workflows](/bolt-python/future/concepts#manifest-workflows), and [Triggers](#setup-trigger) to your app! diff --git a/docs/_future/listening_responding_functions.md b/docs/_future/listening_responding_functions.md deleted file mode 100644 index b7c13c692..000000000 --- a/docs/_future/listening_responding_functions.md +++ /dev/null @@ -1,105 +0,0 @@ ---- -title: Listening & responding to functions -lang: en -slug: functions -order: 5 -layout: future ---- - -
- -Your app can use the `function()` method to listen to incoming function requests. The method requires a function `callback_id` of type `str`. This `callback_id` must also be defined in your [Function](/bolt-python/future/concepts#manifest-functions) definition. Functions must eventually be completed with the `complete()` function to inform Slack that your app has processed the function request. `complete()` requires **one of two** keyword arguments: `outputs` or `error`. There are two ways to complete a Function with `complete()`: - -* `outputs` of type `dict` completes your function **successfully** and provides a dictionary containing the outputs of your function as defined in the app's manifest. -* `error` of type `str` completes your function **unsuccessfully** and provides a message containing information regarding why your function was not successful. - -
- -
-Refer to the module document to learn the available listener arguments. -```python -# The sample function simply outputs an input -@app.function("sample_function") -def sample_func(event: dict, complete: Complete): - try: - message = event["inputs"]["message"] - complete( - outputs={ - "updatedMsg": f":wave: You submitted the following message: \n\n>{message}" - } - ) - except Exception as e: - complete(error=f"Cannot submit the message: {e}") - raise e -``` -
- -
- -

Function Interactivity

-
- -
- -The `function()` method returns a `SlackFunction` decorator object. This object can be used by your app to set up interactive listeners such as [actions](/bolt-python/concepts#action-respond) and [views](/bolt-python/concepts#view_submissions). These listeners listen to events created during the handling of your `function` event. Additionally, they will only be called when a user interacts with a block element that has the following attributes: - -* It was created during the handling of a `function` event. -* The `action_id` matches the interactive listeners `action_id`. - -These listeners behave similarly to the ones assigned directly to your app. The notable difference is that `complete()` must be called once your function is completed. - -
- -```python -# Your listener will be called when your function "sample_function" is triggered from a workflow -# When triggered a message containing a button with an action_id "approve_button" is posted -@app.function("sample_function") -def sample_func(event: dict, complete: Complete): - try: - client.chat_postMessage( - channel="a-channel-id", - text="A new button appears", - blocks=[ - { - "type": "actions", - "block_id": "approve-button", - "elements": [ - { - "type": "button", - "text": { - "type": "plain_text", - "text": "Click", - }, - "action_id": "sample_action", - "style": "primary", - }, - ], - }, - ], - ) - except Exception as e: - complete(error=f"Cannot post the message: {e}") - raise e - -# Your listener will be called when a block element -# - Created by your "sample_func" -# - With the action_id "sample_action" -# is triggered -@sample_func.action("sample_action") -def update_message(ack, body, client, complete): - try: - 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"], - ) - complete() - except Exception as e: - logger.error(e) - complete(error=f"Cannot react to message: {e}") - raise e -``` - -
diff --git a/docs/_future/manifest.md b/docs/_future/manifest.md deleted file mode 100644 index 26b77f28a..000000000 --- a/docs/_future/manifest.md +++ /dev/null @@ -1,181 +0,0 @@ ---- -title: Manifest -lang: en -slug: manifest -order: 2 -layout: future ---- - - -
- -Your project should contain a `manifest.json` file that defines your app's manifest. This is where you'll configure your application name and scopes, declare the functions your app will use, and more. -Refer to the App Manifest and Manifest Property documentation to learn -about the available manifest configurations. - -Notably, the App Manifest informs Slack of the definitions for: - -* [Functions](/bolt-python/future/concepts#manifest-functions) -* [Workflows](/bolt-python/future/concepts#manifest-workflows) - -`manifest.json` is located at the top level of our example projects, along with a `triggers` folder containing -`*_trigger.json` files that define the triggers -for your app. - -
-.
-โ”œโ”€โ”€ ...
-โ”œโ”€โ”€ manifest.json             # app manifest definition
-โ”œโ”€โ”€ triggers                  # folder with trigger files
-โ”‚   โ”œโ”€โ”€ sample_trigger.json   # trigger definition
-โ”‚   โ””โ”€โ”€ ...
-โ””โ”€โ”€ ...
-
- -
- -#### Linting, prediction & validation - -Syntax and formatting checks are required to efficiently edit your `manifest.json`. Multiple IDEs are able to support -this, namely: Visual Studio Code, Pycharm, Sublime Text (via LSP-json) and many more. - -To get **manifest prediction & validation** in your IDE, include the following line in your `manifest.json` file: - -```json -{ - "$schema": "https://raw.githubusercontent.com/slackapi/manifest-schema/main/manifest.schema.json", - ... -} -``` - -Using the Slack CLI you can validate your `manifest.json` against the Slack API with: - -```bash -slack manifest validate -``` - -
- -
-Refer to our template project to view a full version of manifest.json. -```json -{ - "$schema": "https://raw.githubusercontent.com/slackapi/manifest-schema/main/manifest.schema.json", - "_metadata": { - "major_version": 2, - }, - "display_information": { - "name": "Bolt Template App TEST" - }, - "features": { - "app_home": { - "home_tab_enabled": false, - }, - "bot_user": { - "display_name": "Bolt Template App TEST", - "always_online": false - } - }, - "oauth_config": { - "scopes": { - "bot": [ - "chat:write", - ] - } - }, - "settings": { - "socket_mode_enabled": true, - }, - "functions": {}, - "types": {}, - "workflows": {}, - "outgoing_domains": [] -} -``` -
- -
- - -

Common Manifest Types

-
- -
-
- - - - - - - - - - - - - - - - - -
parameters
object
propertiespropertiesdefines the properties
requiredlist[string]defines the properties required by the function
- - - - - - - - - - - - - - - - - -
properties
dictionary
keystringdefines the property name
valuepropertydefines the property
- - - - - - - - - - - - - - - - - -
property
object
typestringdefines the property type
descriptionstringdefines the property description
-
- -```json -"$comment": "sample parameters object" -"*_parameters":{ - "properties": { - "property_0_name": { - "type": "string", - "description": "this is my first property" - }, - "property_1_name": { - "type": "integer", - "description": "this is my second property" - } - }, - "required": [ - "property_0_name" - ] -} -``` - -
-
diff --git a/docs/_future/manifest_functions.md b/docs/_future/manifest_functions.md deleted file mode 100644 index 7c1f7aa0b..000000000 --- a/docs/_future/manifest_functions.md +++ /dev/null @@ -1,92 +0,0 @@ ---- -title: Manifest functions -order: 3 -slug: manifest-functions -lang: en -layout: future ---- - -
-Your app can [invoke Functions](/bolt-python/future/concepts#functions) defined and created by you (Custom Functions). In order for this to work, Slack must know they exist. Define them in your [App Manifest](/bolt-python/concepts#manifest) also known as `manifest.json` in your project. The next time you `slack run` your app will inform Slack they exist. - - - - - - - - - - - - - - - - - -
functions
dictionary
keystringdefines the function's callback_id
valuefunctiondefines the function
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
function
object
titlestringdefines the title
descriptionstringdefines the description
input_parametersparametersdefines the inputs
output_parametersparametersdefines the outputs
- -
- -
-Refer to our template project to view a full version of manifest.json. -```json - "functions": { - "sample_function": { - "title": "Sample function", - "description": "A sample function", - "input_parameters": { - "properties": { - "message": { - "type": "string", - "description": "Message to be posted" - } - }, - "required": [ - "message" - ] - }, - "output_parameters": { - "properties": { - "updatedMsg": { - "type": "string", - "description": "Updated message to be posted" - } - }, - "required": [ - "updatedMsg" - ] - } - } - } -``` -
diff --git a/docs/_future/manifest_workflows.md b/docs/_future/manifest_workflows.md deleted file mode 100644 index 76aa17db5..000000000 --- a/docs/_future/manifest_workflows.md +++ /dev/null @@ -1,159 +0,0 @@ ---- -title: Manifest workflows -order: 4 -slug: manifest-workflows -lang: en -layout: future ---- - -
- -Your app can use Functions by referencing them in Workflows. Your Custom Functions and the Built-in Functions can be used as steps in Workflow definitions. - -Workflows are invoked by Triggers. You will need to set up a Trigger in order to use your defined workflows. Triggers, Workflows, and Functions work together in the following way: - -Trigger โ†’ Workflow โ†’ Workflow Step โ†’ Function - -Your App Manifest, found at `manifest.json`, is where you will define your workflows. - - - - - - - - - - - - - - - - - -
workflows
dictionary
keystringdefines the workflow's id
valueworkflowdefines the function
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
workflow
object
titlestringdefines the title
descriptionstringdefines the description
input_parametersparametersdefines the inputs
stepslist[parameters]defines the steps
- - - - - - - - - - - - - - - - - - - - - - -
step
object
idstringdefines the order of the steps
function_idstringidentifies the function to evoke
inputsdict[string:string]defines the inputs to provide to the function
- -
- -
-Refer to our template project to view a full version of manifest.json. -```json - "workflows": { - "sample_workflow": { - "title": "Sample workflow", - "description": "A sample workflow", - "input_parameters": { - "properties": { - "channel": { - "type": "slack#/types/channel_id" - } - }, - "required": [ - "channel" - ] - }, - "steps": [ - { - "id": "0", - "function_id": "#/functions/sample_function", - "inputs": { - "message": "{{inputs.channel}}" - } - }, - { - "id": "1", - "function_id": "slack#/functions/send_message", - "inputs": { - "channel_id": "{{inputs.channel}}", - "message": "{{steps.0.updatedMsg}}" - } - } - ] - } - } -``` -
- -
- - -

Built-in functions

-
- -
-Slack provides built-in functions that can be used by a Workflow to accomplish simple tasks. You can add these functions to your workflow steps in order to use them. - -- Send message -- Open a form -- Create channel - -Refer to the built-in functions document to learn about the available built-in functions. -
- -```json - "$comment": "A step to post the user name to a channel" - "steps": [ - { - "id": "0", - "function_id": "slack#/functions/send_message", - "inputs": { - "channel_id": "{{inputs.channel}}", - "message": "{{inputs.user_name}}" - } - } - ] -``` - -
diff --git a/docs/_future/setup_existing_app.md b/docs/_future/setup_existing_app.md deleted file mode 100644 index 2e93253bf..000000000 --- a/docs/_future/setup_existing_app.md +++ /dev/null @@ -1,122 +0,0 @@ ---- -title: Setup an existing app -order: 7 -slug: setup-existing-app -lang: en -layout: tutorial -permalink: /tutorial/setup-existing-app ---- - -## Setup an existing app BETA - -
-If you would like to setup an existing Slack app written with the beta tools from the next-generation platform, this guide is for you! -
- -To get started with a new Bolt for Python application take a look at this [Getting Started guide](/bolt-python/tutorial/getting-started-future) instead. - ---- - -### Prerequisites {#prerequisites} - -Before we get started, make sure you've followed the [Setting Up step](/bolt-python/tutorial/getting-started-future#setting-up) of the [Getting Started guide](/bolt-python/tutorial/getting-started-future) to install required dependencies. - ---- - -### Set up your app to work with the Slack CLI {#setup-with-cli} - -Update your project's version of Bolt (sometimes found in requirements.txt) to the latest `*.dev*` distribution and reinstall your dependencies: `pip install -r requirements.txt` - -```text -# with pip -pip install slack-bolt==*.dev* - -# in requirements.txt -slack-bolt==*.dev* -``` - -Then, add a `slack.json` file to your local project root containing the contents of our [template slack.json](https://github.com/slack-samples/bolt-python-starter-template/blob/future/slack.json). - ---- - -### Add your manifest {#manifest-in-code} - -Head to [your app's App Config Page](https://api.slack.com/apps) and navigate to Features > App Manifest. Download a copy of your app manifest in the JSON file format. - -Add this `manifest.json` to your project root. This represents your project's existing configuration. To get **manifest prediction & validation** in your IDE, include the following line in your `manifest.json` file: - -```json -{ - "$schema": "https://raw.githubusercontent.com/slackapi/manifest-schema/main/manifest.schema.json", - ... -} -``` - -To learn more about the `manifest.json` take a look at the [Manifest concept](/bolt-python/future/concepts#manifest). - ---- - -Now let's run the Slack CLI command `slack manifest` to generate your manifest. It should contain at least these settings: - -```bash -{ - "_metadata": { - "major_version": 2 - }, - "oauth_config": { - "token_management_enabled": true - }, - "settings": { - "interactivity": { - "is_enabled": true - } - }, - "org_deploy_enabled": true -} -``` - -You can also run this command to validate your App's configuration with the Slack API: - -```bash -slack manifest validate -``` - ---- - -### Run your app! {#tada} - -Run the Slack CLI command `slack run` to start your app in local development. - -The CLI will create and install a new development app for you with its own App ID, allowing you to keep your testing changes separate from your production App). - -Now you're ready to start adding [Functions](/bolt-python/future/concepts#functions) and [Workflows](/bolt-python/future/concepts#manifest-workflows) to your app! - ---- - -### Updating your app configuration {#update-app} - -You have probably made changes to your appโ€™s manifest (adding a Function or a Workflow, for example). To sync your production appโ€™s configuration with the changes youโ€™ve made locally in your manifest: - -1. Authenticate the Slack CLI with your desired production workspace using `slack login`. -2. In your project, head over to `./slack/apps.json` and make sure an entry exists for your workspace with the current `app_id` and `team_id` of the workspace. - - ```bash - { - "apps": { - "": { - "name": "", - "app_id": "A041G4M3U00", - "team_id": "T038J6TH5PF" - } - }, - "default": "" - } - ``` - -3. Run `slack install` and select your app. Select your workspace from the list prompt to install. - ---- - -### Conclusion {#conclusion} - -Congratulations on migrating your app to the next-generation Slack Platform! ๐ŸŽ‰ You can continue your journey by learning about [Manifests](/bolt-python/future/concepts#manifest) or looking into adding [Functions](/bolt-python/future/concepts#functions) and [Workflows](/bolt-python/future/concepts#manifest-workflows) to your app! diff --git a/docs/_includes/analytics.html b/docs/_includes/analytics.html deleted file mode 100644 index ec5f3a8a2..000000000 --- a/docs/_includes/analytics.html +++ /dev/null @@ -1,7 +0,0 @@ - - - diff --git a/docs/_includes/head.html b/docs/_includes/head.html deleted file mode 100644 index 2e26423e3..000000000 --- a/docs/_includes/head.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - Slack | Bolt for Python - - - - - - {% if page.lang == "ja-jp" %} - - {% endif %} - - - - - - - - - - - diff --git a/docs/_includes/header.html b/docs/_includes/header.html deleted file mode 100644 index 53482748e..000000000 --- a/docs/_includes/header.html +++ /dev/null @@ -1,12 +0,0 @@ -
-
- Code on GitHub - Slack Platform Home - - {% if page.lang == "ja-jp" %} - English - {% else %} - ๆ—ฅๆœฌ่ชž (Japanese) - {% endif %} -
-
diff --git a/docs/_includes/sidebar.html b/docs/_includes/sidebar.html deleted file mode 100644 index 5e9fa141d..000000000 --- a/docs/_includes/sidebar.html +++ /dev/null @@ -1,108 +0,0 @@ - diff --git a/docs/_includes/tag_manager.html b/docs/_includes/tag_manager.html deleted file mode 100644 index 9ffc5e093..000000000 --- a/docs/_includes/tag_manager.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html deleted file mode 100644 index 3d9e34c8f..000000000 --- a/docs/_layouts/default.html +++ /dev/null @@ -1,65 +0,0 @@ ---- -sidebar_style: main ---- - - - -{% include head.html %} - - - {% include tag_manager.html %} -
-
- {% include sidebar.html %} -
- - -
-
- {% include header.html %} -
- -
- {% assign basic_sections = site.basic | sort: "order" | where: "lang", page.lang %} - {% for section in basic_sections %} -
-

{{ section.title }}

- - {{ section.content | markdownify }} - -
-
- {% endfor %} -
- -
- {% assign advanced_sections = site.advanced | sort: "order" | where: "lang", page.lang %} - {% for section in advanced_sections %} -
-

{{ section.title }}

- - {{ section.content | replace: "&", "&" | replace: "<", "<" | replace: ">" , ">" | markdownify }} -
-
- {% endfor %} -
- -
- {% assign workflow_steps = site.steps | sort: "order" | where: "lang", page.lang %} {% for section in - workflow_steps %} -
-

- {{ section.title }} - -

- {{ section.content | markdownify }} -
-
- {% endfor %} -
-
-
- {% include analytics.html %} - - - diff --git a/docs/_layouts/future.html b/docs/_layouts/future.html deleted file mode 100644 index 96b5c4a1a..000000000 --- a/docs/_layouts/future.html +++ /dev/null @@ -1,47 +0,0 @@ ---- -sidebar_style: main ---- - - - -{% include head.html %} - - - {% include tag_manager.html %} -
-
- {% include sidebar.html %} -
- - -
-
- {% include header.html %} -
- -
- {% assign future_sections = site.future | sort: "order" | where: "lang", page.lang %} - {% for section in future_sections %} - {% if section.layout != "tutorial" %} - {% if section.order == 0 %} -
- {{ section.content | markdownify }} -
-
- {% else %} -
-

{{ section.title }}

- {{ section.content | markdownify }} -
-
- {% endif %} - {% endif %} - {% endfor %} -
-
-
- - {% include analytics.html %} - - - \ No newline at end of file diff --git a/docs/_layouts/tutorial.html b/docs/_layouts/tutorial.html deleted file mode 100644 index e54aa628b..000000000 --- a/docs/_layouts/tutorial.html +++ /dev/null @@ -1,36 +0,0 @@ ---- -sidebar_style: main ---- - - - -{% include head.html %} - - - {% include tag_manager.html %} -
-
- {% include sidebar.html %} -
- - -
-
- {% include header.html %} -
- -
-
    -
    - -
    - {{ content | markdownify }} -
    -
    - -
    - - - {% include analytics.html %} - - diff --git a/docs/_steps/adding_editing_workflow_step.md b/docs/_steps/adding_editing_workflow_step.md deleted file mode 100644 index dbe507659..000000000 --- a/docs/_steps/adding_editing_workflow_step.md +++ /dev/null @@ -1,60 +0,0 @@ ---- -title: Adding or editing workflow steps -lang: en -slug: adding-editing-steps -order: 3 ---- - -
    - -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 [workflow step 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 workflow step. - -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/_steps/creating_workflow_step.md b/docs/_steps/creating_workflow_step.md deleted file mode 100644 index 91e7cf071..000000000 --- a/docs/_steps/creating_workflow_step.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -title: Creating workflow steps -lang: en -slug: creating-steps -order: 2 ---- - -
    - -To create a workflow step, 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 workflow step event. - -After instantiating a `WorkflowStep`, you can pass it into `app.step()`. Behind the scenes, your app will listen and respond to the workflow stepโ€™s events using the callbacks provided in the configuration object. - -Alternatively, workflow steps 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://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/_steps/executing_workflow_steps.md b/docs/_steps/executing_workflow_steps.md deleted file mode 100644 index 7b067eedb..000000000 --- a/docs/_steps/executing_workflow_steps.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -title: Executing workflow steps -lang: en -slug: executing-steps -order: 5 ---- - -
    - -When your workflow step 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 workflow steps 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) -``` -
    \ No newline at end of file diff --git a/docs/_steps/ja_adding_editing_workflow_step.md b/docs/_steps/ja_adding_editing_workflow_step.md deleted file mode 100644 index fed6d9740..000000000 --- a/docs/_steps/ja_adding_editing_workflow_step.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -title: ใ‚นใƒ†ใƒƒใƒ—ใฎ่ฟฝๅŠ ใƒป็ทจ้›† -lang: ja-jp -slug: adding-editing-steps -order: 3 ---- - -
    - -ไฝœๆˆใ—ใŸใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผใ‚นใƒ†ใƒƒใƒ—ใŒใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผใซ่ฟฝๅŠ ใพใŸใฏใใฎ่จญๅฎšใ‚’ๅค‰ๆ›ดใ•ใ‚Œใ‚‹ใ‚ฟใ‚คใƒŸใƒณใ‚ฐใงใ€[`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/_steps/ja_creating_workflow_step.md b/docs/_steps/ja_creating_workflow_step.md deleted file mode 100644 index 8d67f55ef..000000000 --- a/docs/_steps/ja_creating_workflow_step.md +++ /dev/null @@ -1,54 +0,0 @@ ---- -title: ใ‚นใƒ†ใƒƒใƒ—ใฎๅฎš็พฉ -lang: ja-jp -slug: creating-steps -order: 2 ---- - -
    - -ใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผใ‚นใƒ†ใƒƒใƒ—ใฎไฝœๆˆใซใฏใ€Bolt ใŒๆไพ›ใ™ใ‚‹ `WorkflowStep` ใ‚ฏใƒฉใ‚นใ‚’ๅˆฉ็”จใ—ใพใ™ใ€‚ - -ใ‚นใƒ†ใƒƒใƒ—ใฎ `callback_id` ใจ่จญๅฎšใ‚ชใƒ–ใ‚ธใ‚งใ‚ฏใƒˆใ‚’ๆŒ‡ๅฎšใ—ใฆใ€`WorkflowStep` ใฎๆ–ฐใ—ใ„ใ‚คใƒณใ‚นใ‚ฟใƒณใ‚นใ‚’ไฝœๆˆใ—ใพใ™ใ€‚ - -่จญๅฎšใ‚ชใƒ–ใ‚ธใ‚งใ‚ฏใƒˆใฏใ€`edit`ใ€`save`ใ€`execute` ใจใ„ใ† 3 ใคใฎใ‚ญใƒผใ‚’ๆŒใกใพใ™ใ€‚ใใ‚Œใžใ‚Œใฎใ‚ญใƒผใฏใ€ๅ˜ไธ€ใฎใ‚ณใƒผใƒซใƒใƒƒใ‚ฏใ€ใพใŸใฏใ‚ณใƒผใƒซใƒใƒƒใ‚ฏใฎใƒชใ‚นใƒˆใงใ‚ใ‚‹ๅฟ…่ฆใŒใ‚ใ‚Šใพใ™ใ€‚ใ™ในใฆใฎใ‚ณใƒผใƒซใƒใƒƒใ‚ฏใฏใ€ใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผใ‚นใƒ†ใƒƒใƒ—ใฎใ‚คใƒ™ใƒณใƒˆใซ้–ขใ™ใ‚‹ๆƒ…ๅ ฑใ‚’ไฟๆŒใ™ใ‚‹ `step` ใ‚ชใƒ–ใ‚ธใ‚งใ‚ฏใƒˆใซใ‚ขใ‚ฏใ‚ปใ‚นใงใใพใ™ใ€‚ - -`WorkflowStep` ใฎใ‚คใƒณใ‚นใ‚ฟใƒณใ‚นใ‚’ไฝœๆˆใ—ใŸใ‚‰ใ€ใใ‚Œใ‚’`app.step()` ใƒกใ‚ฝใƒƒใƒ‰ใซๆธกใ—ใพใ™ใ€‚ใ“ใ‚Œใซใ‚ˆใฃใฆใ€ใ‚ขใƒ—ใƒชใŒใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผใ‚นใƒ†ใƒƒใƒ—ใฎใ‚คใƒ™ใƒณใƒˆใ‚’ใƒชใƒƒใ‚นใƒณใ—ใ€่จญๅฎšใ‚ชใƒ–ใ‚ธใ‚งใ‚ฏใƒˆใงๆŒ‡ๅฎšใ•ใ‚ŒใŸใ‚ณใƒผใƒซใƒใƒƒใ‚ฏใ‚’ไฝฟใฃใฆใใ‚Œใซๅฟœ็ญ”ใงใใ‚‹ใ‚ˆใ†ใซใชใ‚Šใพใ™ใ€‚ - -ใพใŸใ€ใƒ‡ใ‚ณใƒฌใƒผใ‚ฟใƒผใจใ—ใฆๅˆฉ็”จใงใใ‚‹ `WorkflowStepBuilder` ใ‚ฏใƒฉใ‚นใ‚’ไฝฟใฃใฆใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผใ‚นใƒ†ใƒƒใƒ—ใ‚’ๅฎš็พฉใ™ใ‚‹ใ“ใจใ‚‚ใงใใพใ™ใ€‚ ่ฉณ็ดฐใฏใ€[ใ“ใกใ‚‰ใฎใƒ‰ใ‚ญใƒฅใƒกใƒณใƒˆ](https://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/_steps/ja_executing_workflow_steps.md b/docs/_steps/ja_executing_workflow_steps.md deleted file mode 100644 index 68e682ee3..000000000 --- a/docs/_steps/ja_executing_workflow_steps.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -title: ใ‚นใƒ†ใƒƒใƒ—ใฎๅฎŸ่กŒ -lang: ja-jp -slug: executing-steps -order: 5 ---- - -
    - -ใ‚จใƒณใƒ‰ใƒฆใƒผใ‚ถใƒผใŒใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผใ‚นใƒ†ใƒƒใƒ—ใ‚’ๅฎŸ่กŒใ™ใ‚‹ใจใ€ใ‚ขใƒ—ใƒชใซ [`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/_steps/ja_saving_workflow_step.md b/docs/_steps/ja_saving_workflow_step.md deleted file mode 100644 index c95f45efb..000000000 --- a/docs/_steps/ja_saving_workflow_step.md +++ /dev/null @@ -1,59 +0,0 @@ ---- -title: ใ‚นใƒ†ใƒƒใƒ—ใฎ่จญๅฎšใฎไฟๅญ˜ -lang: ja-jp -slug: saving-steps -order: 4 ---- - -
    - -่จญๅฎšใƒขใƒผใƒ€ใƒซใ‚’้–‹ใ„ใŸๅพŒใ€ใ‚ขใƒ—ใƒชใฏ `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/_steps/ja_workflow_steps_overview.md b/docs/_steps/ja_workflow_steps_overview.md deleted file mode 100644 index 4787948ee..000000000 --- a/docs/_steps/ja_workflow_steps_overview.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -title: ใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผใ‚นใƒ†ใƒƒใƒ—ใฎๆฆ‚่ฆ -lang: ja-jp -slug: steps-overview -order: 1 ---- - -
    -๏ผˆใ‚ขใƒ—ใƒชใซใ‚ˆใ‚‹๏ผ‰ใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผใ‚นใƒ†ใƒƒใƒ—ใงใฏใ€ๅ‡ฆ็†ใ‚’ใ‚ขใƒ—ใƒชๅดใง่กŒใ†ใ‚ซใ‚นใ‚ฟใƒ ใฎใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผใ‚นใƒ†ใƒƒใƒ—ใ‚’ๆไพ›ใ™ใ‚‹ใ“ใจใŒใงใใพใ™ใ€‚ใƒฆใƒผใ‚ถใƒผใฏ[ใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผใƒ“ใƒซใƒ€ใƒผ](https://api.slack.com/workflows)ใ‚’ไฝฟใฃใฆใ“ใ‚Œใ‚‰ใฎใ‚นใƒ†ใƒƒใƒ—ใ‚’ใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผใซ่ฟฝๅŠ ใงใใพใ™ใ€‚ - -ใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผใ‚นใƒ†ใƒƒใƒ—ใฏใ€ๆฌกใฎ 3 ใคใฎใƒฆใƒผใ‚ถใƒผใ‚คใƒ™ใƒณใƒˆใงๆง‹ๆˆใ•ใ‚Œใพใ™ใ€‚ - -- ใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผใ‚นใƒ†ใƒƒใƒ—ใ‚’ใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผใซ่ฟฝๅŠ ใƒปๅค‰ๆ›ดใ™ใ‚‹ -- ใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผๅ†…ใฎใ‚นใƒ†ใƒƒใƒ—ใฎ่จญๅฎšๅ†…ๅฎนใ‚’ๆ›ดๆ–ฐใ™ใ‚‹ -- ใ‚จใƒณใƒ‰ใƒฆใƒผใ‚ถใƒผใŒใใฎใ‚นใƒ†ใƒƒใƒ—ใ‚’ๅฎŸ่กŒใ™ใ‚‹ - -ใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผใ‚นใƒ†ใƒƒใƒ—ใ‚’ๆฉŸ่ƒฝใ•ใ›ใ‚‹ใŸใ‚ใซใฏใ€ใ“ใ‚Œใ‚‰ 3 ใคใฎใ‚คใƒ™ใƒณใƒˆใ™ในใฆใซๅฏพๅฟœใ™ใ‚‹ๅฟ…่ฆใŒใ‚ใ‚Šใพใ™ใ€‚ - -ใ‚ขใƒ—ใƒชใ‚’ไฝฟใฃใŸใƒฏใƒผใ‚ฏใƒ•ใƒญใƒผใ‚นใƒ†ใƒƒใƒ—ใซ้–ขใ™ใ‚‹่ฉณ็ดฐใฏใ€[API ใƒ‰ใ‚ญใƒฅใƒกใƒณใƒˆ](https://api.slack.com/workflows/steps)ใ‚’ๅ‚็…งใ—ใฆใใ ใ•ใ„ใ€‚ - -
    diff --git a/docs/_steps/saving_workflow_step.md b/docs/_steps/saving_workflow_step.md deleted file mode 100644 index 4d81f955d..000000000 --- a/docs/_steps/saving_workflow_step.md +++ /dev/null @@ -1,59 +0,0 @@ ---- -title: Saving step configurations -lang: en -slug: saving-steps -order: 4 ---- - -
    - -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 workflow step execution. -- `outputs` is a list of objects containing data that your app will provide upon the workflow 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) -``` -
    \ No newline at end of file diff --git a/docs/_steps/workflow_steps_overview.md b/docs/_steps/workflow_steps_overview.md deleted file mode 100644 index eff6d4c5e..000000000 --- a/docs/_steps/workflow_steps_overview.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -title: Overview of Workflow Steps for apps -lang: en -slug: steps-overview -order: 1 ---- - -
    -Workflow Steps from apps allow your app to create and process custom workflow steps that users can add using [Workflow Builder](https://api.slack.com/workflows). - -A workflow step is 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 workflow step to function. - -Read more about workflow steps from apps in the [API documentation](https://api.slack.com/workflows/steps). - -
    diff --git a/docs/_tutorials/getting_started.md b/docs/_tutorials/getting_started.md deleted file mode 100644 index 492494ac8..000000000 --- a/docs/_tutorials/getting_started.md +++ /dev/null @@ -1,302 +0,0 @@ ---- -title: Getting started -order: 0 -slug: getting-started -lang: en -layout: tutorial -permalink: /tutorial/getting-started -redirect_from: - - /getting-started ---- -# 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. - -> ๐Ÿ’ก 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](/bolt-python/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). - -> ๐Ÿ’ก 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](../assets/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](../assets/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. - - -> ๐Ÿ’ก 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= -``` - -> ๐Ÿ”’ Remember to 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). - -> ๐Ÿ’ก 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](/bolt-python/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://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. - -> ๐Ÿ’ก 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. - -> ๐Ÿ’ก 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](/bolt-python/concepts#basic) 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 [`events()` method](/bolt-python/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](/bolt-python/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](/bolt-python/tutorial/getting-started-http). diff --git a/docs/_tutorials/getting_started_http.md b/docs/_tutorials/getting_started_http.md deleted file mode 100644 index 75ab22f77..000000000 --- a/docs/_tutorials/getting_started_http.md +++ /dev/null @@ -1,307 +0,0 @@ ---- -title: Getting started over HTTP -order: 5 -slug: getting-started-http -lang: en -layout: tutorial -permalink: /tutorial/getting-started-http -redirect_from: - - /tutorial/getting-started-http ---- -# 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). - -> ๐Ÿ’ก 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](../assets/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](../assets/bot-token.png "Bot OAuth Token") - -> ๐Ÿ’ก 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. - -> ๐Ÿ’ก 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://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. - -> ๐Ÿ’ก 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](../assets/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. - -> ๐Ÿ’ก 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](/bolt-python/concepts#basic) 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 [`events()` method](/bolt-python/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](/bolt-python/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. diff --git a/docs/_tutorials/ja_getting_started.md b/docs/_tutorials/ja_getting_started.md deleted file mode 100644 index b85527aca..000000000 --- a/docs/_tutorials/ja_getting_started.md +++ /dev/null @@ -1,297 +0,0 @@ ---- -title: Bolt ๅ…ฅ้–€ใ‚ฌใ‚คใƒ‰ -order: 0 -slug: getting-started -lang: ja-jp -layout: tutorial -permalink: /ja-jp/tutorial/getting-started -redirect_from: - - /ja-jp/getting-started - - /getting-started/ja-jp ---- -# Bolt ๅ…ฅ้–€ใ‚ฌใ‚คใƒ‰ - -
    -ใ“ใฎใ‚ฌใ‚คใƒ‰ใงใฏใ€Bolt for Python ใ‚’ไฝฟใฃใŸ Slack ใ‚ขใƒ—ใƒชใฎ่จญๅฎšใจ่ตทๅ‹•ใ™ใ‚‹ๆ–นๆณ•ใซใคใ„ใฆ่ชฌๆ˜Žใ—ใพใ™ใ€‚ใ“ใ“ใง่ชฌๆ˜Žใ™ใ‚‹ๆ‰‹้ †ใฏใ€ๆ–ฐใ—ใ„ Slack ใ‚ขใƒ—ใƒชใ‚’ไฝœๆˆใ—ใ€ใƒญใƒผใ‚ซใƒซใฎ้–‹็™บ็’ฐๅขƒใ‚’ใ‚ปใƒƒใƒˆใ‚ขใƒƒใƒ—ใ—ใ€Slack ใƒฏใƒผใ‚ฏใ‚นใƒšใƒผใ‚นใ‹ใ‚‰ใฎใƒกใƒƒใ‚ปใƒผใ‚ธใ‚’ใƒชใƒƒใ‚นใƒณใ—ใฆๅฟœ็ญ”ใ™ใ‚‹ใ‚ขใƒ—ใƒชใ‚’้–‹็™บใ™ใ‚‹ใจใ„ใ†ๆตใ‚Œใซใชใ‚Šใพใ™ใ€‚ -
    - -ใ“ใฎๆ‰‹้ †ใ‚’ๅ…จใฆ็ต‚ใ‚ใ‚‰ใ›ใŸใ‚‰ใ€ใ‚ใชใŸใฏใใฃใจ โšก๏ธ[Slack ใ‚ขใƒ—ใƒชใฎใฏใ˜ใ‚ๆ–น](https://github.com/slackapi/bolt-python/tree/main/examples/getting_started)ใฎใ‚ตใƒณใƒ—ใƒซใ‚ขใƒ—ใƒชใ‚’ๅ‹•ไฝœใ•ใ›ใŸใ‚Šใ€ใใ‚Œใซๅค‰ๆ›ดใ‚’ๅŠ ใˆใŸใ‚Šใ€่‡ชๅˆ†ใฎใ‚ขใƒ—ใƒชใ‚’ไฝœใฃใŸใ‚Šใ™ใ‚‹ใ“ใจใŒใงใใ‚‹ใ‚ˆใ†ใซใชใ‚‹ใงใ—ใ‚‡ใ†ใ€‚ - -> ๐Ÿ’ก ใ“ใฎใ‚ฌใ‚คใƒ‰ใงใฏ[ใ‚ฝใ‚ฑใƒƒใƒˆใƒขใƒผใƒ‰](https://api.slack.com/apis/connections/socket) ใ‚’ๅˆฉ็”จใ—ใพใ™ใ€‚ใ‚ฝใ‚ฑใƒƒใƒˆใƒขใƒผใƒ‰ใฏใ€Slack ใ‚ขใƒ—ใƒช้–‹็™บใ‚’ใจใ‚Šใ‚ใˆใšๅง‹ใ‚ใฆใฟใ‚‹ใจใใ‚„ใ‚ใชใŸใฎใƒใƒผใƒ ใ ใ‘ใฎใŸใ‚ใฎใ‚ขใƒ—ใƒชใ‚’ใคใใ‚‹ใจใใซใŠใ™ใ™ใ‚ใฎใ‚„ใ‚Šๆ–นใงใ™ใ€‚ใ‚‚ใ—ใ™ใงใซ HTTP ใ‚’ใ‚ขใƒ—ใƒชใฎใ‚ณใƒŸใƒฅใƒ‹ใ‚ฑใƒผใ‚ทใƒงใƒณใƒ—ใƒญใƒˆใ‚ณใƒซใจใ—ใฆใ™ใ‚‹ใจใ‚ใ‹ใฃใฆใ„ใ‚‹ใชใ‚‰ใ€HTTP ใฎๆ–นๅผใซๅฏพๅฟœใ—ใŸๅŒๆง˜ใฎใƒ‰ใ‚ญใƒฅใƒกใƒณใƒˆใงใ‚ใ‚‹ [Bolt ๅ…ฅ้–€ใ‚ฌใ‚คใƒ‰๏ผˆHTTP๏ผ‰](/bolt-python/ja-jp/tutorial/getting-started-http) ใ‚’ๅ‚็…งใ—ใฆใใ ใ•ใ„ใ€‚ - ---- - -### ใ‚ขใƒ—ใƒชใ‚’ไฝœๆˆใ™ใ‚‹ {#create-an-app} -ๆœ€ๅˆใซใ‚„ใ‚‹ในใใ“ใจ : Bolt ใงใฎ้–‹็™บใ‚’ๅง‹ใ‚ใ‚‹ๅ‰ใซใ€[Slack ใ‚ขใƒ—ใƒชใ‚’ไฝœๆˆ](https://api.slack.com/apps/new)ใ—ใพใ™ใ€‚ - -> ๐Ÿ’ก ใ„ใคใ‚‚ใฎไป•ไบ‹ใฎใ•ใพใŸใ’ใซใชใ‚‰ใชใ„ใ‚ˆใ†ใซใ€ๅˆฅใฎ้–‹็™บ็”จใฎใƒฏใƒผใ‚ฏใ‚นใƒšใƒผใ‚นใ‚’ไฝฟ็”จใ™ใ‚‹ใ“ใจใ‚’ใŠใ™ใ™ใ‚ใ—ใพใ™ใ€‚[ๆ–ฐใ—ใ„ใƒฏใƒผใ‚ฏใ‚นใƒšใƒผใ‚นใฏ็„กๆ–™ใงไฝœๆˆใงใใพใ™](https://slack.com/get-started#create)ใ€‚ - -ใ‚ขใƒ—ใƒชๅใ‚’ๅ…ฅๅŠ›ใ—๏ผˆ_ๅพŒใงๅค‰ๆ›ดๅฏ่ƒฝ_๏ผ‰ใ€ใ‚คใƒณใ‚นใƒˆใƒผใƒซๅ…ˆใฎใƒฏใƒผใ‚ฏใ‚นใƒšใƒผใ‚นใ‚’้ธๆŠžใ—ใŸใ‚‰ใ€ใ€Œ`Create App`ใ€ใƒœใ‚ฟใƒณใ‚’ใ‚ฏใƒชใƒƒใ‚ฏใ™ใ‚‹ใจใ€ใ‚ขใƒ—ใƒชใฎ **Basic Information** ใƒšใƒผใ‚ธใŒ่กจ็คบใ•ใ‚Œใพใ™ใ€‚ - -ใ“ใฎใƒšใƒผใ‚ธใงใฏใ€ใ‚ขใƒ—ใƒชใฎๆฆ‚่ฆใ‚„ใ€้‡่ฆใช่ช่จผๆƒ…ๅ ฑใ‚‚็ขบ่ชใงใใพใ™ใ€‚ใ“ใฎๆƒ…ๅ ฑใฏๅพŒใปใฉๅ‚็…งใ—ใพใ™ใ€‚ - -![Basic Information ใƒšใƒผใ‚ธ](../../assets/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 ใƒˆใƒผใ‚ฏใƒณ](../../assets/bot-token.png "ใƒœใƒƒใƒˆ็”จ OAuth ใƒˆใƒผใ‚ฏใƒณ") - -5. ๆฌกใซใ€Œ**Basic Informationใฎใƒšใƒผใ‚ธ**ใ€ใพใงๆˆปใ‚Šใ€ใ‚ขใƒ—ใƒชใƒˆใƒผใ‚ฏใƒณใฎใ‚ปใ‚ฏใ‚ทใƒงใƒณใพใงไธ‹ใซใ‚นใ‚ฏใƒญใƒผใƒซใ—ใ€Œ**Generate Token and Scopes**ใ€ใ‚’ใ‚ฏใƒชใƒƒใ‚ฏใ—ใฆใ‚ขใƒ—ใƒชใƒฌใƒ™ใƒซใƒˆใƒผใ‚ฏใƒณใ‚’ไฝœๆˆใ—ใพใ™ใ€‚ใ“ใฎใƒˆใƒผใ‚ฏใƒณใซ `connections:write` ใฎใ‚นใ‚ณใƒผใƒ—ใ‚’ไป˜ไธŽใ—ใ€ไฝœๆˆใ•ใ‚ŒใŸ `xapp` ใƒˆใƒผใ‚ฏใƒณใ‚’ไฟๅญ˜ใ—ใพใ™ใ€‚ใ“ใ‚Œใ‚‰ใฎใƒˆใƒผใ‚ฏใƒณใฏๅพŒใปใฉๅˆฉ็”จใ—ใพใ™ใ€‚ - -6. ๅทฆใ‚ตใ‚คใƒ‰ใƒกใƒ‹ใƒฅใƒผใฎใ€Œ**Socket Mode**ใ€ใ‚’ๆœ‰ๅŠนใซใ—ใพใ™ใ€‚ - -> ๐Ÿ’ก ใƒˆใƒผใ‚ฏใƒณใฏใƒ‘ใ‚นใƒฏใƒผใƒ‰ใจๅŒๆง˜ใซๅ–ใ‚Šๆ‰ฑใ„ใ€[ๅฎ‰ๅ…จใชๆ–นๆณ•ใงไฟ็ฎกใ—ใฆใใ ใ•ใ„](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. **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-<ใƒœใƒƒใƒˆใƒˆใƒผใ‚ฏใƒณ> -``` - -2. **Basic Information ใƒšใƒผใ‚ธใฎใ‚ขใƒ—ใƒชใƒฌใƒ™ใƒซใƒˆใƒผใ‚ฏใƒณ๏ผˆxapp๏ผ‰ใ‚’ใ‚ณใƒ”ใƒผ**ใ—ใฆใ€ๅˆฅใฎ็’ฐๅขƒๅค‰ๆ•ฐใซไฟๅญ˜ใ—ใพใ™ใ€‚ -```shell -export SLACK_APP_TOKEN=<ใ‚ขใƒ—ใƒชใƒฌใƒ™ใƒซใƒˆใƒผใ‚ฏใƒณ> -``` - -> ๐Ÿ”’ ๅ…จใฆใฎใƒˆใƒผใ‚ฏใƒณใฏๅฎ‰ๅ…จใซไฟ็ฎกใ—ใฆใใ ใ•ใ„ใ€‚ๅฐ‘ใชใใจใ‚‚ใƒ‘ใƒ–ใƒชใƒƒใ‚ฏใชใƒใƒผใ‚ธใƒงใƒณ็ฎก็†ใซใƒใ‚งใƒƒใ‚ฏใ‚คใƒณใ™ใ‚‹ใ‚ˆใ†ใชใ“ใจใฏ้ฟใ‘ใ‚‹ในใใงใ—ใ‚‡ใ†ใ€‚ใพใŸใ€ไธŠใซใ‚ใฃใŸไพ‹ใฎใ‚ˆใ†ใซ็’ฐๅขƒๅค‰ๆ•ฐใ‚’ไป‹ใ—ใฆใ‚ขใ‚ฏใ‚ปใ‚นใ™ใ‚‹ใ‚ˆใ†ใซใ—ใฆใใ ใ•ใ„ใ€‚่ฉณ็ดฐใชๆƒ…ๅ ฑใฏ [ใ‚ขใƒ—ใƒชใฎใ‚ปใ‚ญใƒฅใƒชใƒ†ใ‚ฃใฎใƒ™ใ‚นใƒˆใƒ—ใƒฉใ‚ฏใƒ†ใ‚ฃใ‚น](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")) - -# ใ‚ขใƒ—ใƒชใ‚’่ตทๅ‹•ใ—ใพใ™ -if __name__ == "__main__": - SocketModeHandler(app, os.environ["SLACK_APP_TOKEN"]).start() -``` - -ใ“ใฎใ‚ˆใ†ใซใƒˆใƒผใ‚ฏใƒณใ•ใˆใ‚ใ‚Œใฐใ€ๆœ€ๅˆใฎ Bolt ใ‚ขใƒ—ใƒชใ‚’ไฝœๆˆใ™ใ‚‹ใ“ใจใŒใงใใพใ™ใ€‚ใ€Œ`app.py`ใ€ใƒ•ใ‚กใ‚คใƒซใ‚’ไฟๅญ˜ใ—ใฆใ€ใ‚ณใƒžใƒณใƒ‰ใƒฉใ‚คใƒณใงไปฅไธ‹ใ‚’ๅฎŸ่กŒใ—ใพใ™ใ€‚ - -```script -python3 app.py -``` - -ใ‚ขใƒ—ใƒชใŒ่ตทๅ‹•ใ—ใ€ๅฎŸ่กŒไธญใงใ‚ใ‚‹ใ“ใจใŒ่กจ็คบใ•ใ‚Œใพใ™ใ€‚๐ŸŽ‰ - ---- - -### ใ‚คใƒ™ใƒณใƒˆใ‚’่จญๅฎšใ™ใ‚‹ {#setting-up-events} -ใ‚ขใƒ—ใƒชใฏใƒฏใƒผใ‚ฏใ‚นใƒšใƒผใ‚นๅ†…ใฎไป–ใฎใƒกใƒณใƒใƒผใจๅŒใ˜ใ‚ˆใ†ใซๆŒฏใ‚‹่ˆžใ„ใ€ใƒกใƒƒใ‚ปใƒผใ‚ธใ‚’ๆŠ•็จฟใ—ใŸใ‚Šใ€็ตตๆ–‡ๅญ—ใƒชใ‚ขใ‚ฏใ‚ทใƒงใƒณใ‚’่ฟฝๅŠ ใ—ใŸใ‚Šใ€ใ‚คใƒ™ใƒณใƒˆใ‚’ใƒชใƒƒใ‚นใƒณใ—ใฆ่ฟ”็ญ”ใ—ใŸใ‚Šใงใใพใ™ใ€‚ - -Slack ใƒฏใƒผใ‚ฏใ‚นใƒšใƒผใ‚นใง็™บ็”Ÿใ™ใ‚‹ใ‚คใƒ™ใƒณใƒˆ๏ผˆใƒกใƒƒใ‚ปใƒผใ‚ธใŒๆŠ•็จฟใ•ใ‚ŒใŸใจใใ‚„ใ€ใƒกใƒƒใ‚ปใƒผใ‚ธใซๅฏพใ™ใ‚‹ใƒชใ‚ขใ‚ฏใ‚ทใƒงใƒณใŒใคใ‘ใ‚‰ใ‚ŒใŸใจใใชใฉ๏ผ‰ใ‚’ใƒชใƒƒใ‚นใƒณใ™ใ‚‹ใซใฏใ€[Events API ใ‚’ไฝฟใฃใฆ็‰นๅฎšใฎ็จฎ้กžใฎใ‚คใƒ™ใƒณใƒˆใ‚’ใ‚ตใƒ–ใ‚นใ‚ฏใƒฉใ‚คใƒ–ใ—ใพใ™](https://api.slack.com/events-api)ใ€‚ - -> ๐Ÿ’ก ใ“ใฎใƒใƒฅใƒผใƒˆใƒชใ‚ขใƒซใฎๅบ็›คใงใ‚ฝใ‚ฑใƒƒใƒˆใƒขใƒผใƒ‰ใ‚’ๆœ‰ๅŠนใซใ—ใพใ—ใŸใ€‚ใ‚ฝใ‚ฑใƒƒใƒˆใƒขใƒผใƒ‰ใ‚’ไฝฟใ†ใ“ใจใงใ€ใ‚ขใƒ—ใƒชใŒๅ…ฌ้–‹ใ•ใ‚ŒใŸ HTTP ใ‚จใƒณใƒ‰ใƒใ‚คใƒณใƒˆใ‚’ๅ…ฌ้–‹ใ›ใšใซ Events API ใ‚„ใ‚คใƒณใ‚ฟใƒฉใ‚ฏใƒ†ใ‚ฃใƒ–ใ‚ณใƒณใƒใƒผใƒใƒณใƒˆใ‚’ๅˆฉ็”จใงใใ‚‹ใ‚ˆใ†ใซใชใ‚Šใพใ™ใ€‚ใ“ใฎใ“ใจใฏใ€้–‹็™บๆ™‚ใ‚„ใƒ•ใ‚กใ‚คใƒคใƒผใ‚ฆใ‚ฉใƒผใƒซใฎ่ฃใ‹ใ‚‰ใฎใƒชใ‚ฏใ‚จใ‚นใƒˆใ‚’ๅ—ใ‘ใ‚‹้š›ใซไพฟๅˆฉใงใ™ใ€‚HTTP ใงใฎๆ–นๅผใฏใƒ›ใ‚นใƒ†ใ‚ฃใƒณใ‚ฐ็’ฐๅขƒใซใƒ‡ใƒ—ใƒญใ‚คใ™ใ‚‹ใ‚ขใƒ—ใƒชใ‚„ Slack App Directoryใง้…ๅธƒใ•ใ‚Œใ‚‹ใ‚ขใƒ—ใƒชใซ้ฉใ—ใฆใ„ใพใ™ใ€‚HTTP ใงใฎๆƒ…ๅ ฑใซใคใ„ใฆใฏ[ใ“ใฎใƒ‰ใ‚ญใƒฅใƒกใƒณใƒˆ](/bolt-python/ja-jp/tutorial/getting-started-http)ใ‚’ๅ‚็…งใ—ใฆใใ ใ•ใ„ใ€‚ - -ใใ‚Œใงใฏใ€็งใŸใกใŒใฉใฎใ‚คใƒ™ใƒณใƒˆใ‚’ใƒชใƒƒใ‚นใƒณใ—ใŸใ„ใ‹ใ‚’ 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 -from slack_bolt.adapter.socket_mode import SocketModeHandler - -# ใƒœใƒƒใƒˆใƒˆใƒผใ‚ฏใƒณใจใ‚ฝใ‚ฑใƒƒใƒˆใƒขใƒผใƒ‰ใƒใƒณใƒ‰ใƒฉใƒผใ‚’ไฝฟใฃใฆใ‚ขใƒ—ใƒชใ‚’ๅˆๆœŸๅŒ–ใ—ใพใ™ -app = App(token=os.environ.get("SLACK_BOT_TOKEN")) - -# 'hello' ใ‚’ๅซใ‚€ใƒกใƒƒใ‚ปใƒผใ‚ธใ‚’ใƒชใƒƒใ‚นใƒณใ—ใพใ™ -# ๆŒ‡ๅฎšๅฏ่ƒฝใชใƒชใ‚นใƒŠใƒผใฎใƒกใ‚ฝใƒƒใƒ‰ๅผ•ๆ•ฐใฎไธ€่ฆงใฏไปฅไธ‹ใฎใƒขใ‚ธใƒฅใƒผใƒซใƒ‰ใ‚ญใƒฅใƒกใƒณใƒˆใ‚’ๅ‚่€ƒใซใ—ใฆใใ ใ•ใ„๏ผš -# https://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__": - SocketModeHandler(app, os.environ["SLACK_APP_TOKEN"]).start() -``` - -ใ‚ขใƒ—ใƒชใ‚’ๅ†่ตทๅ‹•ใ—ใ€ใƒœใƒƒใƒˆใƒฆใƒผใ‚ถใƒผใŒๅ‚ๅŠ ใ—ใฆใ„ใ‚‹ใƒใƒฃใƒณใƒใƒซใพใŸใฏใƒ€ใ‚คใƒฌใ‚ฏใƒˆใƒกใƒƒใ‚ปใƒผใ‚ธใซใ€Œhelloใ€ใจใ„ใ†ใƒกใƒƒใ‚ปใƒผใ‚ธใ‚’ๆŠ•็จฟใ™ใ‚Œใฐใ€ใ‚ขใƒ—ใƒชใŒๅฟœ็ญ”ใ™ใ‚‹ใงใ—ใ‚‡ใ†ใ€‚ - -ใ“ใ‚Œใฏใ”ใๅŸบๆœฌ็š„ใชใ‚ณใƒผใƒ‰ไพ‹ใงใ™ใŒใ€ๆœ€็ต‚็š„ใซใ‚„ใ‚ŠใŸใ„ใ“ใจใ‚’ๅฎŸ็พใ™ใ‚‹ใŸใ‚ใซใ‚ขใƒ—ใƒชใ‚’ใ‚ซใ‚นใ‚ฟใƒžใ‚คใ‚บใ™ใ‚‹ใŸใ‚ใฎ่ตท็‚นใจใ—ใฆๅˆฉ็”จใงใใพใ™ใ€‚ใƒ—ใƒฌใƒผใƒณใƒ†ใ‚ญใ‚นใƒˆใ‚’้€ไฟกใ™ใ‚‹ไปฃใ‚ใ‚Šใซใƒœใ‚ฟใƒณใ‚’่กจ็คบใ™ใ‚‹ใจใ„ใ†ใ€ใ‚‚ใ†ๅฐ‘ใ—ใ‚คใƒณใ‚ฟใƒฉใ‚ฏใƒ†ใ‚ฃใƒ–ใชๅ‹•ไฝœใ‚’่ฉฆใ—ใฆใฟใพใ—ใ‚‡ใ†ใ€‚ - ---- - -### ใ‚ขใ‚ฏใ‚ทใƒงใƒณใ‚’้€ไฟกใ—ใฆๅฟœ็ญ”ใ™ใ‚‹ {#sending-and-responding-to-actions} - -ใ‚คใƒณใ‚ฟใƒฉใ‚ฏใƒ†ใ‚ฃใƒ–ๆฉŸ่ƒฝใ‚’ๆœ‰ๅŠนใซใ™ใ‚‹ใจใ€ใƒœใ‚ฟใƒณใ€้ธๆŠžใƒกใƒ‹ใƒฅใƒผใ€ๆ—ฅไป˜ใƒ”ใƒƒใ‚ซใƒผใ€ใƒขใƒผใƒ€ใƒซใ€ใ‚ทใƒงใƒผใƒˆใ‚ซใƒƒใƒˆใชใฉใฎๆฉŸ่ƒฝใŒๅˆฉ็”จใงใใ‚‹ใ‚ˆใ†ใซใชใ‚Šใพใ™ใ€‚ใ‚ขใƒ—ใƒช่จญๅฎšใƒšใƒผใ‚ธใฎใ€Œ**Interactivity & Shortcuts**ใ€ใซใ‚ขใ‚ฏใ‚ปใ‚นใ—ใฆใใ ใ•ใ„ใ€‚ - -> ๐Ÿ’ก ใ‚ฝใ‚ฑใƒƒใƒˆใƒขใƒผใƒ‰ใ‚’ๆœ‰ๅŠนใซใ—ใฆใ„ใ‚‹ใจใใ€ใƒ‡ใƒ•ใ‚ฉใƒซใƒˆใงๅŸบๆœฌ็š„ใชใ‚คใƒณใ‚ฟใƒฉใ‚ฏใƒ†ใ‚ฃใƒ–ๆฉŸ่ƒฝใŒๆœ‰ๅŠนใซใชใฃใฆใ„ใ‚‹ใ“ใจใซๆฐ—ใฅใใงใ—ใ‚‡ใ†ใ€‚่ฟฝๅŠ ใฎใ‚ขใ‚ฏใ‚ทใƒงใƒณใฏไธ่ฆใงใ™ใ€‚ใ‚‚ใ— HTTP ใ‚’ไฝฟใฃใฆใ„ใ‚‹ๅ ดๅˆใ€Slack ใ‹ใ‚‰ใฎใ‚คใƒ™ใƒณใƒˆ้€ไฟกๅ…ˆใงใ‚ใ‚‹ Request URL ใ‚’่จญๅฎšใ™ใ‚‹ๅฟ…่ฆใŒใ‚ใ‚Šใพใ™ใ€‚ - -ใ‚คใƒณใ‚ฟใƒฉใ‚ฏใƒ†ใ‚ฃใƒ“ใƒ†ใ‚ฃใŒๆœ‰ๅŠนๅŒ–ใ•ใ‚Œใฆใ„ใ‚Œใฐใ€ใ‚ทใƒงใƒผใƒˆใ‚ซใƒƒใƒˆใ€ใƒขใƒผใƒ€ใƒซใ€ใ‚คใƒณใ‚ฟใƒฉใ‚ฏใƒ†ใ‚ฃใƒ–ใ‚ณใƒณใƒใƒผใƒใƒณใƒˆ (ไพ‹๏ผšใƒœใ‚ฟใƒณใ€้ธๆŠžใƒกใƒ‹ใƒฅใƒผใ€ๆ—ฅไป˜ใƒ”ใƒƒใ‚ซใƒผ) ใจใฎใ‚คใƒณใ‚ฟใƒฉใ‚ฏใ‚ทใƒงใƒณใฏใ‚คใƒ™ใƒณใƒˆใจใ—ใฆใ‚ใชใŸใฎใ‚ขใƒ—ใƒชใซ้€ไฟกใ•ใ‚Œใพใ™ใ€‚ - -ใใ‚Œใงใฏใ€ใ‚ขใƒ—ใƒชใฎใ‚ณใƒผใƒ‰ใซๆˆปใ‚Šใ€ใ“ใ‚Œใ‚‰ใฎใ‚คใƒ™ใƒณใƒˆใ‚’ๅ‡ฆ็†ใ™ใ‚‹็‚บใฎใƒญใ‚ธใƒƒใ‚ฏใ‚’่ฟฝๅŠ ใ—ใพใ—ใ‚‡ใ†ใ€‚ -- ใพใšใ€ใ‚คใƒณใ‚ฟใƒฉใ‚ฏใƒ†ใ‚ฃใƒ–ใ‚ณใƒณใƒใƒผใƒใƒณใƒˆ๏ผˆใ“ใ“ใงใฏใƒœใ‚ฟใƒณ๏ผ‰ใ‚’ๅซใ‚“ใ ใƒกใƒƒใ‚ปใƒผใ‚ธใ‚’ใ‚ขใƒ—ใƒชใ‹ใ‚‰้€ไฟกใ—ใพใ™ใ€‚ -- ๆฌกใซใ€ใƒฆใƒผใ‚ถใƒผใ‹ใ‚‰่ฟ”ใ•ใ‚Œใ‚‹ใƒœใ‚ฟใƒณใ‚ฏใƒชใƒƒใ‚ฏใฎใ‚ขใ‚ฏใ‚ทใƒงใƒณใ‚’ใƒชใƒƒใ‚นใƒณใ—ใ€ใใ‚Œใซๅฟœ็ญ”ใ—ใพใ™ใ€‚ - -ไปฅไธ‹ใฎใ‚ณใƒผใƒ‰ใฎๅพŒใฎ้ƒจๅˆ†ใ‚’็ทจ้›†ใ—ใ€ๆ–‡ๅญ—ๅˆ—ใ ใ‘ใฎใƒกใƒƒใ‚ปใƒผใ‚ธใฎไปฃใ‚ใ‚Šใซใ€ใƒœใ‚ฟใƒณใ‚’ๅซใ‚“ใ ใƒกใƒƒใ‚ปใƒผใ‚ธใ‚’้€ไฟกใ™ใ‚‹ใ‚ˆใ†ใซใ—ใฆใฟใพใ™ใ€‚ - -```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")) - -# '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__": - SocketModeHandler(app, os.environ["SLACK_APP_TOKEN"]).start() -``` - -`say()` ใฎไธญใฎๅ€คใ‚’ `blocks` ใจใ„ใ†้…ๅˆ—ใฎใ‚ชใƒ–ใ‚ธใ‚งใ‚ฏใƒˆใซๅค‰ใˆใพใ—ใŸใ€‚ใƒ–ใƒญใƒƒใ‚ฏใฏ Slack ใƒกใƒƒใ‚ปใƒผใ‚ธใ‚’ๆง‹ๆˆใ™ใ‚‹ใ‚ณใƒณใƒใƒผใƒใƒณใƒˆใงใ‚ใ‚Šใ€ใƒ†ใ‚ญใ‚นใƒˆใ‚„็”ปๅƒใ€ๆ—ฅไป˜ใƒ”ใƒƒใ‚ซใƒผใชใฉใ€ใ•ใพใ–ใพใชใ‚ฟใ‚คใƒ—ใฎใƒ–ใƒญใƒƒใ‚ฏใŒใ‚ใ‚Šใพใ™ใ€‚ใ“ใฎไพ‹ใงใฏ `accessory` ใซ `button` ใ‚’ๆŒใŸใ›ใŸใ€Œsectionใ€ใฎใƒ–ใƒญใƒƒใ‚ฏใ‚’ใ€ใ‚ขใƒ—ใƒชใ‹ใ‚‰ใฎๅฟœ็ญ”ใซๅซใ‚ใฆใ„ใพใ™ใ€‚`blocks` ใ‚’ไฝฟ็”จใ™ใ‚‹ๅ ดๅˆใ€`text` ใฏ้€š็Ÿฅใ‚„ใ‚ขใ‚ฏใ‚ปใ‚ทใƒ“ใƒชใƒ†ใ‚ฃใฎใŸใ‚ใฎใƒ•ใ‚ฉใƒผใƒซใƒใƒƒใ‚ฏใจใชใ‚Šใพใ™ใ€‚ - -ใƒœใ‚ฟใƒณใ‚’ๅซใ‚€ `accessory` ใ‚ชใƒ–ใ‚ธใ‚งใ‚ฏใƒˆใงใฏใ€`action_id` ใ‚’ๆŒ‡ๅฎšใ—ใฆใ„ใ‚‹ใ“ใจใŒใ‚ใ‹ใ‚Šใพใ™ใ€‚ใ“ใ‚Œใฏใ€ใƒœใ‚ฟใƒณใ‚’ไธ€ๆ„ใซ็คบใ™่ญ˜ๅˆฅๅญใจใ—ใฆๆฉŸ่ƒฝใ—ใพใ™ใ€‚ใ“ใ‚Œใ‚’ไฝฟใฃใฆใ€ใ‚ขใƒ—ใƒชใ‚’ใฉใฎใ‚ขใ‚ฏใ‚ทใƒงใƒณใซๅฟœ็ญ”ใ•ใ›ใ‚‹ใ‹ใ‚’ๆŒ‡ๅฎšใงใใพใ™ใ€‚ - -> ๐Ÿ’ก [Block Kit Builder](https://app.slack.com/block-kit-builder) ใ‚’ไฝฟ็”จใ™ใ‚‹ใจใ€ใ‚คใƒณใ‚ฟใƒฉใ‚ฏใƒ†ใ‚ฃใƒ–ใชใƒกใƒƒใ‚ปใƒผใ‚ธใฎใƒ—ใƒญใƒˆใ‚ฟใ‚คใƒ—ใ‚’็ฐกๅ˜ใซไฝœๆˆใงใใพใ™ใ€‚่‡ชๅˆ†่‡ช่บซใ‚„ใƒใƒผใƒ ใƒกใƒณใƒใƒผใŒใƒกใƒƒใ‚ปใƒผใ‚ธใฎใƒขใƒƒใ‚ฏใ‚ขใƒƒใƒ—ใ‚’ไฝœๆˆใ—ใ€็”Ÿๆˆใ•ใ‚Œใ‚‹ JSON ใ‚’ใ‚ขใƒ—ใƒชใซ็›ดๆŽฅ่ฒผใ‚Šใคใ‘ใ‚‹ใ“ใจใŒใงใใพใ™ใ€‚ - -ใ‚ขใƒ—ใƒชใ‚’ๅ†่ตทๅ‹•ใ—ใ€ใ‚ขใƒ—ใƒชใŒๅ‚ๅŠ ใ—ใฆใ„ใ‚‹ใƒใƒฃใƒณใƒใƒซใงใ€Œhelloใ€ใจๅ…ฅๅŠ›ใ™ใ‚‹ใจใ€ใƒœใ‚ฟใƒณไป˜ใใฎใƒกใƒƒใ‚ปใƒผใ‚ธใŒ่กจ็คบใ•ใ‚Œใ‚‹ใ‚ˆใ†ใซใชใ‚Šใพใ—ใŸใ€‚ใŸใ ใ—ใ€ใƒœใ‚ฟใƒณใ‚’ใ‚ฏใƒชใƒƒใ‚ฏใ—ใฆใ‚‚ใ€*ใพใ *ไฝ•ใ‚‚่ตทใ“ใ‚Šใพใ›ใ‚“ใ€‚ - -ใƒใƒณใƒ‰ใƒฉใƒผใ‚’่ฟฝๅŠ ใ—ใฆใ€ใƒœใ‚ฟใƒณใŒใ‚ฏใƒชใƒƒใ‚ฏใ•ใ‚ŒใŸใจใใซใƒ•ใ‚ฉใƒญใƒผใ‚ขใƒƒใƒ—ใƒกใƒƒใ‚ปใƒผใ‚ธใ‚’้€ไฟกใ™ใ‚‹ใ‚ˆใ†ใซใ—ใฆใฟใพใ—ใ‚‡ใ†ใ€‚ - -```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")) - -# '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__": - SocketModeHandler(app, os.environ["SLACK_APP_TOKEN"]).start() -``` - -`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-python/ja-jp/concepts#basic)ใซใคใ„ใฆ่ชญใ‚“ใงใฟใฆใใ ใ•ใ„ใ€‚Bolt ใ‚ขใƒ—ใƒชใŒใ‚ขใ‚ฏใ‚ปใ‚นใงใใ‚‹ใ•ใพใ–ใพใƒกใ‚ฝใƒƒใƒ‰ใ‚„ๆฉŸ่ƒฝใซใคใ„ใฆ็Ÿฅใ‚‹ใ“ใจใŒใงใใพใ™ใ€‚ -* [`events()` ใƒกใ‚ฝใƒƒใƒ‰](/bolt-python/ja-jp/concepts#event-listening)ใงใƒœใƒƒใƒˆใŒใƒชใƒƒใ‚นใƒณใงใใ‚‹ใ‚คใƒ™ใƒณใƒˆใ‚’ใปใ‹ใซใ‚‚่ฉฆใ—ใฆใฟใพใ—ใ‚‡ใ†ใ€‚ใ™ในใฆใฎใ‚คใƒ™ใƒณใƒˆใฎไธ€่ฆงใฏ [API ใ‚ตใ‚คใƒˆ](https://api.slack.com/events)ใง็ขบ่ชใงใใพใ™ใ€‚ -* Bolt ใงใฏใ€ใ‚ขใƒ—ใƒชใซใ‚ขใ‚ฟใƒƒใƒใ•ใ‚ŒใŸใ‚ฏใƒฉใ‚คใ‚ขใƒณใƒˆใ‹ใ‚‰ [Web API ใƒกใ‚ฝใƒƒใƒ‰ใ‚’ๅ‘ผใณๅ‡บใ™](/bolt-python/ja-jp/concepts#web-api)ใ“ใจใŒใงใใพใ™ใ€‚API ใ‚ตใ‚คใƒˆใซ [220 ไปฅไธŠใฎใƒกใ‚ฝใƒƒใƒ‰](https://api.slack.com/methods)ใ‚’ไธ€่ฆงใ—ใฆใ„ใพใ™ใ€‚ -* [API ใ‚ตใ‚คใƒˆ](https://api.slack.com/docs/token-types)ใงใปใ‹ใฎใ‚ฟใ‚คใƒ—ใฎใƒˆใƒผใ‚ฏใƒณใ‚’็ขบ่ชใ—ใฆใฟใฆใใ ใ•ใ„ใ€‚ใ‚ขใƒ—ใƒชใงๅฎŸ่กŒใ—ใŸใ„ใ‚ขใ‚ฏใ‚ทใƒงใƒณใซใ‚ˆใฃใฆใ€็•ฐใชใ‚‹ใƒˆใƒผใ‚ฏใƒณใŒๅฟ…่ฆใซใชใ‚‹ๅ ดๅˆใŒใ‚ใ‚Šใพใ™ใ€‚ใ‚ฝใ‚ฑใƒƒใƒˆใƒขใƒผใƒ‰ใ‚’ไฝฟใ‚ใชใ„ใ‚ขใƒ—ใƒชใงใฏใ€้€šๅธธใฏใƒœใƒƒใƒˆใƒˆใƒผใ‚ฏใƒณ (`xoxb`) ใจ็ฝฒๅใ‚ทใƒผใ‚ฏใƒฌใƒƒใƒˆใŒๅฟ…่ฆใงใ™ใ€‚ใ‚ฝใ‚ฑใƒƒใƒˆใƒขใƒผใƒ‰ใ‚’ไฝฟใ‚ใชใ„ๅ ดๅˆใฎไพ‹ใซใคใ„ใฆใฏใ€ HTTP ๆ–นๅผใฎใ‚„ใ‚Šๆ–นใจใ—ใฆใ“ใฎใƒใƒฅใƒผใƒˆใƒชใ‚ขใƒซใจๅฏพใซใชใฃใฆใ„ใ‚‹ [Bolt ๅ…ฅ้–€ใ‚ฌใ‚คใƒ‰๏ผˆHTTP๏ผ‰](/bolt-python/ja-jp/tutorial/getting-started-http)ใ‚’ๅ‚็…งใ—ใฆใใ ใ•ใ„ใ€‚ diff --git a/docs/_tutorials/ja_getting_started_http.md b/docs/_tutorials/ja_getting_started_http.md deleted file mode 100644 index d69b77406..000000000 --- a/docs/_tutorials/ja_getting_started_http.md +++ /dev/null @@ -1,310 +0,0 @@ ---- -title: Bolt ๅ…ฅ้–€ใ‚ฌใ‚คใƒ‰๏ผˆHTTP๏ผ‰ -order: 5 -slug: getting-started-http -lang: ja-jp -layout: tutorial -permalink: /ja-jp/tutorial/getting-started-http -redirect_from: - - /ja-jp/getting-started-http - - /getting-started-http/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)ใ—ใพใ™ใ€‚ - -> ๐Ÿ’ก ใ„ใคใ‚‚ใฎไป•ไบ‹ใฎใ•ใพใŸใ’ใซใชใ‚‰ใชใ„ใ‚ˆใ†ใซใ€ๅˆฅใฎ้–‹็™บ็”จใฎใƒฏใƒผใ‚ฏใ‚นใƒšใƒผใ‚นใ‚’ไฝฟ็”จใ™ใ‚‹ใ“ใจใ‚’ใŠใ™ใ™ใ‚ใ—ใพใ™ใ€‚[ๆ–ฐใ—ใ„ใƒฏใƒผใ‚ฏใ‚นใƒšใƒผใ‚นใฏ็„กๆ–™ใงไฝœๆˆใงใใพใ™](https://slack.com/get-started#create)ใ€‚ - -ใ‚ขใƒ—ใƒชๅใ‚’ๅ…ฅๅŠ›ใ—๏ผˆ_ๅพŒใงๅค‰ๆ›ดๅฏ่ƒฝ_๏ผ‰ใ€ใ‚คใƒณใ‚นใƒˆใƒผใƒซๅ…ˆใฎใƒฏใƒผใ‚ฏใ‚นใƒšใƒผใ‚นใ‚’้ธๆŠžใ—ใŸใ‚‰ใ€ใ€Œ`Create App`ใ€ใƒœใ‚ฟใƒณใ‚’ใ‚ฏใƒชใƒƒใ‚ฏใ™ใ‚‹ใจใ€ใ‚ขใƒ—ใƒชใฎ **Basic Information** ใƒšใƒผใ‚ธใŒ่กจ็คบใ•ใ‚Œใพใ™ใ€‚ - -ใ“ใฎใƒšใƒผใ‚ธใงใฏใ€ใ‚ขใƒ—ใƒชใฎๆฆ‚่ฆใ‚’็ขบ่ชใงใใพใ™ใ€‚ใพใŸใ€ใ€Œ**App Credentials**ใ€ใƒ˜ใƒƒใƒ€ใƒผใฎไธ‹ใงใฏใ€Œ`Signing Secret`ใ€ใชใฉใฎ้‡่ฆใช่ช่จผๆƒ…ๅ ฑใ‚‚็ขบ่ชใงใใพใ™ใ€‚ใ“ใ‚Œใ‚‰ใฎ่ช่จผๆƒ…ๅ ฑใฏๅพŒใงๅฟ…่ฆใซใชใ‚Šใพใ™ใ€‚ - - -![Basic Information ใƒšใƒผใ‚ธ](../../assets/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 ใƒˆใƒผใ‚ฏใƒณ](../../assets/bot-token.png "ใƒœใƒƒใƒˆ็”จ OAuth ใƒˆใƒผใ‚ฏใƒณ") - -> ๐Ÿ’ก ใƒˆใƒผใ‚ฏใƒณใฏใƒ‘ใ‚นใƒฏใƒผใƒ‰ใจๅŒๆง˜ใซๅ–ใ‚Šๆ‰ฑใ„ใ€[ๅฎ‰ๅ…จใชๆ–นๆณ•ใงไฟ็ฎกใ—ใฆใใ ใ•ใ„](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 ใฎๆคœ่จผใŒๆˆๅŠŸใ™ใ‚‹ใฏใšใงใ™ใ€‚ - -> ๐Ÿ’ก ใƒญใƒผใ‚ซใƒซ้–‹็™บใงใฏใ€[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://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** ใƒœใƒƒใ‚ฏใ‚นใ‚’่ฆ‹ใคใ‘ใพใ™ใ€‚ - -> ๐Ÿ’ก ใƒ‡ใƒ•ใ‚ฉใƒซใƒˆใงใฏใ€Bolt ใฏใ‚คใƒ™ใƒณใƒˆใซไฝฟ็”จใ—ใฆใ„ใ‚‹ใฎใจๅŒใ˜ใ‚จใƒณใƒ‰ใƒใ‚คใƒณใƒˆใ‚’ใ‚คใƒณใ‚ฟใƒฉใ‚ฏใƒ†ใ‚ฃใƒ–ใ‚ณใƒณใƒใƒผใƒใƒณใƒˆใซใ‚‚ไฝฟ็”จใ™ใ‚‹ใ‚ˆใ†ใซ่จญๅฎšใ•ใ‚Œใฆใ„ใ‚‹ใŸใ‚ใ€ไธŠ่จ˜ใจๅŒใ˜ใƒชใ‚ฏใ‚จใ‚นใƒˆ URL๏ผˆใ“ใฎไพ‹ใงใฏใ€Œ`https://8e8ec2d7.ngrok.io/slack/events`ใ€๏ผ‰ใ‚’ไฝฟ็”จใ—ใพใ™ใ€‚ใ“ใฎใพใพใฎ็Šถๆ…‹ใงใ€ๅณไธ‹้š…ใซใ‚ใ‚‹ใ€Œ**Save Changes**ใ€ใƒœใ‚ฟใƒณใ‚’ๆŠผใ—ใฆใใ ใ•ใ„ใ€‚ใ“ใ‚Œใงใ‚คใƒณใ‚ฟใƒฉใ‚ฏใƒ†ใ‚ฃใƒ–ๆฉŸ่ƒฝใŒใ‚ขใƒ—ใƒชใงๅˆฉ็”จใงใใ‚‹ใ‚ˆใ†ใซใชใ‚Šใพใ—ใŸใ€‚ - -![Request URL ใฎ่จญๅฎš](../../assets/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` ใ‚’ๆŒ‡ๅฎšใ—ใฆใ„ใ‚‹ใ“ใจใŒใ‚ใ‹ใ‚Šใพใ™ใ€‚ใ“ใ‚Œใฏใ€ใƒœใ‚ฟใƒณใ‚’ไธ€ๆ„ใซ็คบใ™่ญ˜ๅˆฅๅญใจใ—ใฆๆฉŸ่ƒฝใ—ใพใ™ใ€‚ใ“ใ‚Œใ‚’ไฝฟใฃใฆใ€ใ‚ขใƒ—ใƒชใ‚’ใฉใฎใ‚ขใ‚ฏใ‚ทใƒงใƒณใซๅฟœ็ญ”ใ•ใ›ใ‚‹ใ‹ใ‚’ๆŒ‡ๅฎšใงใใพใ™ใ€‚ - -> ๐Ÿ’ก [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-python/ja-jp/concepts#basic)ใซใคใ„ใฆ่ชญใ‚€ใ€‚Bolt ใ‚ขใƒ—ใƒชใŒใ‚ขใ‚ฏใ‚ปใ‚นใงใใ‚‹ใ•ใพใ–ใพใƒกใ‚ฝใƒƒใƒ‰ใ‚„ๆฉŸ่ƒฝใซใคใ„ใฆ็Ÿฅใ‚‹ใ“ใจใŒใงใใพใ™ใ€‚ -* [`events()` ใƒกใ‚ฝใƒƒใƒ‰](/bolt-python/ja-jp/concepts#event-listening)ใงใƒœใƒƒใƒˆใŒใƒชใƒƒใ‚นใƒณใงใใ‚‹ใ‚คใƒ™ใƒณใƒˆใ‚’ใปใ‹ใซใ‚‚่ฉฆใ—ใฆใฟใ‚‹ใ€‚ใ™ในใฆใฎใ‚คใƒ™ใƒณใƒˆใฎไธ€่ฆงใฏ [API ใ‚ตใ‚คใƒˆ](https://api.slack.com/events)ใง็ขบ่ชใงใใพใ™ใ€‚ -* Bolt ใงใฏใ€ใ‚ขใƒ—ใƒชใซใ‚ขใ‚ฟใƒƒใƒใ•ใ‚ŒใŸใ‚ฏใƒฉใ‚คใ‚ขใƒณใƒˆใ‹ใ‚‰ [Web API ใƒกใ‚ฝใƒƒใƒ‰ใ‚’ๅ‘ผใณๅ‡บใ™](/bolt-python/ja-jp/concepts#web-api)ใ“ใจใŒใงใใพใ™ใ€‚API ใ‚ตใ‚คใƒˆใซ [220 ไปฅไธŠใฎใƒกใ‚ฝใƒƒใƒ‰](https://api.slack.com/methods)ใ‚’ไธ€่ฆงใ—ใฆใ„ใพใ™ใ€‚ -* [API ใ‚ตใ‚คใƒˆ](https://api.slack.com/docs/token-types)ใงใปใ‹ใฎใ‚ฟใ‚คใƒ—ใฎใƒˆใƒผใ‚ฏใƒณใ‚’็ขบ่ชใ™ใ‚‹ใ€‚ใ‚ขใƒ—ใƒชใงๅฎŸ่กŒใ—ใŸใ„ใ‚ขใ‚ฏใ‚ทใƒงใƒณใซใ‚ˆใฃใฆใ€็•ฐใชใ‚‹ใƒˆใƒผใ‚ฏใƒณใŒๅฟ…่ฆใซใชใ‚‹ๅ ดๅˆใŒใ‚ใ‚Šใพใ™ใ€‚HTTPใฎไปฃใ‚ใ‚Šใซใ‚ฝใ‚ฑใƒƒใƒˆใƒขใƒผใƒ‰ใ‚’ๅˆฉ็”จใ—ใŸใ„ๅ ดๅˆใซใฏใ€`connections:write` ใฎใ‚นใ‚ณใƒผใƒ—ใ‚’่ฟฝๅŠ ใ—ใŸใ€่ฟฝๅŠ ใฎใƒˆใƒผใ‚ฏใƒณ (`xapp`) ใŒๅฟ…่ฆใงใ™ใ€‚ diff --git a/docs/api-docs/slack_bolt/adapter/aiohttp/index.html b/docs/api-docs/slack_bolt/adapter/aiohttp/index.html deleted file mode 100644 index 693657840..000000000 --- a/docs/api-docs/slack_bolt/adapter/aiohttp/index.html +++ /dev/null @@ -1,167 +0,0 @@ - - - - - - -slack_bolt.adapter.aiohttp API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.adapter.aiohttp

    -
    -
    -
    - -Expand source code - -
    import re
    -
    -from aiohttp import web
    -
    -from slack_bolt.request.async_request import AsyncBoltRequest
    -from slack_bolt.response import BoltResponse
    -
    -
    -async def to_bolt_request(request: web.Request) -> AsyncBoltRequest:
    -    return AsyncBoltRequest(
    -        body=await request.text(),
    -        query=request.query_string,
    -        headers=request.headers,
    -    )
    -
    -
    -async def to_aiohttp_response(bolt_resp: BoltResponse) -> web.Response:
    -    content_type = bolt_resp.headers.pop(
    -        "content-type",
    -        ["application/json" if bolt_resp.body.startswith("{") else "text/plain"],
    -    )[0]
    -    content_type = re.sub(r";\s*charset=utf-8", "", content_type)
    -    resp = web.Response(
    -        status=bolt_resp.status,
    -        body=bolt_resp.body,
    -        headers=bolt_resp.first_headers_without_set_cookie(),
    -        content_type=content_type,
    -    )
    -    for cookie in bolt_resp.cookies():
    -        for name, c in cookie.items():
    -            resp.set_cookie(
    -                name=name,
    -                value=c.value,
    -                max_age=c.get("max-age"),
    -                expires=c.get("expires"),
    -                path=c.get("path"),
    -                domain=c.get("domain"),
    -                secure=True,
    -                httponly=True,
    -            )
    -    return resp
    -
    -
    -__all__ = [
    -    "to_bolt_request",
    -    "to_aiohttp_response",
    -]
    -
    -
    -
    -
    -
    -
    -
    -

    Functions

    -
    -
    -async def to_aiohttp_response(bolt_resp:ย BoltResponse) โ€‘>ย aiohttp.web_response.Response -
    -
    -
    -
    - -Expand source code - -
    async def to_aiohttp_response(bolt_resp: BoltResponse) -> web.Response:
    -    content_type = bolt_resp.headers.pop(
    -        "content-type",
    -        ["application/json" if bolt_resp.body.startswith("{") else "text/plain"],
    -    )[0]
    -    content_type = re.sub(r";\s*charset=utf-8", "", content_type)
    -    resp = web.Response(
    -        status=bolt_resp.status,
    -        body=bolt_resp.body,
    -        headers=bolt_resp.first_headers_without_set_cookie(),
    -        content_type=content_type,
    -    )
    -    for cookie in bolt_resp.cookies():
    -        for name, c in cookie.items():
    -            resp.set_cookie(
    -                name=name,
    -                value=c.value,
    -                max_age=c.get("max-age"),
    -                expires=c.get("expires"),
    -                path=c.get("path"),
    -                domain=c.get("domain"),
    -                secure=True,
    -                httponly=True,
    -            )
    -    return resp
    -
    -
    -
    -async def to_bolt_request(request:ย aiohttp.web_request.Request) โ€‘>ย AsyncBoltRequest -
    -
    -
    -
    - -Expand source code - -
    async def to_bolt_request(request: web.Request) -> AsyncBoltRequest:
    -    return AsyncBoltRequest(
    -        body=await request.text(),
    -        query=request.query_string,
    -        headers=request.headers,
    -    )
    -
    -
    -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/adapter/asgi/aiohttp/index.html b/docs/api-docs/slack_bolt/adapter/asgi/aiohttp/index.html deleted file mode 100644 index 3bd13d292..000000000 --- a/docs/api-docs/slack_bolt/adapter/asgi/aiohttp/index.html +++ /dev/null @@ -1,219 +0,0 @@ - - - - - - -slack_bolt.adapter.asgi.aiohttp API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.adapter.asgi.aiohttp

    -
    -
    -
    - -Expand source code - -
    from slack_bolt.oauth.async_oauth_flow import AsyncOAuthFlow
    -
    -from slack_bolt.adapter.asgi.http_request import AsgiHttpRequest
    -from slack_bolt.adapter.asgi.builtin import SlackRequestHandler
    -
    -from slack_bolt.async_app import AsyncApp
    -
    -from slack_bolt.async_app import AsyncBoltRequest
    -from slack_bolt.response import BoltResponse
    -
    -
    -class AsyncSlackRequestHandler(SlackRequestHandler):
    -    app: AsyncApp
    -
    -    def __init__(self, app: AsyncApp, path: str = "/slack/events"):
    -        """Setup Bolt as an ASGI web framework, this will make your application compatible with ASGI web servers.
    -        This can be used for production deployment.
    -
    -        With the default settings, `http://localhost:3000/slack/events`
    -        Run Bolt with [uvicron](https://www.uvicorn.org/)
    -
    -            # Python
    -            app = AsyncApp()
    -            api = SlackRequestHandler(app)
    -
    -            # bash
    -            export SLACK_SIGNING_SECRET=***
    -            export SLACK_BOT_TOKEN=xoxb-***
    -            uvicorn app:api --port 3000 --log-level debug
    -
    -        Args:
    -            app: Your bolt application
    -            path: The path to handle request from Slack (Default: `/slack/events`)
    -        """
    -        self.path = path
    -        self.app = app
    -
    -    async def dispatch(self, request: AsgiHttpRequest) -> BoltResponse:
    -        return await self.app.async_dispatch(
    -            AsyncBoltRequest(body=await request.get_raw_body(), query=request.query_string, headers=request.get_headers())
    -        )
    -
    -    async def handle_installation(self, request: AsgiHttpRequest) -> BoltResponse:
    -        oauth_flow: AsyncOAuthFlow = self.app.oauth_flow
    -        return await oauth_flow.handle_installation(
    -            AsyncBoltRequest(body=await request.get_raw_body(), query=request.query_string, headers=request.get_headers())
    -        )
    -
    -    async def handle_callback(self, request: AsgiHttpRequest) -> BoltResponse:
    -        oauth_flow: AsyncOAuthFlow = self.app.oauth_flow
    -        return await oauth_flow.handle_callback(
    -            AsyncBoltRequest(body=await request.get_raw_body(), query=request.query_string, headers=request.get_headers())
    -        )
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class AsyncSlackRequestHandler -(app:ย AsyncApp, path:ย strย =ย '/slack/events') -
    -
    -

    Setup Bolt as an ASGI web framework, this will make your application compatible with ASGI web servers. -This can be used for production deployment.

    -

    With the default settings, http://localhost:3000/slack/events -Run Bolt with uvicron

    -
    # Python
    -app = AsyncApp()
    -api = SlackRequestHandler(app)
    -
    -# bash
    -export SLACK_SIGNING_SECRET=***
    -export SLACK_BOT_TOKEN=xoxb-***
    -uvicorn app:api --port 3000 --log-level debug
    -
    -

    Args

    -
    -
    app
    -
    Your bolt application
    -
    path
    -
    The path to handle request from Slack (Default: /slack/events)
    -
    -
    - -Expand source code - -
    class AsyncSlackRequestHandler(SlackRequestHandler):
    -    app: AsyncApp
    -
    -    def __init__(self, app: AsyncApp, path: str = "/slack/events"):
    -        """Setup Bolt as an ASGI web framework, this will make your application compatible with ASGI web servers.
    -        This can be used for production deployment.
    -
    -        With the default settings, `http://localhost:3000/slack/events`
    -        Run Bolt with [uvicron](https://www.uvicorn.org/)
    -
    -            # Python
    -            app = AsyncApp()
    -            api = SlackRequestHandler(app)
    -
    -            # bash
    -            export SLACK_SIGNING_SECRET=***
    -            export SLACK_BOT_TOKEN=xoxb-***
    -            uvicorn app:api --port 3000 --log-level debug
    -
    -        Args:
    -            app: Your bolt application
    -            path: The path to handle request from Slack (Default: `/slack/events`)
    -        """
    -        self.path = path
    -        self.app = app
    -
    -    async def dispatch(self, request: AsgiHttpRequest) -> BoltResponse:
    -        return await self.app.async_dispatch(
    -            AsyncBoltRequest(body=await request.get_raw_body(), query=request.query_string, headers=request.get_headers())
    -        )
    -
    -    async def handle_installation(self, request: AsgiHttpRequest) -> BoltResponse:
    -        oauth_flow: AsyncOAuthFlow = self.app.oauth_flow
    -        return await oauth_flow.handle_installation(
    -            AsyncBoltRequest(body=await request.get_raw_body(), query=request.query_string, headers=request.get_headers())
    -        )
    -
    -    async def handle_callback(self, request: AsgiHttpRequest) -> BoltResponse:
    -        oauth_flow: AsyncOAuthFlow = self.app.oauth_flow
    -        return await oauth_flow.handle_callback(
    -            AsyncBoltRequest(body=await request.get_raw_body(), query=request.query_string, headers=request.get_headers())
    -        )
    -
    -

    Ancestors

    - -

    Class variables

    -
    -
    var app :ย AsyncApp
    -
    -
    -
    -
    -

    Inherited members

    - -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/adapter/asgi/base_handler.html b/docs/api-docs/slack_bolt/adapter/asgi/base_handler.html deleted file mode 100644 index 86f349b7d..000000000 --- a/docs/api-docs/slack_bolt/adapter/asgi/base_handler.html +++ /dev/null @@ -1,273 +0,0 @@ - - - - - - -slack_bolt.adapter.asgi.base_handler API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.adapter.asgi.base_handler

    -
    -
    -
    - -Expand source code - -
    from typing import Callable, Dict
    -
    -from .http_request import AsgiHttpRequest
    -from .http_response import AsgiHttpResponse
    -from .utils import scope_type
    -
    -from slack_bolt import App
    -
    -from slack_bolt.response import BoltResponse
    -
    -"""
    -This handler implements the ASGI standard found here https://asgi.readthedocs.io/en/latest/specs/index.html
    -"""
    -
    -
    -class BaseSlackRequestHandler:
    -    app: App  # type: ignore
    -    path: str
    -
    -    async def dispatch(self, request: AsgiHttpRequest) -> BoltResponse:
    -        """Dispatches a request to the Bolt App"""
    -        raise NotImplementedError
    -
    -    async def handle_installation(self, request: AsgiHttpRequest) -> BoltResponse:
    -        """Handles installation of the OAuthFlow"""
    -        raise NotImplementedError
    -
    -    async def handle_callback(self, request: AsgiHttpRequest) -> BoltResponse:
    -        """Handles the callback of the OAuthFlow"""
    -        raise NotImplementedError
    -
    -    async def _get_http_response(self, method: str, path: str, request: AsgiHttpRequest) -> AsgiHttpResponse:
    -        if method == "GET":
    -            if self.app.oauth_flow is not None:
    -                if path == self.app.oauth_flow.install_path:
    -                    bolt_response: BoltResponse = await self.handle_installation(request)
    -                    return AsgiHttpResponse(
    -                        status=bolt_response.status, headers=bolt_response.headers, body=bolt_response.body
    -                    )
    -                if path == self.app.oauth_flow.redirect_uri_path:
    -                    bolt_response: BoltResponse = await self.handle_callback(request)
    -                    return AsgiHttpResponse(
    -                        status=bolt_response.status, headers=bolt_response.headers, body=bolt_response.body
    -                    )
    -        if method == "POST" and path == self.path:
    -            bolt_response: BoltResponse = await self.dispatch(request)
    -            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 __call__(self, scope: scope_type, receive: Callable, send: Callable) -> None:
    -        if scope["type"] == "http":
    -            response: AsgiHttpResponse = await self._get_http_response(
    -                scope["method"], scope["path"], AsgiHttpRequest(scope, receive)
    -            )
    -            await send(response.get_response_start())
    -            await send(response.get_response_body())
    -            return
    -        if scope["type"] == "lifespan":
    -            await send(await self._handle_lifespan(receive))
    -            return
    -        raise TypeError(f"Unsupported scope type: {scope['type']}")
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class BaseSlackRequestHandler -
    -
    -
    -
    - -Expand source code - -
    class BaseSlackRequestHandler:
    -    app: App  # type: ignore
    -    path: str
    -
    -    async def dispatch(self, request: AsgiHttpRequest) -> BoltResponse:
    -        """Dispatches a request to the Bolt App"""
    -        raise NotImplementedError
    -
    -    async def handle_installation(self, request: AsgiHttpRequest) -> BoltResponse:
    -        """Handles installation of the OAuthFlow"""
    -        raise NotImplementedError
    -
    -    async def handle_callback(self, request: AsgiHttpRequest) -> BoltResponse:
    -        """Handles the callback of the OAuthFlow"""
    -        raise NotImplementedError
    -
    -    async def _get_http_response(self, method: str, path: str, request: AsgiHttpRequest) -> AsgiHttpResponse:
    -        if method == "GET":
    -            if self.app.oauth_flow is not None:
    -                if path == self.app.oauth_flow.install_path:
    -                    bolt_response: BoltResponse = await self.handle_installation(request)
    -                    return AsgiHttpResponse(
    -                        status=bolt_response.status, headers=bolt_response.headers, body=bolt_response.body
    -                    )
    -                if path == self.app.oauth_flow.redirect_uri_path:
    -                    bolt_response: BoltResponse = await self.handle_callback(request)
    -                    return AsgiHttpResponse(
    -                        status=bolt_response.status, headers=bolt_response.headers, body=bolt_response.body
    -                    )
    -        if method == "POST" and path == self.path:
    -            bolt_response: BoltResponse = await self.dispatch(request)
    -            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 __call__(self, scope: scope_type, receive: Callable, send: Callable) -> None:
    -        if scope["type"] == "http":
    -            response: AsgiHttpResponse = await self._get_http_response(
    -                scope["method"], scope["path"], AsgiHttpRequest(scope, receive)
    -            )
    -            await send(response.get_response_start())
    -            await send(response.get_response_body())
    -            return
    -        if scope["type"] == "lifespan":
    -            await send(await self._handle_lifespan(receive))
    -            return
    -        raise TypeError(f"Unsupported scope type: {scope['type']}")
    -
    -

    Subclasses

    - -

    Class variables

    -
    -
    var app :ย App
    -
    -
    -
    -
    var path :ย str
    -
    -
    -
    -
    -

    Methods

    -
    -
    -async def dispatch(self, request:ย AsgiHttpRequest) โ€‘>ย BoltResponse -
    -
    -

    Dispatches a request to the Bolt App

    -
    - -Expand source code - -
    async def dispatch(self, request: AsgiHttpRequest) -> BoltResponse:
    -    """Dispatches a request to the Bolt App"""
    -    raise NotImplementedError
    -
    -
    -
    -async def handle_callback(self, request:ย AsgiHttpRequest) โ€‘>ย BoltResponse -
    -
    -

    Handles the callback of the OAuthFlow

    -
    - -Expand source code - -
    async def handle_callback(self, request: AsgiHttpRequest) -> BoltResponse:
    -    """Handles the callback of the OAuthFlow"""
    -    raise NotImplementedError
    -
    -
    -
    -async def handle_installation(self, request:ย AsgiHttpRequest) โ€‘>ย BoltResponse -
    -
    -

    Handles installation of the OAuthFlow

    -
    - -Expand source code - -
    async def handle_installation(self, request: AsgiHttpRequest) -> BoltResponse:
    -    """Handles installation of the OAuthFlow"""
    -    raise NotImplementedError
    -
    -
    -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/adapter/asgi/builtin/index.html b/docs/api-docs/slack_bolt/adapter/asgi/builtin/index.html deleted file mode 100644 index 8f31742c4..000000000 --- a/docs/api-docs/slack_bolt/adapter/asgi/builtin/index.html +++ /dev/null @@ -1,223 +0,0 @@ - - - - - - -slack_bolt.adapter.asgi.builtin API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.adapter.asgi.builtin

    -
    -
    -
    - -Expand source code - -
    from slack_bolt.oauth.oauth_flow import OAuthFlow
    -from slack_bolt.adapter.asgi.http_request import AsgiHttpRequest
    -
    -from slack_bolt import App
    -
    -from slack_bolt.request import BoltRequest
    -from slack_bolt.response import BoltResponse
    -
    -from slack_bolt.adapter.asgi.base_handler import BaseSlackRequestHandler
    -
    -
    -class SlackRequestHandler(BaseSlackRequestHandler):
    -    def __init__(self, app: App, path: str = "/slack/events"):  # type: ignore
    -        """Setup Bolt as an ASGI web framework, this will make your application compatible with ASGI web servers.
    -        This can be used for production deployment.
    -
    -        With the default settings, `http://localhost:3000/slack/events`
    -        Run Bolt with [uvicron](https://www.uvicorn.org/)
    -
    -            # Python
    -            app = App()
    -            api = SlackRequestHandler(app)
    -
    -            # bash
    -            export SLACK_SIGNING_SECRET=***
    -            export SLACK_BOT_TOKEN=xoxb-***
    -            uvicorn app:api --port 3000 --log-level debug
    -
    -        Args:
    -            app: Your bolt application
    -            path: The path to handle request from Slack (Default: `/slack/events`)
    -        """
    -        self.path = path
    -        self.app = app
    -
    -    async def dispatch(self, request: AsgiHttpRequest) -> BoltResponse:
    -        return self.app.dispatch(
    -            BoltRequest(body=await request.get_raw_body(), query=request.query_string, headers=request.get_headers())
    -        )
    -
    -    async def handle_installation(self, request: AsgiHttpRequest) -> BoltResponse:
    -        oauth_flow: OAuthFlow = self.app.oauth_flow
    -        return oauth_flow.handle_installation(
    -            BoltRequest(body=await request.get_raw_body(), query=request.query_string, headers=request.get_headers())
    -        )
    -
    -    async def handle_callback(self, request: AsgiHttpRequest) -> BoltResponse:
    -        oauth_flow: OAuthFlow = self.app.oauth_flow
    -        return oauth_flow.handle_callback(
    -            BoltRequest(body=await request.get_raw_body(), query=request.query_string, headers=request.get_headers())
    -        )
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class SlackRequestHandler -(app:ย App, path:ย strย =ย '/slack/events') -
    -
    -

    Setup Bolt as an ASGI web framework, this will make your application compatible with ASGI web servers. -This can be used for production deployment.

    -

    With the default settings, http://localhost:3000/slack/events -Run Bolt with uvicron

    -
    # Python
    -app = App()
    -api = SlackRequestHandler(app)
    -
    -# bash
    -export SLACK_SIGNING_SECRET=***
    -export SLACK_BOT_TOKEN=xoxb-***
    -uvicorn app:api --port 3000 --log-level debug
    -
    -

    Args

    -
    -
    app
    -
    Your bolt application
    -
    path
    -
    The path to handle request from Slack (Default: /slack/events)
    -
    -
    - -Expand source code - -
    class SlackRequestHandler(BaseSlackRequestHandler):
    -    def __init__(self, app: App, path: str = "/slack/events"):  # type: ignore
    -        """Setup Bolt as an ASGI web framework, this will make your application compatible with ASGI web servers.
    -        This can be used for production deployment.
    -
    -        With the default settings, `http://localhost:3000/slack/events`
    -        Run Bolt with [uvicron](https://www.uvicorn.org/)
    -
    -            # Python
    -            app = App()
    -            api = SlackRequestHandler(app)
    -
    -            # bash
    -            export SLACK_SIGNING_SECRET=***
    -            export SLACK_BOT_TOKEN=xoxb-***
    -            uvicorn app:api --port 3000 --log-level debug
    -
    -        Args:
    -            app: Your bolt application
    -            path: The path to handle request from Slack (Default: `/slack/events`)
    -        """
    -        self.path = path
    -        self.app = app
    -
    -    async def dispatch(self, request: AsgiHttpRequest) -> BoltResponse:
    -        return self.app.dispatch(
    -            BoltRequest(body=await request.get_raw_body(), query=request.query_string, headers=request.get_headers())
    -        )
    -
    -    async def handle_installation(self, request: AsgiHttpRequest) -> BoltResponse:
    -        oauth_flow: OAuthFlow = self.app.oauth_flow
    -        return oauth_flow.handle_installation(
    -            BoltRequest(body=await request.get_raw_body(), query=request.query_string, headers=request.get_headers())
    -        )
    -
    -    async def handle_callback(self, request: AsgiHttpRequest) -> BoltResponse:
    -        oauth_flow: OAuthFlow = self.app.oauth_flow
    -        return oauth_flow.handle_callback(
    -            BoltRequest(body=await request.get_raw_body(), query=request.query_string, headers=request.get_headers())
    -        )
    -
    -

    Ancestors

    - -

    Subclasses

    - -

    Class variables

    -
    -
    var app :ย App
    -
    -
    -
    -
    var path :ย str
    -
    -
    -
    -
    -

    Inherited members

    - -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/adapter/asgi/http_request.html b/docs/api-docs/slack_bolt/adapter/asgi/http_request.html deleted file mode 100644 index 3aca34491..000000000 --- a/docs/api-docs/slack_bolt/adapter/asgi/http_request.html +++ /dev/null @@ -1,192 +0,0 @@ - - - - - - -slack_bolt.adapter.asgi.http_request API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.adapter.asgi.http_request

    -
    -
    -
    - -Expand source code - -
    from typing import Callable, Dict, Union
    -
    -from .utils import scope_type, ENCODING
    -
    -
    -class AsgiHttpRequest:
    -    __slots__ = ("receive", "query_string", "raw_headers")
    -
    -    def __init__(self, scope: scope_type, receive: Callable):
    -        self.receive = receive
    -        self.query_string = str(scope["query_string"], ENCODING)
    -        self.raw_headers = scope["headers"]
    -
    -    def get_headers(self) -> Dict[str, str]:
    -        return {str(header[0], ENCODING): str(header[1], (ENCODING)) for header in self.raw_headers}
    -
    -    async def get_raw_body(self) -> str:
    -        chunks = bytearray()
    -        while True:
    -            chunk: Dict[str, Union[str, bytes]] = await self.receive()
    -
    -            if chunk["type"] != "http.request":
    -                raise Exception("Body chunks could not be received from asgi server")
    -
    -            chunks.extend(chunk.get("body", b""))
    -            if not chunk.get("more_body", False):
    -                break
    -        return bytes(chunks).decode(ENCODING)
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class AsgiHttpRequest -(scope:ย Dict[str,ย Union[str,ย bytes,ย Iterable[Tuple[bytes,ย bytes]]]], receive:ย Callable) -
    -
    -
    -
    - -Expand source code - -
    class AsgiHttpRequest:
    -    __slots__ = ("receive", "query_string", "raw_headers")
    -
    -    def __init__(self, scope: scope_type, receive: Callable):
    -        self.receive = receive
    -        self.query_string = str(scope["query_string"], ENCODING)
    -        self.raw_headers = scope["headers"]
    -
    -    def get_headers(self) -> Dict[str, str]:
    -        return {str(header[0], ENCODING): str(header[1], (ENCODING)) for header in self.raw_headers}
    -
    -    async def get_raw_body(self) -> str:
    -        chunks = bytearray()
    -        while True:
    -            chunk: Dict[str, Union[str, bytes]] = await self.receive()
    -
    -            if chunk["type"] != "http.request":
    -                raise Exception("Body chunks could not be received from asgi server")
    -
    -            chunks.extend(chunk.get("body", b""))
    -            if not chunk.get("more_body", False):
    -                break
    -        return bytes(chunks).decode(ENCODING)
    -
    -

    Instance variables

    -
    -
    var query_string
    -
    -

    Return an attribute of instance, which is of type owner.

    -
    -
    var raw_headers
    -
    -

    Return an attribute of instance, which is of type owner.

    -
    -
    var receive
    -
    -

    Return an attribute of instance, which is of type owner.

    -
    -
    -

    Methods

    -
    -
    -def get_headers(self) โ€‘>ย Dict[str,ย str] -
    -
    -
    -
    - -Expand source code - -
    def get_headers(self) -> Dict[str, str]:
    -    return {str(header[0], ENCODING): str(header[1], (ENCODING)) for header in self.raw_headers}
    -
    -
    -
    -async def get_raw_body(self) โ€‘>ย str -
    -
    -
    -
    - -Expand source code - -
    async def get_raw_body(self) -> str:
    -    chunks = bytearray()
    -    while True:
    -        chunk: Dict[str, Union[str, bytes]] = await self.receive()
    -
    -        if chunk["type"] != "http.request":
    -            raise Exception("Body chunks could not be received from asgi server")
    -
    -        chunks.extend(chunk.get("body", b""))
    -        if not chunk.get("more_body", False):
    -            break
    -    return bytes(chunks).decode(ENCODING)
    -
    -
    -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/adapter/asgi/http_response.html b/docs/api-docs/slack_bolt/adapter/asgi/http_response.html deleted file mode 100644 index ed93d1392..000000000 --- a/docs/api-docs/slack_bolt/adapter/asgi/http_response.html +++ /dev/null @@ -1,192 +0,0 @@ - - - - - - -slack_bolt.adapter.asgi.http_response API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.adapter.asgi.http_response

    -
    -
    -
    - -Expand source code - -
    from typing import Iterable, Sequence, Tuple, Dict, Union, List
    -
    -from .utils import ENCODING
    -
    -
    -class AsgiHttpResponse:
    -    __slots__ = ("status", "raw_headers", "body")
    -
    -    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)
    -
    -    def get_response_start(self) -> Dict[str, Union[str, int, Iterable[Tuple[bytes, bytes]]]]:
    -        return {
    -            "type": "http.response.start",
    -            "status": self.status,
    -            "headers": self.raw_headers,
    -        }
    -
    -    def get_response_body(self) -> Dict[str, Union[str, bytes, bool]]:
    -        return {
    -            "type": "http.response.body",
    -            "body": self.body,
    -            "more_body": False,
    -        }
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class AsgiHttpResponse -(status:ย int, headers:ย Dict[str,ย Sequence[str]]ย =ย {}, body:ย strย =ย '') -
    -
    -
    -
    - -Expand source code - -
    class AsgiHttpResponse:
    -    __slots__ = ("status", "raw_headers", "body")
    -
    -    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)
    -
    -    def get_response_start(self) -> Dict[str, Union[str, int, Iterable[Tuple[bytes, bytes]]]]:
    -        return {
    -            "type": "http.response.start",
    -            "status": self.status,
    -            "headers": self.raw_headers,
    -        }
    -
    -    def get_response_body(self) -> Dict[str, Union[str, bytes, bool]]:
    -        return {
    -            "type": "http.response.body",
    -            "body": self.body,
    -            "more_body": False,
    -        }
    -
    -

    Instance variables

    -
    -
    var body
    -
    -

    Return an attribute of instance, which is of type owner.

    -
    -
    var raw_headers
    -
    -

    Return an attribute of instance, which is of type owner.

    -
    -
    var status
    -
    -

    Return an attribute of instance, which is of type owner.

    -
    -
    -

    Methods

    -
    -
    -def get_response_body(self) โ€‘>ย Dict[str,ย Union[str,ย bytes,ย bool]] -
    -
    -
    -
    - -Expand source code - -
    def get_response_body(self) -> Dict[str, Union[str, bytes, bool]]:
    -    return {
    -        "type": "http.response.body",
    -        "body": self.body,
    -        "more_body": False,
    -    }
    -
    -
    -
    -def get_response_start(self) โ€‘>ย Dict[str,ย Union[str,ย int,ย Iterable[Tuple[bytes,ย bytes]]]] -
    -
    -
    -
    - -Expand source code - -
    def get_response_start(self) -> Dict[str, Union[str, int, Iterable[Tuple[bytes, bytes]]]]:
    -    return {
    -        "type": "http.response.start",
    -        "status": self.status,
    -        "headers": self.raw_headers,
    -    }
    -
    -
    -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/adapter/asgi/utils.html b/docs/api-docs/slack_bolt/adapter/asgi/utils.html deleted file mode 100644 index 67df776e1..000000000 --- a/docs/api-docs/slack_bolt/adapter/asgi/utils.html +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - -slack_bolt.adapter.asgi.utils API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.adapter.asgi.utils

    -
    -
    -
    - -Expand source code - -
    from typing import Iterable, Tuple, Union, Dict
    -
    -ENCODING = "utf-8"  # should always be utf-8
    -
    -scope_value_type = Union[str, bytes, Iterable[Tuple[bytes, bytes]]]
    -
    -scope_type = Dict[str, scope_value_type]
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/adapter/aws_lambda/chalice_lazy_listener_runner.html b/docs/api-docs/slack_bolt/adapter/aws_lambda/chalice_lazy_listener_runner.html deleted file mode 100644 index 62b24b4c9..000000000 --- a/docs/api-docs/slack_bolt/adapter/aws_lambda/chalice_lazy_listener_runner.html +++ /dev/null @@ -1,170 +0,0 @@ - - - - - - -slack_bolt.adapter.aws_lambda.chalice_lazy_listener_runner API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.adapter.aws_lambda.chalice_lazy_listener_runner

    -
    -
    -
    - -Expand source code - -
    import json
    -from logging import Logger
    -from typing import Callable, Optional
    -
    -import boto3
    -from botocore.client import BaseClient
    -
    -from slack_bolt import BoltRequest
    -from slack_bolt.lazy_listener import LazyListenerRunner
    -
    -
    -class ChaliceLazyListenerRunner(LazyListenerRunner):
    -    def __init__(self, logger: Logger, lambda_client: Optional[BaseClient] = None):
    -        self.lambda_client = lambda_client
    -        self.logger = logger
    -
    -    def start(self, function: Callable[..., None], request: BoltRequest) -> None:
    -        if self.lambda_client is None:
    -            self.lambda_client = boto3.client("lambda")
    -
    -        chalice_request: dict = request.context["chalice_request"]
    -        request.headers["x-slack-bolt-lazy-only"] = ["1"]
    -        request.headers["x-slack-bolt-lazy-function-name"] = [request.lazy_function_name]
    -        payload = {
    -            "method": "NONE",
    -            "headers": {k: v[0] for k, v in request.headers.items()},
    -            "multiValueQueryStringParameters": request.query,
    -            "queryStringParameters": {k: v[0] for k, v in request.query.items()},
    -            "pathParameters": {},
    -            "stageVariables": {},
    -            "requestContext": chalice_request["context"],
    -            "body": request.raw_body,
    -            "isBase64Encoded": False,
    -        }
    -        invocation = self.lambda_client.invoke(
    -            FunctionName=request.context["aws_lambda_function_name"],
    -            InvocationType="Event",
    -            Payload=json.dumps(payload),
    -        )
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class ChaliceLazyListenerRunner -(logger:ย logging.Logger, lambda_client:ย Optional[botocore.client.BaseClient]ย =ย None) -
    -
    -
    -
    - -Expand source code - -
    class ChaliceLazyListenerRunner(LazyListenerRunner):
    -    def __init__(self, logger: Logger, lambda_client: Optional[BaseClient] = None):
    -        self.lambda_client = lambda_client
    -        self.logger = logger
    -
    -    def start(self, function: Callable[..., None], request: BoltRequest) -> None:
    -        if self.lambda_client is None:
    -            self.lambda_client = boto3.client("lambda")
    -
    -        chalice_request: dict = request.context["chalice_request"]
    -        request.headers["x-slack-bolt-lazy-only"] = ["1"]
    -        request.headers["x-slack-bolt-lazy-function-name"] = [request.lazy_function_name]
    -        payload = {
    -            "method": "NONE",
    -            "headers": {k: v[0] for k, v in request.headers.items()},
    -            "multiValueQueryStringParameters": request.query,
    -            "queryStringParameters": {k: v[0] for k, v in request.query.items()},
    -            "pathParameters": {},
    -            "stageVariables": {},
    -            "requestContext": chalice_request["context"],
    -            "body": request.raw_body,
    -            "isBase64Encoded": False,
    -        }
    -        invocation = self.lambda_client.invoke(
    -            FunctionName=request.context["aws_lambda_function_name"],
    -            InvocationType="Event",
    -            Payload=json.dumps(payload),
    -        )
    -
    -

    Ancestors

    - -

    Class variables

    -
    -
    var logger :ย logging.Logger
    -
    -
    -
    -
    -

    Inherited members

    - -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/adapter/aws_lambda/internals.html b/docs/api-docs/slack_bolt/adapter/aws_lambda/internals.html deleted file mode 100644 index 653a3c7f0..000000000 --- a/docs/api-docs/slack_bolt/adapter/aws_lambda/internals.html +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - -slack_bolt.adapter.aws_lambda.internals API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.adapter.aws_lambda.internals

    -
    -
    -
    - -Expand source code - -
    from typing import Dict, Optional, Sequence
    -
    -
    -def _first_value(query: Dict[str, Sequence[str]], name: str) -> Optional[str]:
    -    if query:
    -        values = query.get(name, [])
    -        if values and len(values) > 0:
    -            return values[0]
    -    return None
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/adapter/aws_lambda/lambda_s3_oauth_flow.html b/docs/api-docs/slack_bolt/adapter/aws_lambda/lambda_s3_oauth_flow.html deleted file mode 100644 index f3fe5a2ef..000000000 --- a/docs/api-docs/slack_bolt/adapter/aws_lambda/lambda_s3_oauth_flow.html +++ /dev/null @@ -1,303 +0,0 @@ - - - - - - -slack_bolt.adapter.aws_lambda.lambda_s3_oauth_flow API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.adapter.aws_lambda.lambda_s3_oauth_flow

    -
    -
    -
    - -Expand source code - -
    import logging
    -import os
    -from logging import Logger
    -from typing import Optional
    -
    -import boto3
    -
    -from slack_bolt.authorization.authorize import InstallationStoreAuthorize
    -from slack_bolt.oauth import OAuthFlow
    -from slack_sdk import WebClient
    -from slack_sdk.oauth.installation_store.amazon_s3 import AmazonS3InstallationStore
    -from slack_sdk.oauth.state_store.amazon_s3 import AmazonS3OAuthStateStore
    -
    -from slack_bolt.oauth.oauth_settings import OAuthSettings
    -from slack_bolt.util.utils import create_web_client
    -
    -
    -class LambdaS3OAuthFlow(OAuthFlow):
    -    def __init__(
    -        self,
    -        *,
    -        client: Optional[WebClient] = None,
    -        logger: Optional[Logger] = None,
    -        settings: Optional[OAuthSettings] = None,
    -        oauth_state_bucket_name: Optional[str] = None,  # required
    -        installation_bucket_name: Optional[str] = None,  # required
    -    ):
    -        logger = logger or logging.getLogger(__name__)
    -        settings = settings or OAuthSettings(
    -            client_id=os.environ["SLACK_CLIENT_ID"],
    -            client_secret=os.environ["SLACK_CLIENT_SECRET"],
    -        )
    -        oauth_state_bucket_name = oauth_state_bucket_name or os.environ["SLACK_STATE_S3_BUCKET_NAME"]
    -        installation_bucket_name = installation_bucket_name or os.environ["SLACK_INSTALLATION_S3_BUCKET_NAME"]
    -        self.s3_client = boto3.client("s3")
    -        if settings.state_store is None or not isinstance(settings.state_store, AmazonS3OAuthStateStore):
    -            settings.state_store = AmazonS3OAuthStateStore(
    -                logger=logger,
    -                s3_client=self.s3_client,
    -                bucket_name=oauth_state_bucket_name,
    -                expiration_seconds=settings.state_expiration_seconds,
    -            )
    -
    -        if settings.installation_store is None or not isinstance(settings.installation_store, AmazonS3InstallationStore):
    -            settings.installation_store = AmazonS3InstallationStore(
    -                logger=logger,
    -                s3_client=self.s3_client,
    -                bucket_name=installation_bucket_name,
    -                client_id=settings.client_id,
    -            )
    -
    -        # Set up authorize function to surely use this installation_store.
    -        # When a developer use a settings initialized outside this constructor,
    -        # the settings may already have pre-defined authorize.
    -        # In this case, the /slack/events endpoint doesn't work along with the OAuth flow.
    -        settings.authorize = InstallationStoreAuthorize(
    -            logger=logger,
    -            client_id=settings.client_id,
    -            client_secret=settings.client_secret,
    -            installation_store=settings.installation_store,
    -            bot_only=settings.installation_store_bot_only,
    -            user_token_resolution=(settings.user_token_resolution if settings is not None else "authed_user"),
    -        )
    -
    -        OAuthFlow.__init__(self, client=client, logger=logger, settings=settings)
    -
    -    @property
    -    def client(self) -> WebClient:
    -        if self._client is None:
    -            self._client = create_web_client(logger=self.logger)
    -        return self._client
    -
    -    @property
    -    def logger(self) -> Logger:
    -        if self._logger is None:
    -            self._logger = logging.getLogger(__name__)
    -        return self._logger
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class LambdaS3OAuthFlow -(*, client:ย Optional[slack_sdk.web.client.WebClient]ย =ย None, logger:ย Optional[logging.Logger]ย =ย None, settings:ย Optional[OAuthSettings]ย =ย None, oauth_state_bucket_name:ย Optional[str]ย =ย None, installation_bucket_name:ย Optional[str]ย =ย None) -
    -
    -

    The module to run the Slack app installation flow (OAuth flow).

    -

    Args

    -
    -
    client
    -
    The slack_sdk.web.WebClient instance.
    -
    logger
    -
    The logger.
    -
    settings
    -
    OAuth settings to configure this module.
    -
    -
    - -Expand source code - -
    class LambdaS3OAuthFlow(OAuthFlow):
    -    def __init__(
    -        self,
    -        *,
    -        client: Optional[WebClient] = None,
    -        logger: Optional[Logger] = None,
    -        settings: Optional[OAuthSettings] = None,
    -        oauth_state_bucket_name: Optional[str] = None,  # required
    -        installation_bucket_name: Optional[str] = None,  # required
    -    ):
    -        logger = logger or logging.getLogger(__name__)
    -        settings = settings or OAuthSettings(
    -            client_id=os.environ["SLACK_CLIENT_ID"],
    -            client_secret=os.environ["SLACK_CLIENT_SECRET"],
    -        )
    -        oauth_state_bucket_name = oauth_state_bucket_name or os.environ["SLACK_STATE_S3_BUCKET_NAME"]
    -        installation_bucket_name = installation_bucket_name or os.environ["SLACK_INSTALLATION_S3_BUCKET_NAME"]
    -        self.s3_client = boto3.client("s3")
    -        if settings.state_store is None or not isinstance(settings.state_store, AmazonS3OAuthStateStore):
    -            settings.state_store = AmazonS3OAuthStateStore(
    -                logger=logger,
    -                s3_client=self.s3_client,
    -                bucket_name=oauth_state_bucket_name,
    -                expiration_seconds=settings.state_expiration_seconds,
    -            )
    -
    -        if settings.installation_store is None or not isinstance(settings.installation_store, AmazonS3InstallationStore):
    -            settings.installation_store = AmazonS3InstallationStore(
    -                logger=logger,
    -                s3_client=self.s3_client,
    -                bucket_name=installation_bucket_name,
    -                client_id=settings.client_id,
    -            )
    -
    -        # Set up authorize function to surely use this installation_store.
    -        # When a developer use a settings initialized outside this constructor,
    -        # the settings may already have pre-defined authorize.
    -        # In this case, the /slack/events endpoint doesn't work along with the OAuth flow.
    -        settings.authorize = InstallationStoreAuthorize(
    -            logger=logger,
    -            client_id=settings.client_id,
    -            client_secret=settings.client_secret,
    -            installation_store=settings.installation_store,
    -            bot_only=settings.installation_store_bot_only,
    -            user_token_resolution=(settings.user_token_resolution if settings is not None else "authed_user"),
    -        )
    -
    -        OAuthFlow.__init__(self, client=client, logger=logger, settings=settings)
    -
    -    @property
    -    def client(self) -> WebClient:
    -        if self._client is None:
    -            self._client = create_web_client(logger=self.logger)
    -        return self._client
    -
    -    @property
    -    def logger(self) -> Logger:
    -        if self._logger is None:
    -            self._logger = logging.getLogger(__name__)
    -        return self._logger
    -
    -

    Ancestors

    - -

    Class variables

    -
    -
    var client_id :ย str
    -
    -
    -
    -
    var failure_handler :ย Callable[[FailureArgs],ย BoltResponse]
    -
    -
    -
    -
    var install_path :ย str
    -
    -
    -
    -
    var redirect_uri :ย Optional[str]
    -
    -
    -
    -
    var redirect_uri_path :ย str
    -
    -
    -
    -
    var settings :ย OAuthSettings
    -
    -
    -
    -
    var success_handler :ย Callable[[SuccessArgs],ย BoltResponse]
    -
    -
    -
    -
    -

    Instance variables

    -
    -
    var client :ย slack_sdk.web.client.WebClient
    -
    -
    -
    - -Expand source code - -
    @property
    -def client(self) -> WebClient:
    -    if self._client is None:
    -        self._client = create_web_client(logger=self.logger)
    -    return self._client
    -
    -
    -
    var logger :ย logging.Logger
    -
    -
    -
    - -Expand source code - -
    @property
    -def logger(self) -> Logger:
    -    if self._logger is None:
    -        self._logger = logging.getLogger(__name__)
    -    return self._logger
    -
    -
    -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/adapter/aws_lambda/lazy_listener_runner.html b/docs/api-docs/slack_bolt/adapter/aws_lambda/lazy_listener_runner.html deleted file mode 100644 index 3f030bc11..000000000 --- a/docs/api-docs/slack_bolt/adapter/aws_lambda/lazy_listener_runner.html +++ /dev/null @@ -1,153 +0,0 @@ - - - - - - -slack_bolt.adapter.aws_lambda.lazy_listener_runner API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.adapter.aws_lambda.lazy_listener_runner

    -
    -
    -
    - -Expand source code - -
    import json
    -from logging import Logger
    -from typing import Callable, Optional, Any
    -
    -import boto3
    -
    -from slack_bolt import BoltRequest
    -from slack_bolt.lazy_listener import LazyListenerRunner
    -
    -
    -class LambdaLazyListenerRunner(LazyListenerRunner):
    -    def __init__(self, logger: Logger, lambda_client: Optional[Any] = None):
    -        self.lambda_client = lambda_client
    -        self.logger = logger
    -
    -    def start(self, function: Callable[..., None], request: BoltRequest) -> None:
    -        if self.lambda_client is None:
    -            self.lambda_client = boto3.client("lambda")
    -
    -        event: dict = request.context["lambda_request"]
    -        headers = event["headers"]
    -        headers["x-slack-bolt-lazy-only"] = "1"  # not an array
    -        headers["x-slack-bolt-lazy-function-name"] = request.lazy_function_name  # not an array
    -        event["method"] = "NONE"
    -        invocation = self.lambda_client.invoke(
    -            FunctionName=request.context["aws_lambda_invoked_function_arn"],
    -            InvocationType="Event",
    -            Payload=json.dumps(event),
    -        )
    -        self.logger.info(invocation)
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class LambdaLazyListenerRunner -(logger:ย logging.Logger, lambda_client:ย Optional[Any]ย =ย None) -
    -
    -
    -
    - -Expand source code - -
    class LambdaLazyListenerRunner(LazyListenerRunner):
    -    def __init__(self, logger: Logger, lambda_client: Optional[Any] = None):
    -        self.lambda_client = lambda_client
    -        self.logger = logger
    -
    -    def start(self, function: Callable[..., None], request: BoltRequest) -> None:
    -        if self.lambda_client is None:
    -            self.lambda_client = boto3.client("lambda")
    -
    -        event: dict = request.context["lambda_request"]
    -        headers = event["headers"]
    -        headers["x-slack-bolt-lazy-only"] = "1"  # not an array
    -        headers["x-slack-bolt-lazy-function-name"] = request.lazy_function_name  # not an array
    -        event["method"] = "NONE"
    -        invocation = self.lambda_client.invoke(
    -            FunctionName=request.context["aws_lambda_invoked_function_arn"],
    -            InvocationType="Event",
    -            Payload=json.dumps(event),
    -        )
    -        self.logger.info(invocation)
    -
    -

    Ancestors

    - -

    Class variables

    -
    -
    var logger :ย logging.Logger
    -
    -
    -
    -
    -

    Inherited members

    - -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/adapter/aws_lambda/local_lambda_client.html b/docs/api-docs/slack_bolt/adapter/aws_lambda/local_lambda_client.html deleted file mode 100644 index 6bf632f6e..000000000 --- a/docs/api-docs/slack_bolt/adapter/aws_lambda/local_lambda_client.html +++ /dev/null @@ -1,158 +0,0 @@ - - - - - - -slack_bolt.adapter.aws_lambda.local_lambda_client API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.adapter.aws_lambda.local_lambda_client

    -
    -
    -
    - -Expand source code - -
    import json
    -
    -from chalice.app import Chalice
    -from chalice.config import Config
    -from chalice.test import BaseClient, LambdaContext, InvokeResponse
    -
    -
    -class LocalLambdaClient(BaseClient):
    -    """Lambda client implementing `invoke` for use when running with Chalice CLI."""
    -
    -    def __init__(self, app: Chalice, config: Config) -> None:
    -        self._app = app
    -        self._config = config if config else Config()
    -
    -    def invoke(
    -        self,
    -        FunctionName: str,
    -        InvocationType: str = "Event",
    -        Payload: str = "{}",
    -    ) -> InvokeResponse:
    -        scoped = self._config.scope(self._config.chalice_stage, FunctionName)
    -        lambda_context = LambdaContext(FunctionName, memory_size=scoped.lambda_memory_size)
    -
    -        with self._patched_env_vars(scoped.environment_variables):
    -            response = self._app(json.loads(Payload), lambda_context)
    -        return InvokeResponse(payload=response)
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class LocalLambdaClient -(app:ย chalice.app.Chalice, config:ย chalice.config.Config) -
    -
    -

    Lambda client implementing invoke for use when running with Chalice CLI.

    -
    - -Expand source code - -
    class LocalLambdaClient(BaseClient):
    -    """Lambda client implementing `invoke` for use when running with Chalice CLI."""
    -
    -    def __init__(self, app: Chalice, config: Config) -> None:
    -        self._app = app
    -        self._config = config if config else Config()
    -
    -    def invoke(
    -        self,
    -        FunctionName: str,
    -        InvocationType: str = "Event",
    -        Payload: str = "{}",
    -    ) -> InvokeResponse:
    -        scoped = self._config.scope(self._config.chalice_stage, FunctionName)
    -        lambda_context = LambdaContext(FunctionName, memory_size=scoped.lambda_memory_size)
    -
    -        with self._patched_env_vars(scoped.environment_variables):
    -            response = self._app(json.loads(Payload), lambda_context)
    -        return InvokeResponse(payload=response)
    -
    -

    Ancestors

    -
      -
    • chalice.test.BaseClient
    • -
    -

    Methods

    -
    -
    -def invoke(self, FunctionName:ย str, InvocationType:ย strย =ย 'Event', Payload:ย strย =ย '{}') โ€‘>ย chalice.test.InvokeResponse -
    -
    -
    -
    - -Expand source code - -
    def invoke(
    -    self,
    -    FunctionName: str,
    -    InvocationType: str = "Event",
    -    Payload: str = "{}",
    -) -> InvokeResponse:
    -    scoped = self._config.scope(self._config.chalice_stage, FunctionName)
    -    lambda_context = LambdaContext(FunctionName, memory_size=scoped.lambda_memory_size)
    -
    -    with self._patched_env_vars(scoped.environment_variables):
    -        response = self._app(json.loads(Payload), lambda_context)
    -    return InvokeResponse(payload=response)
    -
    -
    -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/adapter/flask/index.html b/docs/api-docs/slack_bolt/adapter/flask/index.html deleted file mode 100644 index 02c5dd6b8..000000000 --- a/docs/api-docs/slack_bolt/adapter/flask/index.html +++ /dev/null @@ -1,148 +0,0 @@ - - - - - - -slack_bolt.adapter.flask API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.adapter.flask

    -
    -
    -
    - -Expand source code - -
    from .handler import SlackRequestHandler
    -
    -__all__ = [
    -    "SlackRequestHandler",
    -]
    -
    -
    -
    -

    Sub-modules

    -
    -
    slack_bolt.adapter.flask.handler
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class SlackRequestHandler -(app:ย App) -
    -
    -
    -
    - -Expand source code - -
    class SlackRequestHandler:
    -    def __init__(self, app: App):  # type: ignore
    -        self.app = app
    -
    -    def handle(self, req: Request) -> Response:
    -        if req.method == "GET":
    -            if self.app.oauth_flow is not None:
    -                oauth_flow: OAuthFlow = self.app.oauth_flow
    -                if req.path == oauth_flow.install_path:
    -                    bolt_resp = oauth_flow.handle_installation(to_bolt_request(req))
    -                    return to_flask_response(bolt_resp)
    -                elif req.path == oauth_flow.redirect_uri_path:
    -                    bolt_resp = oauth_flow.handle_callback(to_bolt_request(req))
    -                    return to_flask_response(bolt_resp)
    -        elif req.method == "POST":
    -            bolt_resp: BoltResponse = self.app.dispatch(to_bolt_request(req))
    -            return to_flask_response(bolt_resp)
    -
    -        return make_response("Not Found", 404)
    -
    -

    Methods

    -
    -
    -def handle(self, req:ย flask.wrappers.Request) โ€‘>ย flask.wrappers.Response -
    -
    -
    -
    - -Expand source code - -
    def handle(self, req: Request) -> Response:
    -    if req.method == "GET":
    -        if self.app.oauth_flow is not None:
    -            oauth_flow: OAuthFlow = self.app.oauth_flow
    -            if req.path == oauth_flow.install_path:
    -                bolt_resp = oauth_flow.handle_installation(to_bolt_request(req))
    -                return to_flask_response(bolt_resp)
    -            elif req.path == oauth_flow.redirect_uri_path:
    -                bolt_resp = oauth_flow.handle_callback(to_bolt_request(req))
    -                return to_flask_response(bolt_resp)
    -    elif req.method == "POST":
    -        bolt_resp: BoltResponse = self.app.dispatch(to_bolt_request(req))
    -        return to_flask_response(bolt_resp)
    -
    -    return make_response("Not Found", 404)
    -
    -
    -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/adapter/google_cloud_functions/handler.html b/docs/api-docs/slack_bolt/adapter/google_cloud_functions/handler.html deleted file mode 100644 index 652bc59e3..000000000 --- a/docs/api-docs/slack_bolt/adapter/google_cloud_functions/handler.html +++ /dev/null @@ -1,220 +0,0 @@ - - - - - - -slack_bolt.adapter.google_cloud_functions.handler API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.adapter.google_cloud_functions.handler

    -
    -
    -
    - -Expand source code - -
    from typing import Callable
    -
    -from flask import Request, Response, make_response
    -
    -from slack_bolt.adapter.flask.handler import to_bolt_request, to_flask_response
    -from slack_bolt.app import App
    -from slack_bolt.error import BoltError
    -from slack_bolt.lazy_listener import LazyListenerRunner
    -from slack_bolt.request import BoltRequest
    -from slack_bolt.response import BoltResponse
    -
    -
    -class NoopLazyListenerRunner(LazyListenerRunner):
    -    def start(self, function: Callable[..., None], request: BoltRequest) -> None:
    -        raise BoltError(
    -            "The google_cloud_functions adapter does not support lazy listeners. "
    -            "Please consider either having a queue to pass the request to a different function or "
    -            "rewriting your code not to use lazy listeners."
    -        )
    -
    -
    -class SlackRequestHandler:
    -    def __init__(self, app: App):  # type: ignore
    -        self.app = app
    -        # Note that lazy listener is not supported
    -        self.app.listener_runner.lazy_listener_runner = NoopLazyListenerRunner()
    -        if self.app.oauth_flow is not None:
    -            self.app.oauth_flow.settings.redirect_uri_page_renderer.install_path = "?"
    -
    -    def handle(self, req: Request) -> Response:
    -        if req.method == "GET" and self.app.oauth_flow is not None:
    -            bolt_req = to_bolt_request(req)
    -            if "code" in req.args or "error" in req.args or "state" in req.args:
    -                bolt_resp = self.app.oauth_flow.handle_callback(bolt_req)
    -                return to_flask_response(bolt_resp)
    -            else:
    -                bolt_resp = self.app.oauth_flow.handle_installation(bolt_req)
    -                return to_flask_response(bolt_resp)
    -        elif req.method == "POST":
    -            bolt_resp: BoltResponse = self.app.dispatch(to_bolt_request(req))
    -            return to_flask_response(bolt_resp)
    -
    -        return make_response("Not Found", 404)
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class NoopLazyListenerRunner -
    -
    -
    -
    - -Expand source code - -
    class NoopLazyListenerRunner(LazyListenerRunner):
    -    def start(self, function: Callable[..., None], request: BoltRequest) -> None:
    -        raise BoltError(
    -            "The google_cloud_functions adapter does not support lazy listeners. "
    -            "Please consider either having a queue to pass the request to a different function or "
    -            "rewriting your code not to use lazy listeners."
    -        )
    -
    -

    Ancestors

    - -

    Class variables

    -
    -
    var logger :ย logging.Logger
    -
    -
    -
    -
    -

    Inherited members

    - -
    -
    -class SlackRequestHandler -(app:ย App) -
    -
    -
    -
    - -Expand source code - -
    class SlackRequestHandler:
    -    def __init__(self, app: App):  # type: ignore
    -        self.app = app
    -        # Note that lazy listener is not supported
    -        self.app.listener_runner.lazy_listener_runner = NoopLazyListenerRunner()
    -        if self.app.oauth_flow is not None:
    -            self.app.oauth_flow.settings.redirect_uri_page_renderer.install_path = "?"
    -
    -    def handle(self, req: Request) -> Response:
    -        if req.method == "GET" and self.app.oauth_flow is not None:
    -            bolt_req = to_bolt_request(req)
    -            if "code" in req.args or "error" in req.args or "state" in req.args:
    -                bolt_resp = self.app.oauth_flow.handle_callback(bolt_req)
    -                return to_flask_response(bolt_resp)
    -            else:
    -                bolt_resp = self.app.oauth_flow.handle_installation(bolt_req)
    -                return to_flask_response(bolt_resp)
    -        elif req.method == "POST":
    -            bolt_resp: BoltResponse = self.app.dispatch(to_bolt_request(req))
    -            return to_flask_response(bolt_resp)
    -
    -        return make_response("Not Found", 404)
    -
    -

    Methods

    -
    -
    -def handle(self, req:ย flask.wrappers.Request) โ€‘>ย flask.wrappers.Response -
    -
    -
    -
    - -Expand source code - -
    def handle(self, req: Request) -> Response:
    -    if req.method == "GET" and self.app.oauth_flow is not None:
    -        bolt_req = to_bolt_request(req)
    -        if "code" in req.args or "error" in req.args or "state" in req.args:
    -            bolt_resp = self.app.oauth_flow.handle_callback(bolt_req)
    -            return to_flask_response(bolt_resp)
    -        else:
    -            bolt_resp = self.app.oauth_flow.handle_installation(bolt_req)
    -            return to_flask_response(bolt_resp)
    -    elif req.method == "POST":
    -        bolt_resp: BoltResponse = self.app.dispatch(to_bolt_request(req))
    -        return to_flask_response(bolt_resp)
    -
    -    return make_response("Not Found", 404)
    -
    -
    -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/adapter/sanic/async_handler.html b/docs/api-docs/slack_bolt/adapter/sanic/async_handler.html deleted file mode 100644 index 7619d773a..000000000 --- a/docs/api-docs/slack_bolt/adapter/sanic/async_handler.html +++ /dev/null @@ -1,259 +0,0 @@ - - - - - - -slack_bolt.adapter.sanic.async_handler API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.adapter.sanic.async_handler

    -
    -
    -
    - -Expand source code - -
    from datetime import datetime  # type: ignore
    -
    -from sanic.request import Request
    -from sanic.response import HTTPResponse
    -
    -from slack_bolt import BoltResponse
    -from slack_bolt.async_app import AsyncApp, AsyncBoltRequest
    -from slack_bolt.oauth.async_oauth_flow import AsyncOAuthFlow
    -
    -
    -def to_async_bolt_request(req: Request) -> AsyncBoltRequest:
    -    return AsyncBoltRequest(
    -        body=req.body.decode("utf-8"),
    -        query=req.query_string,
    -        headers=req.headers,
    -    )
    -
    -
    -def to_sanic_response(bolt_resp: BoltResponse) -> HTTPResponse:
    -    resp = HTTPResponse(
    -        status=bolt_resp.status,
    -        body=bolt_resp.body,
    -        headers=bolt_resp.first_headers_without_set_cookie(),
    -    )
    -    for cookie in bolt_resp.cookies():
    -        for name, c in cookie.items():
    -            resp.cookies[name] = c.value
    -            expire_value = c.get("expires")
    -            if expire_value is not None and expire_value != "":
    -                expire = datetime.strptime(expire_value, "%a, %d %b %Y %H:%M:%S %Z")
    -                resp.cookies[name]["expires"] = expire
    -            resp.cookies[name]["path"] = c.get("path")
    -            resp.cookies[name]["domain"] = c.get("domain")
    -            if c.get("max-age") is not None and len(c.get("max-age")) > 0:
    -                resp.cookies[name]["max-age"] = int(c.get("max-age"))
    -            resp.cookies[name]["secure"] = True
    -            resp.cookies[name]["httponly"] = True
    -    return resp
    -
    -
    -class AsyncSlackRequestHandler:
    -    def __init__(self, app: AsyncApp):  # type: ignore
    -        self.app = app
    -
    -    async def handle(self, req: Request) -> HTTPResponse:
    -        if req.method == "GET":
    -            if self.app.oauth_flow is not None:
    -                oauth_flow: AsyncOAuthFlow = self.app.oauth_flow
    -                if req.path == oauth_flow.install_path:
    -                    bolt_resp = await oauth_flow.handle_installation(to_async_bolt_request(req))
    -                    return to_sanic_response(bolt_resp)
    -                elif req.path == oauth_flow.redirect_uri_path:
    -                    bolt_resp = await oauth_flow.handle_callback(to_async_bolt_request(req))
    -                    return to_sanic_response(bolt_resp)
    -
    -        elif req.method == "POST":
    -            bolt_resp = await self.app.async_dispatch(to_async_bolt_request(req))
    -            return to_sanic_response(bolt_resp)
    -
    -        return HTTPResponse(
    -            status=404,
    -            body="Not found",
    -        )
    -
    -
    -
    -
    -
    -
    -
    -

    Functions

    -
    -
    -def to_async_bolt_request(req:ย sanic.request.Request) โ€‘>ย AsyncBoltRequest -
    -
    -
    -
    - -Expand source code - -
    def to_async_bolt_request(req: Request) -> AsyncBoltRequest:
    -    return AsyncBoltRequest(
    -        body=req.body.decode("utf-8"),
    -        query=req.query_string,
    -        headers=req.headers,
    -    )
    -
    -
    -
    -def to_sanic_response(bolt_resp:ย BoltResponse) โ€‘>ย sanic.response.HTTPResponse -
    -
    -
    -
    - -Expand source code - -
    def to_sanic_response(bolt_resp: BoltResponse) -> HTTPResponse:
    -    resp = HTTPResponse(
    -        status=bolt_resp.status,
    -        body=bolt_resp.body,
    -        headers=bolt_resp.first_headers_without_set_cookie(),
    -    )
    -    for cookie in bolt_resp.cookies():
    -        for name, c in cookie.items():
    -            resp.cookies[name] = c.value
    -            expire_value = c.get("expires")
    -            if expire_value is not None and expire_value != "":
    -                expire = datetime.strptime(expire_value, "%a, %d %b %Y %H:%M:%S %Z")
    -                resp.cookies[name]["expires"] = expire
    -            resp.cookies[name]["path"] = c.get("path")
    -            resp.cookies[name]["domain"] = c.get("domain")
    -            if c.get("max-age") is not None and len(c.get("max-age")) > 0:
    -                resp.cookies[name]["max-age"] = int(c.get("max-age"))
    -            resp.cookies[name]["secure"] = True
    -            resp.cookies[name]["httponly"] = True
    -    return resp
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class AsyncSlackRequestHandler -(app:ย AsyncApp) -
    -
    -
    -
    - -Expand source code - -
    class AsyncSlackRequestHandler:
    -    def __init__(self, app: AsyncApp):  # type: ignore
    -        self.app = app
    -
    -    async def handle(self, req: Request) -> HTTPResponse:
    -        if req.method == "GET":
    -            if self.app.oauth_flow is not None:
    -                oauth_flow: AsyncOAuthFlow = self.app.oauth_flow
    -                if req.path == oauth_flow.install_path:
    -                    bolt_resp = await oauth_flow.handle_installation(to_async_bolt_request(req))
    -                    return to_sanic_response(bolt_resp)
    -                elif req.path == oauth_flow.redirect_uri_path:
    -                    bolt_resp = await oauth_flow.handle_callback(to_async_bolt_request(req))
    -                    return to_sanic_response(bolt_resp)
    -
    -        elif req.method == "POST":
    -            bolt_resp = await self.app.async_dispatch(to_async_bolt_request(req))
    -            return to_sanic_response(bolt_resp)
    -
    -        return HTTPResponse(
    -            status=404,
    -            body="Not found",
    -        )
    -
    -

    Methods

    -
    -
    -async def handle(self, req:ย sanic.request.Request) โ€‘>ย sanic.response.HTTPResponse -
    -
    -
    -
    - -Expand source code - -
    async def handle(self, req: Request) -> HTTPResponse:
    -    if req.method == "GET":
    -        if self.app.oauth_flow is not None:
    -            oauth_flow: AsyncOAuthFlow = self.app.oauth_flow
    -            if req.path == oauth_flow.install_path:
    -                bolt_resp = await oauth_flow.handle_installation(to_async_bolt_request(req))
    -                return to_sanic_response(bolt_resp)
    -            elif req.path == oauth_flow.redirect_uri_path:
    -                bolt_resp = await oauth_flow.handle_callback(to_async_bolt_request(req))
    -                return to_sanic_response(bolt_resp)
    -
    -    elif req.method == "POST":
    -        bolt_resp = await self.app.async_dispatch(to_async_bolt_request(req))
    -        return to_sanic_response(bolt_resp)
    -
    -    return HTTPResponse(
    -        status=404,
    -        body="Not found",
    -    )
    -
    -
    -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/adapter/sanic/index.html b/docs/api-docs/slack_bolt/adapter/sanic/index.html deleted file mode 100644 index 767ed7050..000000000 --- a/docs/api-docs/slack_bolt/adapter/sanic/index.html +++ /dev/null @@ -1,156 +0,0 @@ - - - - - - -slack_bolt.adapter.sanic API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.adapter.sanic

    -
    -
    -
    - -Expand source code - -
    from .async_handler import AsyncSlackRequestHandler
    -
    -__all__ = [
    -    "AsyncSlackRequestHandler",
    -]
    -
    -
    -
    -

    Sub-modules

    -
    -
    slack_bolt.adapter.sanic.async_handler
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class AsyncSlackRequestHandler -(app:ย AsyncApp) -
    -
    -
    -
    - -Expand source code - -
    class AsyncSlackRequestHandler:
    -    def __init__(self, app: AsyncApp):  # type: ignore
    -        self.app = app
    -
    -    async def handle(self, req: Request) -> HTTPResponse:
    -        if req.method == "GET":
    -            if self.app.oauth_flow is not None:
    -                oauth_flow: AsyncOAuthFlow = self.app.oauth_flow
    -                if req.path == oauth_flow.install_path:
    -                    bolt_resp = await oauth_flow.handle_installation(to_async_bolt_request(req))
    -                    return to_sanic_response(bolt_resp)
    -                elif req.path == oauth_flow.redirect_uri_path:
    -                    bolt_resp = await oauth_flow.handle_callback(to_async_bolt_request(req))
    -                    return to_sanic_response(bolt_resp)
    -
    -        elif req.method == "POST":
    -            bolt_resp = await self.app.async_dispatch(to_async_bolt_request(req))
    -            return to_sanic_response(bolt_resp)
    -
    -        return HTTPResponse(
    -            status=404,
    -            body="Not found",
    -        )
    -
    -

    Methods

    -
    -
    -async def handle(self, req:ย sanic.request.Request) โ€‘>ย sanic.response.HTTPResponse -
    -
    -
    -
    - -Expand source code - -
    async def handle(self, req: Request) -> HTTPResponse:
    -    if req.method == "GET":
    -        if self.app.oauth_flow is not None:
    -            oauth_flow: AsyncOAuthFlow = self.app.oauth_flow
    -            if req.path == oauth_flow.install_path:
    -                bolt_resp = await oauth_flow.handle_installation(to_async_bolt_request(req))
    -                return to_sanic_response(bolt_resp)
    -            elif req.path == oauth_flow.redirect_uri_path:
    -                bolt_resp = await oauth_flow.handle_callback(to_async_bolt_request(req))
    -                return to_sanic_response(bolt_resp)
    -
    -    elif req.method == "POST":
    -        bolt_resp = await self.app.async_dispatch(to_async_bolt_request(req))
    -        return to_sanic_response(bolt_resp)
    -
    -    return HTTPResponse(
    -        status=404,
    -        body="Not found",
    -    )
    -
    -
    -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/adapter/socket_mode/async_internals.html b/docs/api-docs/slack_bolt/adapter/socket_mode/async_internals.html deleted file mode 100644 index bd1da4a19..000000000 --- a/docs/api-docs/slack_bolt/adapter/socket_mode/async_internals.html +++ /dev/null @@ -1,164 +0,0 @@ - - - - - - -slack_bolt.adapter.socket_mode.async_internals API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.adapter.socket_mode.async_internals

    -
    -
    -

    Internal functions

    -
    - -Expand source code - -
    """Internal functions"""
    -import json
    -import logging
    -from time import time
    -
    -from slack_sdk.socket_mode.async_client import AsyncBaseSocketModeClient
    -from slack_sdk.socket_mode.request import SocketModeRequest
    -from slack_sdk.socket_mode.response import SocketModeResponse
    -
    -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):  # type: ignore
    -    bolt_req: AsyncBoltRequest = AsyncBoltRequest(mode="socket_mode", body=req.payload)
    -    bolt_resp: BoltResponse = await app.async_dispatch(bolt_req)
    -    return bolt_resp
    -
    -
    -async def send_async_response(
    -    client: AsyncBaseSocketModeClient,
    -    req: SocketModeRequest,
    -    bolt_resp: BoltResponse,
    -    start_time: float,
    -):
    -    if bolt_resp.status == 200:
    -        content_type = bolt_resp.headers.get("content-type", [""])[0]
    -        if bolt_resp.body is None or len(bolt_resp.body) == 0:
    -            await client.send_socket_mode_response(SocketModeResponse(envelope_id=req.envelope_id))
    -        elif content_type.startswith("application/json"):
    -            dict_body = json.loads(bolt_resp.body)
    -            await client.send_socket_mode_response(SocketModeResponse(envelope_id=req.envelope_id, payload=dict_body))
    -        else:
    -            await client.send_socket_mode_response(
    -                SocketModeResponse(
    -                    envelope_id=req.envelope_id,
    -                    payload={"text": bolt_resp.body},
    -                )
    -            )
    -        if client.logger.level <= logging.DEBUG:
    -            spent_time = int((time() - start_time) * 1000)
    -            client.logger.debug(f"Response time: {spent_time} milliseconds")
    -    else:
    -        client.logger.info(f"Unsuccessful Bolt execution result (status: {bolt_resp.status}, body: {bolt_resp.body})")
    -
    -
    -
    -
    -
    -
    -
    -

    Functions

    -
    -
    -async def run_async_bolt_app(app:ย AsyncApp, req:ย slack_sdk.socket_mode.request.SocketModeRequest) -
    -
    -
    -
    - -Expand source code - -
    async def run_async_bolt_app(app: AsyncApp, req: SocketModeRequest):  # type: ignore
    -    bolt_req: AsyncBoltRequest = AsyncBoltRequest(mode="socket_mode", body=req.payload)
    -    bolt_resp: BoltResponse = await app.async_dispatch(bolt_req)
    -    return bolt_resp
    -
    -
    -
    -async def send_async_response(client:ย slack_sdk.socket_mode.async_client.AsyncBaseSocketModeClient, req:ย slack_sdk.socket_mode.request.SocketModeRequest, bolt_resp:ย BoltResponse, start_time:ย float) -
    -
    -
    -
    - -Expand source code - -
    async def send_async_response(
    -    client: AsyncBaseSocketModeClient,
    -    req: SocketModeRequest,
    -    bolt_resp: BoltResponse,
    -    start_time: float,
    -):
    -    if bolt_resp.status == 200:
    -        content_type = bolt_resp.headers.get("content-type", [""])[0]
    -        if bolt_resp.body is None or len(bolt_resp.body) == 0:
    -            await client.send_socket_mode_response(SocketModeResponse(envelope_id=req.envelope_id))
    -        elif content_type.startswith("application/json"):
    -            dict_body = json.loads(bolt_resp.body)
    -            await client.send_socket_mode_response(SocketModeResponse(envelope_id=req.envelope_id, payload=dict_body))
    -        else:
    -            await client.send_socket_mode_response(
    -                SocketModeResponse(
    -                    envelope_id=req.envelope_id,
    -                    payload={"text": bolt_resp.body},
    -                )
    -            )
    -        if client.logger.level <= logging.DEBUG:
    -            spent_time = int((time() - start_time) * 1000)
    -            client.logger.debug(f"Response time: {spent_time} milliseconds")
    -    else:
    -        client.logger.info(f"Unsuccessful Bolt execution result (status: {bolt_resp.status}, body: {bolt_resp.body})")
    -
    -
    -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/adapter/socket_mode/builtin/index.html b/docs/api-docs/slack_bolt/adapter/socket_mode/builtin/index.html deleted file mode 100644 index 5ca55c520..000000000 --- a/docs/api-docs/slack_bolt/adapter/socket_mode/builtin/index.html +++ /dev/null @@ -1,282 +0,0 @@ - - - - - - -slack_bolt.adapter.socket_mode.builtin API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.adapter.socket_mode.builtin

    -
    -
    -

    The built-in implementation, which does not have any external dependencies

    -
    - -Expand source code - -
    """The built-in implementation, which does not have any external dependencies"""
    -import os
    -from logging import Logger
    -from time import time
    -from typing import Optional, Dict
    -
    -from slack_sdk import WebClient
    -from slack_sdk.socket_mode.request import SocketModeRequest
    -from slack_sdk.socket_mode.builtin import SocketModeClient
    -
    -from slack_bolt import App
    -from slack_bolt.adapter.socket_mode.base_handler import BaseSocketModeHandler
    -from slack_bolt.adapter.socket_mode.internals import run_bolt_app, send_response
    -from slack_bolt.response import BoltResponse
    -
    -
    -class SocketModeHandler(BaseSocketModeHandler):
    -    app: App  # type: ignore
    -    app_token: str
    -    client: SocketModeClient
    -
    -    def __init__(  # type: ignore
    -        self,
    -        app: App,  # type: ignore
    -        app_token: Optional[str] = None,
    -        logger: Optional[Logger] = None,
    -        web_client: Optional[WebClient] = None,
    -        proxy: Optional[str] = None,
    -        proxy_headers: Optional[Dict[str, str]] = None,
    -        auto_reconnect_enabled: bool = True,
    -        trace_enabled: bool = False,
    -        all_message_trace_enabled: bool = False,
    -        ping_pong_trace_enabled: bool = False,
    -        ping_interval: float = 10,
    -        receive_buffer_size: int = 1024,
    -        concurrency: int = 10,
    -    ):
    -        """Socket Mode adapter for Bolt apps
    -
    -        Args:
    -            app: The Bolt app
    -            app_token: App-level token starting with `xapp-`
    -            logger: Custom logger
    -            web_client: custom `slack_sdk.web.WebClient` instance
    -            proxy: HTTP proxy URL
    -            proxy_headers: Additional request header for proxy connections
    -            auto_reconnect_enabled: True if the auto-reconnect logic works
    -            trace_enabled: True if trace-level logging is enabled
    -            all_message_trace_enabled: True if trace-logging for all received WebSocket messages is enabled
    -            ping_pong_trace_enabled: True if trace-logging for all ping-pong communications
    -            ping_interval: The ping-pong internal (seconds)
    -            receive_buffer_size: The data length for a single socket recv operation
    -            concurrency: The size of the underlying thread pool
    -        """
    -        self.app = app
    -        self.app_token = app_token or os.environ["SLACK_APP_TOKEN"]
    -        self.client = SocketModeClient(
    -            app_token=self.app_token,
    -            logger=logger if logger is not None else app.logger,
    -            web_client=web_client if web_client is not None else app.client,
    -            proxy=proxy if proxy is not None else app.client.proxy,
    -            proxy_headers=proxy_headers,
    -            auto_reconnect_enabled=auto_reconnect_enabled,
    -            trace_enabled=trace_enabled,
    -            all_message_trace_enabled=all_message_trace_enabled,
    -            ping_pong_trace_enabled=ping_pong_trace_enabled,
    -            ping_interval=ping_interval,
    -            receive_buffer_size=receive_buffer_size,
    -            concurrency=concurrency,
    -        )
    -        self.client.socket_mode_request_listeners.append(self.handle)
    -
    -    def handle(self, client: SocketModeClient, req: SocketModeRequest) -> None:
    -        start = time()
    -        bolt_resp: BoltResponse = run_bolt_app(self.app, req)
    -        send_response(client, req, bolt_resp, start)
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class SocketModeHandler -(app:ย App, app_token:ย Optional[str]ย =ย None, logger:ย Optional[logging.Logger]ย =ย None, web_client:ย Optional[slack_sdk.web.client.WebClient]ย =ย None, proxy:ย Optional[str]ย =ย None, proxy_headers:ย Optional[Dict[str,ย str]]ย =ย None, auto_reconnect_enabled:ย boolย =ย True, trace_enabled:ย boolย =ย False, all_message_trace_enabled:ย boolย =ย False, ping_pong_trace_enabled:ย boolย =ย False, ping_interval:ย floatย =ย 10, receive_buffer_size:ย intย =ย 1024, concurrency:ย intย =ย 10) -
    -
    -

    Socket Mode adapter for Bolt apps

    -

    Args

    -
    -
    app
    -
    The Bolt app
    -
    app_token
    -
    App-level token starting with xapp-
    -
    logger
    -
    Custom logger
    -
    web_client
    -
    custom slack_sdk.web.WebClient instance
    -
    proxy
    -
    HTTP proxy URL
    -
    proxy_headers
    -
    Additional request header for proxy connections
    -
    auto_reconnect_enabled
    -
    True if the auto-reconnect logic works
    -
    trace_enabled
    -
    True if trace-level logging is enabled
    -
    all_message_trace_enabled
    -
    True if trace-logging for all received WebSocket messages is enabled
    -
    ping_pong_trace_enabled
    -
    True if trace-logging for all ping-pong communications
    -
    ping_interval
    -
    The ping-pong internal (seconds)
    -
    receive_buffer_size
    -
    The data length for a single socket recv operation
    -
    concurrency
    -
    The size of the underlying thread pool
    -
    -
    - -Expand source code - -
    class SocketModeHandler(BaseSocketModeHandler):
    -    app: App  # type: ignore
    -    app_token: str
    -    client: SocketModeClient
    -
    -    def __init__(  # type: ignore
    -        self,
    -        app: App,  # type: ignore
    -        app_token: Optional[str] = None,
    -        logger: Optional[Logger] = None,
    -        web_client: Optional[WebClient] = None,
    -        proxy: Optional[str] = None,
    -        proxy_headers: Optional[Dict[str, str]] = None,
    -        auto_reconnect_enabled: bool = True,
    -        trace_enabled: bool = False,
    -        all_message_trace_enabled: bool = False,
    -        ping_pong_trace_enabled: bool = False,
    -        ping_interval: float = 10,
    -        receive_buffer_size: int = 1024,
    -        concurrency: int = 10,
    -    ):
    -        """Socket Mode adapter for Bolt apps
    -
    -        Args:
    -            app: The Bolt app
    -            app_token: App-level token starting with `xapp-`
    -            logger: Custom logger
    -            web_client: custom `slack_sdk.web.WebClient` instance
    -            proxy: HTTP proxy URL
    -            proxy_headers: Additional request header for proxy connections
    -            auto_reconnect_enabled: True if the auto-reconnect logic works
    -            trace_enabled: True if trace-level logging is enabled
    -            all_message_trace_enabled: True if trace-logging for all received WebSocket messages is enabled
    -            ping_pong_trace_enabled: True if trace-logging for all ping-pong communications
    -            ping_interval: The ping-pong internal (seconds)
    -            receive_buffer_size: The data length for a single socket recv operation
    -            concurrency: The size of the underlying thread pool
    -        """
    -        self.app = app
    -        self.app_token = app_token or os.environ["SLACK_APP_TOKEN"]
    -        self.client = SocketModeClient(
    -            app_token=self.app_token,
    -            logger=logger if logger is not None else app.logger,
    -            web_client=web_client if web_client is not None else app.client,
    -            proxy=proxy if proxy is not None else app.client.proxy,
    -            proxy_headers=proxy_headers,
    -            auto_reconnect_enabled=auto_reconnect_enabled,
    -            trace_enabled=trace_enabled,
    -            all_message_trace_enabled=all_message_trace_enabled,
    -            ping_pong_trace_enabled=ping_pong_trace_enabled,
    -            ping_interval=ping_interval,
    -            receive_buffer_size=receive_buffer_size,
    -            concurrency=concurrency,
    -        )
    -        self.client.socket_mode_request_listeners.append(self.handle)
    -
    -    def handle(self, client: SocketModeClient, req: SocketModeRequest) -> None:
    -        start = time()
    -        bolt_resp: BoltResponse = run_bolt_app(self.app, req)
    -        send_response(client, req, bolt_resp, start)
    -
    -

    Ancestors

    - -

    Class variables

    -
    -
    var app :ย App
    -
    -
    -
    -
    var app_token :ย str
    -
    -
    -
    -
    var client :ย slack_sdk.socket_mode.builtin.client.SocketModeClient
    -
    -
    -
    -
    -

    Inherited members

    - -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/adapter/socket_mode/internals.html b/docs/api-docs/slack_bolt/adapter/socket_mode/internals.html deleted file mode 100644 index dcd5c6fe3..000000000 --- a/docs/api-docs/slack_bolt/adapter/socket_mode/internals.html +++ /dev/null @@ -1,160 +0,0 @@ - - - - - - -slack_bolt.adapter.socket_mode.internals API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.adapter.socket_mode.internals

    -
    -
    -

    Internal functions

    -
    - -Expand source code - -
    """Internal functions"""
    -import json
    -import logging
    -from time import time
    -
    -from slack_sdk.socket_mode.client import BaseSocketModeClient
    -from slack_sdk.socket_mode.request import SocketModeRequest
    -from slack_sdk.socket_mode.response import SocketModeResponse
    -
    -from slack_bolt.app import App
    -from slack_bolt.request import BoltRequest
    -from slack_bolt.response import BoltResponse
    -
    -
    -def run_bolt_app(app: App, req: SocketModeRequest):  # type: ignore
    -    bolt_req: BoltRequest = BoltRequest(mode="socket_mode", body=req.payload)
    -    bolt_resp: BoltResponse = app.dispatch(bolt_req)
    -    return bolt_resp
    -
    -
    -def send_response(
    -    client: BaseSocketModeClient,
    -    req: SocketModeRequest,
    -    bolt_resp: BoltResponse,
    -    start_time: float,
    -):
    -    if bolt_resp.status == 200:
    -        content_type = bolt_resp.headers.get("content-type", [""])[0]
    -        if bolt_resp.body is None or len(bolt_resp.body) == 0:
    -            client.send_socket_mode_response(SocketModeResponse(envelope_id=req.envelope_id))
    -        elif content_type.startswith("application/json"):
    -            dict_body = json.loads(bolt_resp.body)
    -            client.send_socket_mode_response(SocketModeResponse(envelope_id=req.envelope_id, payload=dict_body))
    -        else:
    -            client.send_socket_mode_response(
    -                SocketModeResponse(envelope_id=req.envelope_id, payload={"text": bolt_resp.body})
    -            )
    -
    -        if client.logger.level <= logging.DEBUG:
    -            spent_time = int((time() - start_time) * 1000)
    -            client.logger.debug(f"Response time: {spent_time} milliseconds")
    -    else:
    -        client.logger.info(f"Unsuccessful Bolt execution result (status: {bolt_resp.status}, body: {bolt_resp.body})")
    -
    -
    -
    -
    -
    -
    -
    -

    Functions

    -
    -
    -def run_bolt_app(app:ย App, req:ย slack_sdk.socket_mode.request.SocketModeRequest) -
    -
    -
    -
    - -Expand source code - -
    def run_bolt_app(app: App, req: SocketModeRequest):  # type: ignore
    -    bolt_req: BoltRequest = BoltRequest(mode="socket_mode", body=req.payload)
    -    bolt_resp: BoltResponse = app.dispatch(bolt_req)
    -    return bolt_resp
    -
    -
    -
    -def send_response(client:ย slack_sdk.socket_mode.client.BaseSocketModeClient, req:ย slack_sdk.socket_mode.request.SocketModeRequest, bolt_resp:ย BoltResponse, start_time:ย float) -
    -
    -
    -
    - -Expand source code - -
    def send_response(
    -    client: BaseSocketModeClient,
    -    req: SocketModeRequest,
    -    bolt_resp: BoltResponse,
    -    start_time: float,
    -):
    -    if bolt_resp.status == 200:
    -        content_type = bolt_resp.headers.get("content-type", [""])[0]
    -        if bolt_resp.body is None or len(bolt_resp.body) == 0:
    -            client.send_socket_mode_response(SocketModeResponse(envelope_id=req.envelope_id))
    -        elif content_type.startswith("application/json"):
    -            dict_body = json.loads(bolt_resp.body)
    -            client.send_socket_mode_response(SocketModeResponse(envelope_id=req.envelope_id, payload=dict_body))
    -        else:
    -            client.send_socket_mode_response(
    -                SocketModeResponse(envelope_id=req.envelope_id, payload={"text": bolt_resp.body})
    -            )
    -
    -        if client.logger.level <= logging.DEBUG:
    -            spent_time = int((time() - start_time) * 1000)
    -            client.logger.debug(f"Response time: {spent_time} milliseconds")
    -    else:
    -        client.logger.info(f"Unsuccessful Bolt execution result (status: {bolt_resp.status}, body: {bolt_resp.body})")
    -
    -
    -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/adapter/socket_mode/websocket_client/index.html b/docs/api-docs/slack_bolt/adapter/socket_mode/websocket_client/index.html deleted file mode 100644 index 049a23153..000000000 --- a/docs/api-docs/slack_bolt/adapter/socket_mode/websocket_client/index.html +++ /dev/null @@ -1,266 +0,0 @@ - - - - - - -slack_bolt.adapter.socket_mode.websocket_client API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.adapter.socket_mode.websocket_client

    -
    -
    -

    websocket-client based implementation

    -
    - -Expand source code - -
    """[`websocket-client`](https://pypi.org/project/websocket-client/) based implementation"""
    -import os
    -from logging import Logger
    -from time import time
    -from typing import Optional, Tuple
    -
    -from slack_sdk import WebClient
    -from slack_sdk.socket_mode.request import SocketModeRequest
    -from slack_sdk.socket_mode.websocket_client import SocketModeClient
    -
    -from slack_bolt import App
    -from slack_bolt.adapter.socket_mode.base_handler import BaseSocketModeHandler
    -from slack_bolt.adapter.socket_mode.internals import run_bolt_app, send_response
    -from slack_bolt.response import BoltResponse
    -
    -
    -class SocketModeHandler(BaseSocketModeHandler):
    -    app: App  # type: ignore
    -    app_token: str
    -    client: SocketModeClient
    -
    -    def __init__(  # type: ignore
    -        self,
    -        app: App,  # type: ignore
    -        app_token: Optional[str] = None,
    -        logger: Optional[Logger] = None,
    -        web_client: Optional[WebClient] = None,
    -        ping_interval: float = 10,
    -        concurrency: int = 10,
    -        http_proxy_host: Optional[str] = None,
    -        http_proxy_port: Optional[int] = None,
    -        http_proxy_auth: Optional[Tuple[str, str]] = None,
    -        proxy_type: Optional[str] = None,
    -        trace_enabled: bool = False,
    -    ):
    -        """Socket Mode adapter for Bolt apps
    -
    -        Args:
    -            app: The Bolt app
    -            app_token: App-level token starting with `xapp-`
    -            logger: Custom logger
    -            web_client: custom `slack_sdk.web.WebClient` instance
    -            ping_interval: The ping-pong internal (seconds)
    -            concurrency: The size of the underlying thread pool
    -            http_proxy_host: HTTP proxy host
    -            http_proxy_port: HTTP proxy port
    -            http_proxy_auth: HTTP proxy authentication (username, password)
    -            proxy_type: Proxy type
    -            trace_enabled: True if trace-level logging is enabled
    -        """
    -        self.app = app
    -        self.app_token = app_token or os.environ["SLACK_APP_TOKEN"]
    -        self.client = SocketModeClient(
    -            app_token=self.app_token,
    -            logger=logger if logger is not None else app.logger,
    -            web_client=web_client if web_client is not None else app.client,
    -            ping_interval=ping_interval,
    -            concurrency=concurrency,
    -            http_proxy_host=http_proxy_host,
    -            http_proxy_port=http_proxy_port,
    -            http_proxy_auth=http_proxy_auth,
    -            proxy_type=proxy_type,
    -            trace_enabled=trace_enabled,
    -        )
    -        self.client.socket_mode_request_listeners.append(self.handle)
    -
    -    def handle(self, client: SocketModeClient, req: SocketModeRequest) -> None:
    -        start = time()
    -        bolt_resp: BoltResponse = run_bolt_app(self.app, req)
    -        send_response(client, req, bolt_resp, start)
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class SocketModeHandler -(app:ย App, app_token:ย Optional[str]ย =ย None, logger:ย Optional[logging.Logger]ย =ย None, web_client:ย Optional[slack_sdk.web.client.WebClient]ย =ย None, ping_interval:ย floatย =ย 10, concurrency:ย intย =ย 10, http_proxy_host:ย Optional[str]ย =ย None, http_proxy_port:ย Optional[int]ย =ย None, http_proxy_auth:ย Optional[Tuple[str,ย str]]ย =ย None, proxy_type:ย Optional[str]ย =ย None, trace_enabled:ย boolย =ย False) -
    -
    -

    Socket Mode adapter for Bolt apps

    -

    Args

    -
    -
    app
    -
    The Bolt app
    -
    app_token
    -
    App-level token starting with xapp-
    -
    logger
    -
    Custom logger
    -
    web_client
    -
    custom slack_sdk.web.WebClient instance
    -
    ping_interval
    -
    The ping-pong internal (seconds)
    -
    concurrency
    -
    The size of the underlying thread pool
    -
    http_proxy_host
    -
    HTTP proxy host
    -
    http_proxy_port
    -
    HTTP proxy port
    -
    http_proxy_auth
    -
    HTTP proxy authentication (username, password)
    -
    proxy_type
    -
    Proxy type
    -
    trace_enabled
    -
    True if trace-level logging is enabled
    -
    -
    - -Expand source code - -
    class SocketModeHandler(BaseSocketModeHandler):
    -    app: App  # type: ignore
    -    app_token: str
    -    client: SocketModeClient
    -
    -    def __init__(  # type: ignore
    -        self,
    -        app: App,  # type: ignore
    -        app_token: Optional[str] = None,
    -        logger: Optional[Logger] = None,
    -        web_client: Optional[WebClient] = None,
    -        ping_interval: float = 10,
    -        concurrency: int = 10,
    -        http_proxy_host: Optional[str] = None,
    -        http_proxy_port: Optional[int] = None,
    -        http_proxy_auth: Optional[Tuple[str, str]] = None,
    -        proxy_type: Optional[str] = None,
    -        trace_enabled: bool = False,
    -    ):
    -        """Socket Mode adapter for Bolt apps
    -
    -        Args:
    -            app: The Bolt app
    -            app_token: App-level token starting with `xapp-`
    -            logger: Custom logger
    -            web_client: custom `slack_sdk.web.WebClient` instance
    -            ping_interval: The ping-pong internal (seconds)
    -            concurrency: The size of the underlying thread pool
    -            http_proxy_host: HTTP proxy host
    -            http_proxy_port: HTTP proxy port
    -            http_proxy_auth: HTTP proxy authentication (username, password)
    -            proxy_type: Proxy type
    -            trace_enabled: True if trace-level logging is enabled
    -        """
    -        self.app = app
    -        self.app_token = app_token or os.environ["SLACK_APP_TOKEN"]
    -        self.client = SocketModeClient(
    -            app_token=self.app_token,
    -            logger=logger if logger is not None else app.logger,
    -            web_client=web_client if web_client is not None else app.client,
    -            ping_interval=ping_interval,
    -            concurrency=concurrency,
    -            http_proxy_host=http_proxy_host,
    -            http_proxy_port=http_proxy_port,
    -            http_proxy_auth=http_proxy_auth,
    -            proxy_type=proxy_type,
    -            trace_enabled=trace_enabled,
    -        )
    -        self.client.socket_mode_request_listeners.append(self.handle)
    -
    -    def handle(self, client: SocketModeClient, req: SocketModeRequest) -> None:
    -        start = time()
    -        bolt_resp: BoltResponse = run_bolt_app(self.app, req)
    -        send_response(client, req, bolt_resp, start)
    -
    -

    Ancestors

    - -

    Class variables

    -
    -
    var app :ย App
    -
    -
    -
    -
    var app_token :ย str
    -
    -
    -
    -
    var client :ย slack_sdk.socket_mode.websocket_client.SocketModeClient
    -
    -
    -
    -
    -

    Inherited members

    - -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/authorization/authorize_result.html b/docs/api-docs/slack_bolt/authorization/authorize_result.html deleted file mode 100644 index 8cef1d5c1..000000000 --- a/docs/api-docs/slack_bolt/authorization/authorize_result.html +++ /dev/null @@ -1,444 +0,0 @@ - - - - - - -slack_bolt.authorization.authorize_result API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.authorization.authorize_result

    -
    -
    -
    - -Expand source code - -
    from typing import Optional, List, Union
    -
    -from slack_sdk.web import SlackResponse
    -
    -
    -class AuthorizeResult(dict):
    -    """Authorize function call result"""
    -
    -    enterprise_id: Optional[str]
    -    team_id: Optional[str]
    -    team: Optional[str]  # since v1.18
    -    url: Optional[str]  # since v1.18
    -
    -    bot_id: Optional[str]
    -    bot_user_id: Optional[str]
    -    bot_token: Optional[str]
    -    bot_scopes: Optional[List[str]]  # since v1.17
    -
    -    user_id: Optional[str]
    -    user: Optional[str]  # since v1.18
    -    user_token: Optional[str]
    -    user_scopes: Optional[List[str]]  # since v1.17
    -
    -    def __init__(
    -        self,
    -        *,
    -        enterprise_id: Optional[str],
    -        team_id: Optional[str],
    -        team: Optional[str] = None,
    -        url: Optional[str] = None,
    -        # bot
    -        bot_user_id: Optional[str] = None,
    -        bot_id: Optional[str] = None,
    -        bot_token: Optional[str] = None,
    -        bot_scopes: Optional[Union[List[str], str]] = None,
    -        # user
    -        user_id: Optional[str] = None,
    -        user: Optional[str] = None,
    -        user_token: Optional[str] = None,
    -        user_scopes: Optional[Union[List[str], str]] = None,
    -    ):
    -        """
    -        Args:
    -            enterprise_id: Organization ID (Enterprise Grid) starting with `E`
    -            team_id: Workspace ID starting with `T`
    -            team: Workspace name
    -            url: Workspace slack.com URL
    -            bot_user_id: Bot user's User ID starting with either `U` or `W`
    -            bot_id: Bot ID starting with `B`
    -            bot_token: Bot user access token starting with `xoxb-`
    -            bot_scopes: The scopes associated with the bot token
    -            user_id: The request user ID
    -            user: The request user's name
    -            user_token: User access token starting with `xoxp-`
    -            user_scopes: The scopes associated wth the user token
    -        """
    -        self["enterprise_id"] = self.enterprise_id = enterprise_id
    -        self["team_id"] = self.team_id = team_id
    -        self["team"] = self.team = team
    -        self["url"] = self.url = url
    -        # bot
    -        self["bot_user_id"] = self.bot_user_id = bot_user_id
    -        self["bot_id"] = self.bot_id = bot_id
    -        self["bot_token"] = self.bot_token = bot_token
    -        if bot_scopes is not None and isinstance(bot_scopes, str):
    -            bot_scopes = [scope.strip() for scope in bot_scopes.split(",")]
    -        self["bot_scopes"] = self.bot_scopes = bot_scopes  # type: ignore
    -        # user
    -        self["user_id"] = self.user_id = user_id
    -        self["user"] = self.user = user
    -        self["user_token"] = self.user_token = user_token
    -        if user_scopes is not None and isinstance(user_scopes, str):
    -            user_scopes = [scope.strip() for scope in user_scopes.split(",")]
    -        self["user_scopes"] = self.user_scopes = user_scopes  # type: ignore
    -
    -    @classmethod
    -    def from_auth_test_response(
    -        cls,
    -        *,
    -        bot_token: Optional[str] = None,
    -        user_token: Optional[str] = None,
    -        bot_scopes: Optional[Union[List[str], str]] = None,
    -        user_scopes: Optional[Union[List[str], str]] = None,
    -        auth_test_response: SlackResponse,
    -        user_auth_test_response: Optional[SlackResponse] = None,
    -    ) -> "AuthorizeResult":
    -        bot_user_id: Optional[str] = (  # type:ignore
    -            auth_test_response.get("user_id") if auth_test_response.get("bot_id") is not None else None
    -        )
    -        user_id: Optional[str] = (  # type:ignore
    -            auth_test_response.get("user_id") if auth_test_response.get("bot_id") is None else None
    -        )
    -        user_name = auth_test_response.get("user")
    -        if user_id is None and user_auth_test_response is not None:
    -            user_id: Optional[str] = user_auth_test_response.get("user_id")  # type:ignore
    -            user_name: Optional[str] = user_auth_test_response.get("user")  # type:ignore
    -
    -        return AuthorizeResult(
    -            enterprise_id=auth_test_response.get("enterprise_id"),
    -            team_id=auth_test_response.get("team_id"),
    -            team=auth_test_response.get("team"),
    -            url=auth_test_response.get("url"),
    -            bot_id=auth_test_response.get("bot_id"),
    -            bot_user_id=bot_user_id,
    -            bot_scopes=bot_scopes,
    -            user_id=user_id,
    -            user=user_name,
    -            bot_token=bot_token,
    -            user_token=user_token,
    -            user_scopes=user_scopes,
    -        )
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class AuthorizeResult -(*, enterprise_id:ย Optional[str], team_id:ย Optional[str], team:ย Optional[str]ย =ย None, url:ย Optional[str]ย =ย None, bot_user_id:ย Optional[str]ย =ย None, bot_id:ย Optional[str]ย =ย None, bot_token:ย Optional[str]ย =ย None, bot_scopes:ย Union[List[str],ย str,ย ForwardRef(None)]ย =ย None, user_id:ย Optional[str]ย =ย None, user:ย Optional[str]ย =ย None, user_token:ย Optional[str]ย =ย None, user_scopes:ย Union[List[str],ย str,ย ForwardRef(None)]ย =ย None) -
    -
    -

    Authorize function call result

    -

    Args

    -
    -
    enterprise_id
    -
    Organization ID (Enterprise Grid) starting with E
    -
    team_id
    -
    Workspace ID starting with T
    -
    team
    -
    Workspace name
    -
    url
    -
    Workspace slack.com URL
    -
    bot_user_id
    -
    Bot user's User ID starting with either U or W
    -
    bot_id
    -
    Bot ID starting with B
    -
    bot_token
    -
    Bot user access token starting with xoxb-
    -
    bot_scopes
    -
    The scopes associated with the bot token
    -
    user_id
    -
    The request user ID
    -
    user
    -
    The request user's name
    -
    user_token
    -
    User access token starting with xoxp-
    -
    user_scopes
    -
    The scopes associated wth the user token
    -
    -
    - -Expand source code - -
    class AuthorizeResult(dict):
    -    """Authorize function call result"""
    -
    -    enterprise_id: Optional[str]
    -    team_id: Optional[str]
    -    team: Optional[str]  # since v1.18
    -    url: Optional[str]  # since v1.18
    -
    -    bot_id: Optional[str]
    -    bot_user_id: Optional[str]
    -    bot_token: Optional[str]
    -    bot_scopes: Optional[List[str]]  # since v1.17
    -
    -    user_id: Optional[str]
    -    user: Optional[str]  # since v1.18
    -    user_token: Optional[str]
    -    user_scopes: Optional[List[str]]  # since v1.17
    -
    -    def __init__(
    -        self,
    -        *,
    -        enterprise_id: Optional[str],
    -        team_id: Optional[str],
    -        team: Optional[str] = None,
    -        url: Optional[str] = None,
    -        # bot
    -        bot_user_id: Optional[str] = None,
    -        bot_id: Optional[str] = None,
    -        bot_token: Optional[str] = None,
    -        bot_scopes: Optional[Union[List[str], str]] = None,
    -        # user
    -        user_id: Optional[str] = None,
    -        user: Optional[str] = None,
    -        user_token: Optional[str] = None,
    -        user_scopes: Optional[Union[List[str], str]] = None,
    -    ):
    -        """
    -        Args:
    -            enterprise_id: Organization ID (Enterprise Grid) starting with `E`
    -            team_id: Workspace ID starting with `T`
    -            team: Workspace name
    -            url: Workspace slack.com URL
    -            bot_user_id: Bot user's User ID starting with either `U` or `W`
    -            bot_id: Bot ID starting with `B`
    -            bot_token: Bot user access token starting with `xoxb-`
    -            bot_scopes: The scopes associated with the bot token
    -            user_id: The request user ID
    -            user: The request user's name
    -            user_token: User access token starting with `xoxp-`
    -            user_scopes: The scopes associated wth the user token
    -        """
    -        self["enterprise_id"] = self.enterprise_id = enterprise_id
    -        self["team_id"] = self.team_id = team_id
    -        self["team"] = self.team = team
    -        self["url"] = self.url = url
    -        # bot
    -        self["bot_user_id"] = self.bot_user_id = bot_user_id
    -        self["bot_id"] = self.bot_id = bot_id
    -        self["bot_token"] = self.bot_token = bot_token
    -        if bot_scopes is not None and isinstance(bot_scopes, str):
    -            bot_scopes = [scope.strip() for scope in bot_scopes.split(",")]
    -        self["bot_scopes"] = self.bot_scopes = bot_scopes  # type: ignore
    -        # user
    -        self["user_id"] = self.user_id = user_id
    -        self["user"] = self.user = user
    -        self["user_token"] = self.user_token = user_token
    -        if user_scopes is not None and isinstance(user_scopes, str):
    -            user_scopes = [scope.strip() for scope in user_scopes.split(",")]
    -        self["user_scopes"] = self.user_scopes = user_scopes  # type: ignore
    -
    -    @classmethod
    -    def from_auth_test_response(
    -        cls,
    -        *,
    -        bot_token: Optional[str] = None,
    -        user_token: Optional[str] = None,
    -        bot_scopes: Optional[Union[List[str], str]] = None,
    -        user_scopes: Optional[Union[List[str], str]] = None,
    -        auth_test_response: SlackResponse,
    -        user_auth_test_response: Optional[SlackResponse] = None,
    -    ) -> "AuthorizeResult":
    -        bot_user_id: Optional[str] = (  # type:ignore
    -            auth_test_response.get("user_id") if auth_test_response.get("bot_id") is not None else None
    -        )
    -        user_id: Optional[str] = (  # type:ignore
    -            auth_test_response.get("user_id") if auth_test_response.get("bot_id") is None else None
    -        )
    -        user_name = auth_test_response.get("user")
    -        if user_id is None and user_auth_test_response is not None:
    -            user_id: Optional[str] = user_auth_test_response.get("user_id")  # type:ignore
    -            user_name: Optional[str] = user_auth_test_response.get("user")  # type:ignore
    -
    -        return AuthorizeResult(
    -            enterprise_id=auth_test_response.get("enterprise_id"),
    -            team_id=auth_test_response.get("team_id"),
    -            team=auth_test_response.get("team"),
    -            url=auth_test_response.get("url"),
    -            bot_id=auth_test_response.get("bot_id"),
    -            bot_user_id=bot_user_id,
    -            bot_scopes=bot_scopes,
    -            user_id=user_id,
    -            user=user_name,
    -            bot_token=bot_token,
    -            user_token=user_token,
    -            user_scopes=user_scopes,
    -        )
    -
    -

    Ancestors

    -
      -
    • builtins.dict
    • -
    -

    Class variables

    -
    -
    var bot_id :ย Optional[str]
    -
    -
    -
    -
    var bot_scopes :ย Optional[List[str]]
    -
    -
    -
    -
    var bot_token :ย Optional[str]
    -
    -
    -
    -
    var bot_user_id :ย Optional[str]
    -
    -
    -
    -
    var enterprise_id :ย Optional[str]
    -
    -
    -
    -
    var team :ย Optional[str]
    -
    -
    -
    -
    var team_id :ย Optional[str]
    -
    -
    -
    -
    var url :ย Optional[str]
    -
    -
    -
    -
    var user :ย Optional[str]
    -
    -
    -
    -
    var user_id :ย Optional[str]
    -
    -
    -
    -
    var user_scopes :ย Optional[List[str]]
    -
    -
    -
    -
    var user_token :ย Optional[str]
    -
    -
    -
    -
    -

    Static methods

    -
    -
    -def from_auth_test_response(*, bot_token:ย Optional[str]ย =ย None, user_token:ย Optional[str]ย =ย None, bot_scopes:ย Union[List[str],ย str,ย ForwardRef(None)]ย =ย None, user_scopes:ย Union[List[str],ย str,ย ForwardRef(None)]ย =ย None, auth_test_response:ย slack_sdk.web.slack_response.SlackResponse, user_auth_test_response:ย Optional[slack_sdk.web.slack_response.SlackResponse]ย =ย None) โ€‘>ย AuthorizeResult -
    -
    -
    -
    - -Expand source code - -
    @classmethod
    -def from_auth_test_response(
    -    cls,
    -    *,
    -    bot_token: Optional[str] = None,
    -    user_token: Optional[str] = None,
    -    bot_scopes: Optional[Union[List[str], str]] = None,
    -    user_scopes: Optional[Union[List[str], str]] = None,
    -    auth_test_response: SlackResponse,
    -    user_auth_test_response: Optional[SlackResponse] = None,
    -) -> "AuthorizeResult":
    -    bot_user_id: Optional[str] = (  # type:ignore
    -        auth_test_response.get("user_id") if auth_test_response.get("bot_id") is not None else None
    -    )
    -    user_id: Optional[str] = (  # type:ignore
    -        auth_test_response.get("user_id") if auth_test_response.get("bot_id") is None else None
    -    )
    -    user_name = auth_test_response.get("user")
    -    if user_id is None and user_auth_test_response is not None:
    -        user_id: Optional[str] = user_auth_test_response.get("user_id")  # type:ignore
    -        user_name: Optional[str] = user_auth_test_response.get("user")  # type:ignore
    -
    -    return AuthorizeResult(
    -        enterprise_id=auth_test_response.get("enterprise_id"),
    -        team_id=auth_test_response.get("team_id"),
    -        team=auth_test_response.get("team"),
    -        url=auth_test_response.get("url"),
    -        bot_id=auth_test_response.get("bot_id"),
    -        bot_user_id=bot_user_id,
    -        bot_scopes=bot_scopes,
    -        user_id=user_id,
    -        user=user_name,
    -        bot_token=bot_token,
    -        user_token=user_token,
    -        user_scopes=user_scopes,
    -    )
    -
    -
    -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/context/ack/ack.html b/docs/api-docs/slack_bolt/context/ack/ack.html deleted file mode 100644 index b0987d0b3..000000000 --- a/docs/api-docs/slack_bolt/context/ack/ack.html +++ /dev/null @@ -1,171 +0,0 @@ - - - - - - -slack_bolt.context.ack.ack API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.context.ack.ack

    -
    -
    -
    - -Expand source code - -
    from typing import Optional, Union, Dict, Sequence
    -
    -from slack_sdk.models.attachments import Attachment
    -from slack_sdk.models.blocks import Block, Option, OptionGroup
    -from slack_sdk.models.views import View
    -
    -from slack_bolt.context.ack.internals import _set_response
    -from slack_bolt.response.response import BoltResponse
    -
    -
    -class Ack:
    -    response: Optional[BoltResponse]
    -
    -    def __init__(self):
    -        self.response: Optional[BoltResponse] = None
    -
    -    def __call__(
    -        self,
    -        text: Union[str, dict] = "",  # text: str or whole_response: dict
    -        blocks: Optional[Sequence[Union[dict, Block]]] = None,
    -        attachments: Optional[Sequence[Union[dict, Attachment]]] = None,
    -        unfurl_links: Optional[bool] = None,
    -        unfurl_media: Optional[bool] = None,
    -        response_type: Optional[str] = None,  # in_channel / ephemeral
    -        # block_suggestion / dialog_suggestion
    -        options: Optional[Sequence[Union[dict, Option]]] = None,
    -        option_groups: Optional[Sequence[Union[dict, OptionGroup]]] = None,
    -        # view_submission
    -        response_action: Optional[str] = None,  # errors / update / push / clear
    -        errors: Optional[Dict[str, str]] = None,
    -        view: Optional[Union[dict, View]] = None,
    -    ) -> BoltResponse:
    -        return _set_response(
    -            self,
    -            text_or_whole_response=text,
    -            blocks=blocks,
    -            attachments=attachments,
    -            unfurl_links=unfurl_links,
    -            unfurl_media=unfurl_media,
    -            response_type=response_type,
    -            options=options,
    -            option_groups=option_groups,
    -            response_action=response_action,
    -            errors=errors,
    -            view=view,
    -        )
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class Ack -
    -
    -
    -
    - -Expand source code - -
    class Ack:
    -    response: Optional[BoltResponse]
    -
    -    def __init__(self):
    -        self.response: Optional[BoltResponse] = None
    -
    -    def __call__(
    -        self,
    -        text: Union[str, dict] = "",  # text: str or whole_response: dict
    -        blocks: Optional[Sequence[Union[dict, Block]]] = None,
    -        attachments: Optional[Sequence[Union[dict, Attachment]]] = None,
    -        unfurl_links: Optional[bool] = None,
    -        unfurl_media: Optional[bool] = None,
    -        response_type: Optional[str] = None,  # in_channel / ephemeral
    -        # block_suggestion / dialog_suggestion
    -        options: Optional[Sequence[Union[dict, Option]]] = None,
    -        option_groups: Optional[Sequence[Union[dict, OptionGroup]]] = None,
    -        # view_submission
    -        response_action: Optional[str] = None,  # errors / update / push / clear
    -        errors: Optional[Dict[str, str]] = None,
    -        view: Optional[Union[dict, View]] = None,
    -    ) -> BoltResponse:
    -        return _set_response(
    -            self,
    -            text_or_whole_response=text,
    -            blocks=blocks,
    -            attachments=attachments,
    -            unfurl_links=unfurl_links,
    -            unfurl_media=unfurl_media,
    -            response_type=response_type,
    -            options=options,
    -            option_groups=option_groups,
    -            response_action=response_action,
    -            errors=errors,
    -            view=view,
    -        )
    -
    -

    Class variables

    -
    -
    var response :ย Optional[BoltResponse]
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/context/ack/async_ack.html b/docs/api-docs/slack_bolt/context/ack/async_ack.html deleted file mode 100644 index 4c2f5fcdc..000000000 --- a/docs/api-docs/slack_bolt/context/ack/async_ack.html +++ /dev/null @@ -1,171 +0,0 @@ - - - - - - -slack_bolt.context.ack.async_ack API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.context.ack.async_ack

    -
    -
    -
    - -Expand source code - -
    from typing import Optional, Union, Dict, Sequence
    -
    -from slack_sdk.models.attachments import Attachment
    -from slack_sdk.models.blocks import Block, Option, OptionGroup
    -from slack_sdk.models.views import View
    -
    -from slack_bolt.context.ack.internals import _set_response
    -from slack_bolt.response.response import BoltResponse
    -
    -
    -class AsyncAck:
    -    response: Optional[BoltResponse]
    -
    -    def __init__(self):
    -        self.response: Optional[BoltResponse] = None
    -
    -    async def __call__(
    -        self,
    -        text: Union[str, dict] = "",  # text: str or whole_response: dict
    -        blocks: Optional[Sequence[Union[dict, Block]]] = None,
    -        attachments: Optional[Sequence[Union[dict, Attachment]]] = None,
    -        unfurl_links: Optional[bool] = None,
    -        unfurl_media: Optional[bool] = None,
    -        response_type: Optional[str] = None,  # in_channel / ephemeral
    -        # block_suggestion / dialog_suggestion
    -        options: Optional[Sequence[Union[dict, Option]]] = None,
    -        option_groups: Optional[Sequence[Union[dict, OptionGroup]]] = None,
    -        # view_submission
    -        response_action: Optional[str] = None,  # errors / update / push / clear
    -        errors: Optional[Dict[str, str]] = None,
    -        view: Optional[Union[dict, View]] = None,
    -    ) -> BoltResponse:
    -        return _set_response(
    -            self,
    -            text_or_whole_response=text,
    -            blocks=blocks,
    -            attachments=attachments,
    -            unfurl_links=unfurl_links,
    -            unfurl_media=unfurl_media,
    -            response_type=response_type,
    -            options=options,
    -            option_groups=option_groups,
    -            response_action=response_action,
    -            errors=errors,
    -            view=view,
    -        )
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class AsyncAck -
    -
    -
    -
    - -Expand source code - -
    class AsyncAck:
    -    response: Optional[BoltResponse]
    -
    -    def __init__(self):
    -        self.response: Optional[BoltResponse] = None
    -
    -    async def __call__(
    -        self,
    -        text: Union[str, dict] = "",  # text: str or whole_response: dict
    -        blocks: Optional[Sequence[Union[dict, Block]]] = None,
    -        attachments: Optional[Sequence[Union[dict, Attachment]]] = None,
    -        unfurl_links: Optional[bool] = None,
    -        unfurl_media: Optional[bool] = None,
    -        response_type: Optional[str] = None,  # in_channel / ephemeral
    -        # block_suggestion / dialog_suggestion
    -        options: Optional[Sequence[Union[dict, Option]]] = None,
    -        option_groups: Optional[Sequence[Union[dict, OptionGroup]]] = None,
    -        # view_submission
    -        response_action: Optional[str] = None,  # errors / update / push / clear
    -        errors: Optional[Dict[str, str]] = None,
    -        view: Optional[Union[dict, View]] = None,
    -    ) -> BoltResponse:
    -        return _set_response(
    -            self,
    -            text_or_whole_response=text,
    -            blocks=blocks,
    -            attachments=attachments,
    -            unfurl_links=unfurl_links,
    -            unfurl_media=unfurl_media,
    -            response_type=response_type,
    -            options=options,
    -            option_groups=option_groups,
    -            response_action=response_action,
    -            errors=errors,
    -            view=view,
    -        )
    -
    -

    Class variables

    -
    -
    var response :ย Optional[BoltResponse]
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/context/ack/index.html b/docs/api-docs/slack_bolt/context/ack/index.html deleted file mode 100644 index a75ce98f3..000000000 --- a/docs/api-docs/slack_bolt/context/ack/index.html +++ /dev/null @@ -1,153 +0,0 @@ - - - - - - -slack_bolt.context.ack API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.context.ack

    -
    -
    -
    - -Expand source code - -
    # Don't add async module imports here
    -from .ack import Ack
    -
    -__all__ = [
    -    "Ack",
    -]
    -
    -
    -
    -

    Sub-modules

    -
    -
    slack_bolt.context.ack.ack
    -
    -
    -
    -
    slack_bolt.context.ack.async_ack
    -
    -
    -
    -
    slack_bolt.context.ack.internals
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class Ack -
    -
    -
    -
    - -Expand source code - -
    class Ack:
    -    response: Optional[BoltResponse]
    -
    -    def __init__(self):
    -        self.response: Optional[BoltResponse] = None
    -
    -    def __call__(
    -        self,
    -        text: Union[str, dict] = "",  # text: str or whole_response: dict
    -        blocks: Optional[Sequence[Union[dict, Block]]] = None,
    -        attachments: Optional[Sequence[Union[dict, Attachment]]] = None,
    -        unfurl_links: Optional[bool] = None,
    -        unfurl_media: Optional[bool] = None,
    -        response_type: Optional[str] = None,  # in_channel / ephemeral
    -        # block_suggestion / dialog_suggestion
    -        options: Optional[Sequence[Union[dict, Option]]] = None,
    -        option_groups: Optional[Sequence[Union[dict, OptionGroup]]] = None,
    -        # view_submission
    -        response_action: Optional[str] = None,  # errors / update / push / clear
    -        errors: Optional[Dict[str, str]] = None,
    -        view: Optional[Union[dict, View]] = None,
    -    ) -> BoltResponse:
    -        return _set_response(
    -            self,
    -            text_or_whole_response=text,
    -            blocks=blocks,
    -            attachments=attachments,
    -            unfurl_links=unfurl_links,
    -            unfurl_media=unfurl_media,
    -            response_type=response_type,
    -            options=options,
    -            option_groups=option_groups,
    -            response_action=response_action,
    -            errors=errors,
    -            view=view,
    -        )
    -
    -

    Class variables

    -
    -
    var response :ย Optional[BoltResponse]
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/context/ack/internals.html b/docs/api-docs/slack_bolt/context/ack/internals.html deleted file mode 100644 index 255f2c6e6..000000000 --- a/docs/api-docs/slack_bolt/context/ack/internals.html +++ /dev/null @@ -1,159 +0,0 @@ - - - - - - -slack_bolt.context.ack.internals API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.context.ack.internals

    -
    -
    -
    - -Expand source code - -
    from typing import Optional, Union, Any, Dict, Sequence
    -
    -from slack_sdk.models.attachments import Attachment
    -from slack_sdk.models.blocks import Block, Option, OptionGroup
    -from slack_sdk.models.views import View
    -
    -from slack_bolt.error import BoltError
    -from slack_bolt.response import BoltResponse
    -from slack_bolt.util.utils import convert_to_dict_list, convert_to_dict
    -
    -
    -def _set_response(
    -    self: Any,
    -    text_or_whole_response: Union[str, dict] = "",
    -    blocks: Optional[Sequence[Union[dict, Block]]] = None,
    -    attachments: Optional[Sequence[Union[dict, Attachment]]] = None,
    -    unfurl_links: Optional[bool] = None,
    -    unfurl_media: Optional[bool] = None,
    -    response_type: Optional[str] = None,  # in_channel / ephemeral
    -    # block_suggestion / dialog_suggestion
    -    options: Optional[Sequence[Union[dict, Option]]] = None,
    -    option_groups: Optional[Sequence[Union[dict, OptionGroup]]] = None,
    -    # view_submission
    -    response_action: Optional[str] = None,
    -    errors: Optional[Union[Dict[str, str], Sequence[Dict[str, str]]]] = None,
    -    view: Optional[Union[dict, View]] = None,
    -) -> BoltResponse:
    -    if isinstance(text_or_whole_response, str):
    -        text: str = text_or_whole_response
    -        body = {"text": text}
    -        if response_type:
    -            body["response_type"] = response_type
    -        if unfurl_links is not None:
    -            body["unfurl_links"] = unfurl_links
    -        if unfurl_media is not None:
    -            body["unfurl_media"] = unfurl_media
    -        if attachments and len(attachments) > 0:
    -            body.update({"text": text, "attachments": convert_to_dict_list(attachments)})
    -            self.response = BoltResponse(status=200, body=body)
    -        elif blocks and len(blocks) > 0:
    -            body.update({"text": text, "blocks": convert_to_dict_list(blocks)})
    -            self.response = BoltResponse(status=200, body=body)
    -        elif options is not None:
    -            body = {"options": convert_to_dict_list(options)}
    -            self.response = BoltResponse(status=200, body=body)
    -        elif option_groups is not None:
    -            body = {"option_groups": convert_to_dict_list(option_groups)}
    -            self.response = BoltResponse(status=200, body=body)
    -        elif response_action:
    -            # These patterns are in response to view_submission requests
    -            if response_action == "errors":
    -                if errors:
    -                    self.response = BoltResponse(
    -                        status=200,
    -                        body={
    -                            "response_action": response_action,
    -                            "errors": convert_to_dict(errors),
    -                        },
    -                    )
    -                else:
    -                    raise ValueError("errors field is required for response_action: errors")
    -            else:
    -                body = {"response_action": response_action}
    -                if view:
    -                    body["view"] = convert_to_dict(view)
    -                self.response = BoltResponse(status=200, body=body)
    -        elif errors:
    -            # dialogs: errors without response_action
    -            body = {"errors": convert_to_dict_list(errors)}
    -            self.response = BoltResponse(status=200, body=body)
    -        else:
    -            if len(body) == 1 and "text" in body:
    -                self.response = BoltResponse(status=200, body=body["text"])
    -            else:
    -                self.response = BoltResponse(status=200, body=body)
    -        return self.response
    -    elif isinstance(text_or_whole_response, dict):
    -        body = text_or_whole_response
    -        if "attachments" in body:
    -            body["attachments"] = convert_to_dict_list(body["attachments"])
    -        if "blocks" in body:
    -            body["blocks"] = convert_to_dict_list(body["blocks"])
    -        if "options" in body:
    -            body["options"] = convert_to_dict_list(body["options"])
    -        if "option_groups" in body:
    -            body["option_groups"] = convert_to_dict_list(body["option_groups"])
    -        if "errors" in body:
    -            if body.get("response_action", "") == "errors":
    -                # modal
    -                body["errors"] = convert_to_dict(body["errors"])
    -            else:
    -                # dialog
    -                body["errors"] = convert_to_dict_list(body["errors"])
    -        if "view" in body:
    -            body["view"] = convert_to_dict(body["view"])
    -        # no modification for response_type, response_action here
    -
    -        self.response = BoltResponse(status=200, body=body)
    -        return self.response
    -    else:
    -        raise BoltError(f"{text_or_whole_response} (type: {type(text_or_whole_response)}) is unsupported")
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/context/async_context.html b/docs/api-docs/slack_bolt/context/async_context.html deleted file mode 100644 index 0a74c4ff1..000000000 --- a/docs/api-docs/slack_bolt/context/async_context.html +++ /dev/null @@ -1,566 +0,0 @@ - - - - - - -slack_bolt.context.async_context API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.context.async_context

    -
    -
    -
    - -Expand source code - -
    from typing import Optional
    -
    -from slack_sdk.web.async_client import AsyncWebClient
    -
    -from slack_bolt.context.ack.async_ack import AsyncAck
    -from slack_bolt.context.base_context import BaseContext
    -from slack_bolt.context.respond.async_respond import AsyncRespond
    -from slack_bolt.context.say.async_say import AsyncSay
    -from slack_bolt.util.utils import create_copy
    -
    -
    -class AsyncBoltContext(BaseContext):
    -    """Context object associated with a request from Slack."""
    -
    -    def to_copyable(self) -> "AsyncBoltContext":
    -        new_dict = {}
    -        for prop_name, prop_value in self.items():
    -            if prop_name in self.standard_property_names:
    -                # all the standard properties are copiable
    -                new_dict[prop_name] = prop_value
    -            else:
    -                try:
    -                    copied_value = create_copy(prop_value)
    -                    new_dict[prop_name] = copied_value
    -                except TypeError as te:
    -                    self.logger.debug(
    -                        f"Skipped settings '{prop_name}' to a copied request for lazy listeners "
    -                        f"as it's not possible to make a deep copy (error: {te})"
    -                    )
    -        return AsyncBoltContext(new_dict)
    -
    -    @property
    -    def client(self) -> Optional[AsyncWebClient]:
    -        """The `AsyncWebClient` instance available for this request.
    -
    -            @app.event("app_mention")
    -            async def handle_events(context):
    -                await context.client.chat_postMessage(
    -                    channel=context.channel_id,
    -                    text="Thanks!",
    -                )
    -
    -            # You can access "client" this way too.
    -            @app.event("app_mention")
    -            async def handle_events(client, context):
    -                await client.chat_postMessage(
    -                    channel=context.channel_id,
    -                    text="Thanks!",
    -                )
    -
    -        Returns:
    -            `AsyncWebClient` instance
    -        """
    -        if "client" not in self:
    -            self["client"] = AsyncWebClient(token=None)
    -        return self["client"]
    -
    -    @property
    -    def ack(self) -> AsyncAck:
    -        """`ack()` function for this request.
    -
    -            @app.action("button")
    -            async def handle_button_clicks(context):
    -                await context.ack()
    -
    -            # You can access "ack" this way too.
    -            @app.action("button")
    -            async def handle_button_clicks(ack):
    -                await ack()
    -
    -        Returns:
    -            Callable `ack()` function
    -        """
    -        if "ack" not in self:
    -            self["ack"] = AsyncAck()
    -        return self["ack"]
    -
    -    @property
    -    def say(self) -> AsyncSay:
    -        """`say()` function for this request.
    -
    -            @app.action("button")
    -            async def handle_button_clicks(context):
    -                await context.ack()
    -                await context.say("Hi!")
    -
    -            # You can access "ack" this way too.
    -            @app.action("button")
    -            async def handle_button_clicks(ack, say):
    -                await ack()
    -                await say("Hi!")
    -
    -        Returns:
    -            Callable `say()` function
    -        """
    -        if "say" not in self:
    -            self["say"] = AsyncSay(client=self.client, channel=self.channel_id)
    -        return self["say"]
    -
    -    @property
    -    def respond(self) -> Optional[AsyncRespond]:
    -        """`respond()` function for this request.
    -
    -            @app.action("button")
    -            async def handle_button_clicks(context):
    -                await context.ack()
    -                await context.respond("Hi!")
    -
    -            # You can access "ack" this way too.
    -            @app.action("button")
    -            async def handle_button_clicks(ack, respond):
    -                await ack()
    -                await respond("Hi!")
    -
    -        Returns:
    -            Callable `respond()` function
    -        """
    -        if "respond" not in self:
    -            self["respond"] = AsyncRespond(
    -                response_url=self.response_url,
    -                proxy=self.client.proxy,
    -                ssl=self.client.ssl,
    -            )
    -        return self["respond"]
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class AsyncBoltContext -(*args, **kwargs) -
    -
    -

    Context object associated with a request from Slack.

    -
    - -Expand source code - -
    class AsyncBoltContext(BaseContext):
    -    """Context object associated with a request from Slack."""
    -
    -    def to_copyable(self) -> "AsyncBoltContext":
    -        new_dict = {}
    -        for prop_name, prop_value in self.items():
    -            if prop_name in self.standard_property_names:
    -                # all the standard properties are copiable
    -                new_dict[prop_name] = prop_value
    -            else:
    -                try:
    -                    copied_value = create_copy(prop_value)
    -                    new_dict[prop_name] = copied_value
    -                except TypeError as te:
    -                    self.logger.debug(
    -                        f"Skipped settings '{prop_name}' to a copied request for lazy listeners "
    -                        f"as it's not possible to make a deep copy (error: {te})"
    -                    )
    -        return AsyncBoltContext(new_dict)
    -
    -    @property
    -    def client(self) -> Optional[AsyncWebClient]:
    -        """The `AsyncWebClient` instance available for this request.
    -
    -            @app.event("app_mention")
    -            async def handle_events(context):
    -                await context.client.chat_postMessage(
    -                    channel=context.channel_id,
    -                    text="Thanks!",
    -                )
    -
    -            # You can access "client" this way too.
    -            @app.event("app_mention")
    -            async def handle_events(client, context):
    -                await client.chat_postMessage(
    -                    channel=context.channel_id,
    -                    text="Thanks!",
    -                )
    -
    -        Returns:
    -            `AsyncWebClient` instance
    -        """
    -        if "client" not in self:
    -            self["client"] = AsyncWebClient(token=None)
    -        return self["client"]
    -
    -    @property
    -    def ack(self) -> AsyncAck:
    -        """`ack()` function for this request.
    -
    -            @app.action("button")
    -            async def handle_button_clicks(context):
    -                await context.ack()
    -
    -            # You can access "ack" this way too.
    -            @app.action("button")
    -            async def handle_button_clicks(ack):
    -                await ack()
    -
    -        Returns:
    -            Callable `ack()` function
    -        """
    -        if "ack" not in self:
    -            self["ack"] = AsyncAck()
    -        return self["ack"]
    -
    -    @property
    -    def say(self) -> AsyncSay:
    -        """`say()` function for this request.
    -
    -            @app.action("button")
    -            async def handle_button_clicks(context):
    -                await context.ack()
    -                await context.say("Hi!")
    -
    -            # You can access "ack" this way too.
    -            @app.action("button")
    -            async def handle_button_clicks(ack, say):
    -                await ack()
    -                await say("Hi!")
    -
    -        Returns:
    -            Callable `say()` function
    -        """
    -        if "say" not in self:
    -            self["say"] = AsyncSay(client=self.client, channel=self.channel_id)
    -        return self["say"]
    -
    -    @property
    -    def respond(self) -> Optional[AsyncRespond]:
    -        """`respond()` function for this request.
    -
    -            @app.action("button")
    -            async def handle_button_clicks(context):
    -                await context.ack()
    -                await context.respond("Hi!")
    -
    -            # You can access "ack" this way too.
    -            @app.action("button")
    -            async def handle_button_clicks(ack, respond):
    -                await ack()
    -                await respond("Hi!")
    -
    -        Returns:
    -            Callable `respond()` function
    -        """
    -        if "respond" not in self:
    -            self["respond"] = AsyncRespond(
    -                response_url=self.response_url,
    -                proxy=self.client.proxy,
    -                ssl=self.client.ssl,
    -            )
    -        return self["respond"]
    -
    -

    Ancestors

    - -

    Instance variables

    -
    -
    var ack :ย AsyncAck
    -
    -

    ack() function for this request.

    -
    @app.action("button")
    -async def handle_button_clicks(context):
    -    await context.ack()
    -
    -# You can access "ack" this way too.
    -@app.action("button")
    -async def handle_button_clicks(ack):
    -    await ack()
    -
    -

    Returns

    -

    Callable ack() function

    -
    - -Expand source code - -
    @property
    -def ack(self) -> AsyncAck:
    -    """`ack()` function for this request.
    -
    -        @app.action("button")
    -        async def handle_button_clicks(context):
    -            await context.ack()
    -
    -        # You can access "ack" this way too.
    -        @app.action("button")
    -        async def handle_button_clicks(ack):
    -            await ack()
    -
    -    Returns:
    -        Callable `ack()` function
    -    """
    -    if "ack" not in self:
    -        self["ack"] = AsyncAck()
    -    return self["ack"]
    -
    -
    -
    var client :ย Optional[slack_sdk.web.async_client.AsyncWebClient]
    -
    -

    The AsyncWebClient instance available for this request.

    -
    @app.event("app_mention")
    -async def handle_events(context):
    -    await context.client.chat_postMessage(
    -        channel=context.channel_id,
    -        text="Thanks!",
    -    )
    -
    -# You can access "client" this way too.
    -@app.event("app_mention")
    -async def handle_events(client, context):
    -    await client.chat_postMessage(
    -        channel=context.channel_id,
    -        text="Thanks!",
    -    )
    -
    -

    Returns

    -

    AsyncWebClient instance

    -
    - -Expand source code - -
    @property
    -def client(self) -> Optional[AsyncWebClient]:
    -    """The `AsyncWebClient` instance available for this request.
    -
    -        @app.event("app_mention")
    -        async def handle_events(context):
    -            await context.client.chat_postMessage(
    -                channel=context.channel_id,
    -                text="Thanks!",
    -            )
    -
    -        # You can access "client" this way too.
    -        @app.event("app_mention")
    -        async def handle_events(client, context):
    -            await client.chat_postMessage(
    -                channel=context.channel_id,
    -                text="Thanks!",
    -            )
    -
    -    Returns:
    -        `AsyncWebClient` instance
    -    """
    -    if "client" not in self:
    -        self["client"] = AsyncWebClient(token=None)
    -    return self["client"]
    -
    -
    -
    var respond :ย Optional[AsyncRespond]
    -
    -

    respond() function for this request.

    -
    @app.action("button")
    -async def handle_button_clicks(context):
    -    await context.ack()
    -    await context.respond("Hi!")
    -
    -# You can access "ack" this way too.
    -@app.action("button")
    -async def handle_button_clicks(ack, respond):
    -    await ack()
    -    await respond("Hi!")
    -
    -

    Returns

    -

    Callable respond() function

    -
    - -Expand source code - -
    @property
    -def respond(self) -> Optional[AsyncRespond]:
    -    """`respond()` function for this request.
    -
    -        @app.action("button")
    -        async def handle_button_clicks(context):
    -            await context.ack()
    -            await context.respond("Hi!")
    -
    -        # You can access "ack" this way too.
    -        @app.action("button")
    -        async def handle_button_clicks(ack, respond):
    -            await ack()
    -            await respond("Hi!")
    -
    -    Returns:
    -        Callable `respond()` function
    -    """
    -    if "respond" not in self:
    -        self["respond"] = AsyncRespond(
    -            response_url=self.response_url,
    -            proxy=self.client.proxy,
    -            ssl=self.client.ssl,
    -        )
    -    return self["respond"]
    -
    -
    -
    var say :ย AsyncSay
    -
    -

    say() function for this request.

    -
    @app.action("button")
    -async def handle_button_clicks(context):
    -    await context.ack()
    -    await context.say("Hi!")
    -
    -# You can access "ack" this way too.
    -@app.action("button")
    -async def handle_button_clicks(ack, say):
    -    await ack()
    -    await say("Hi!")
    -
    -

    Returns

    -

    Callable say() function

    -
    - -Expand source code - -
    @property
    -def say(self) -> AsyncSay:
    -    """`say()` function for this request.
    -
    -        @app.action("button")
    -        async def handle_button_clicks(context):
    -            await context.ack()
    -            await context.say("Hi!")
    -
    -        # You can access "ack" this way too.
    -        @app.action("button")
    -        async def handle_button_clicks(ack, say):
    -            await ack()
    -            await say("Hi!")
    -
    -    Returns:
    -        Callable `say()` function
    -    """
    -    if "say" not in self:
    -        self["say"] = AsyncSay(client=self.client, channel=self.channel_id)
    -    return self["say"]
    -
    -
    -
    -

    Methods

    -
    -
    -def to_copyable(self) โ€‘>ย AsyncBoltContext -
    -
    -
    -
    - -Expand source code - -
    def to_copyable(self) -> "AsyncBoltContext":
    -    new_dict = {}
    -    for prop_name, prop_value in self.items():
    -        if prop_name in self.standard_property_names:
    -            # all the standard properties are copiable
    -            new_dict[prop_name] = prop_value
    -        else:
    -            try:
    -                copied_value = create_copy(prop_value)
    -                new_dict[prop_name] = copied_value
    -            except TypeError as te:
    -                self.logger.debug(
    -                    f"Skipped settings '{prop_name}' to a copied request for lazy listeners "
    -                    f"as it's not possible to make a deep copy (error: {te})"
    -                )
    -    return AsyncBoltContext(new_dict)
    -
    -
    -
    -

    Inherited members

    - -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/context/context.html b/docs/api-docs/slack_bolt/context/context.html deleted file mode 100644 index b8519aaf3..000000000 --- a/docs/api-docs/slack_bolt/context/context.html +++ /dev/null @@ -1,570 +0,0 @@ - - - - - - -slack_bolt.context.context API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.context.context

    -
    -
    -
    - -Expand source code - -
    # pytype: skip-file
    -from typing import Optional
    -
    -from slack_sdk import WebClient
    -
    -from slack_bolt.context.ack import Ack
    -from slack_bolt.context.base_context import BaseContext
    -from slack_bolt.context.respond import Respond
    -from slack_bolt.context.say import Say
    -from slack_bolt.util.utils import create_copy
    -
    -
    -class BoltContext(BaseContext):
    -    """Context object associated with a request from Slack."""
    -
    -    def to_copyable(self) -> "BoltContext":
    -        new_dict = {}
    -        for prop_name, prop_value in self.items():
    -            if prop_name in self.standard_property_names:
    -                # all the standard properties are copiable
    -                new_dict[prop_name] = prop_value
    -            else:
    -                try:
    -                    copied_value = create_copy(prop_value)
    -                    new_dict[prop_name] = copied_value
    -                except TypeError as te:
    -                    self.logger.warning(
    -                        f"Skipped setting '{prop_name}' to a copied request for lazy listeners "
    -                        "due to a deep-copy creation error. Consider passing the value not as part of context object "
    -                        f"(error: {te})"
    -                    )
    -        return BoltContext(new_dict)
    -
    -    @property
    -    def client(self) -> Optional[WebClient]:
    -        """The `WebClient` instance available for this request.
    -
    -            @app.event("app_mention")
    -            def handle_events(context):
    -                context.client.chat_postMessage(
    -                    channel=context.channel_id,
    -                    text="Thanks!",
    -                )
    -
    -            # You can access "client" this way too.
    -            @app.event("app_mention")
    -            def handle_events(client, context):
    -                client.chat_postMessage(
    -                    channel=context.channel_id,
    -                    text="Thanks!",
    -                )
    -
    -        Returns:
    -            `WebClient` instance
    -        """
    -        if "client" not in self:
    -            self["client"] = WebClient(token=None)
    -        return self["client"]
    -
    -    @property
    -    def ack(self) -> Ack:
    -        """`ack()` function for this request.
    -
    -            @app.action("button")
    -            def handle_button_clicks(context):
    -                context.ack()
    -
    -            # You can access "ack" this way too.
    -            @app.action("button")
    -            def handle_button_clicks(ack):
    -                ack()
    -
    -        Returns:
    -            Callable `ack()` function
    -        """
    -        if "ack" not in self:
    -            self["ack"] = Ack()
    -        return self["ack"]
    -
    -    @property
    -    def say(self) -> Say:
    -        """`say()` function for this request.
    -
    -            @app.action("button")
    -            def handle_button_clicks(context):
    -                context.ack()
    -                context.say("Hi!")
    -
    -            # You can access "ack" this way too.
    -            @app.action("button")
    -            def handle_button_clicks(ack, say):
    -                ack()
    -                say("Hi!")
    -
    -        Returns:
    -            Callable `say()` function
    -        """
    -        if "say" not in self:
    -            self["say"] = Say(client=self.client, channel=self.channel_id)
    -        return self["say"]
    -
    -    @property
    -    def respond(self) -> Optional[Respond]:
    -        """`respond()` function for this request.
    -
    -            @app.action("button")
    -            def handle_button_clicks(context):
    -                context.ack()
    -                context.respond("Hi!")
    -
    -            # You can access "ack" this way too.
    -            @app.action("button")
    -            def handle_button_clicks(ack, respond):
    -                ack()
    -                respond("Hi!")
    -
    -        Returns:
    -            Callable `respond()` function
    -        """
    -        if "respond" not in self:
    -            self["respond"] = Respond(
    -                response_url=self.response_url,
    -                proxy=self.client.proxy,
    -                ssl=self.client.ssl,
    -            )
    -        return self["respond"]
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class BoltContext -(*args, **kwargs) -
    -
    -

    Context object associated with a request from Slack.

    -
    - -Expand source code - -
    class BoltContext(BaseContext):
    -    """Context object associated with a request from Slack."""
    -
    -    def to_copyable(self) -> "BoltContext":
    -        new_dict = {}
    -        for prop_name, prop_value in self.items():
    -            if prop_name in self.standard_property_names:
    -                # all the standard properties are copiable
    -                new_dict[prop_name] = prop_value
    -            else:
    -                try:
    -                    copied_value = create_copy(prop_value)
    -                    new_dict[prop_name] = copied_value
    -                except TypeError as te:
    -                    self.logger.warning(
    -                        f"Skipped setting '{prop_name}' to a copied request for lazy listeners "
    -                        "due to a deep-copy creation error. Consider passing the value not as part of context object "
    -                        f"(error: {te})"
    -                    )
    -        return BoltContext(new_dict)
    -
    -    @property
    -    def client(self) -> Optional[WebClient]:
    -        """The `WebClient` instance available for this request.
    -
    -            @app.event("app_mention")
    -            def handle_events(context):
    -                context.client.chat_postMessage(
    -                    channel=context.channel_id,
    -                    text="Thanks!",
    -                )
    -
    -            # You can access "client" this way too.
    -            @app.event("app_mention")
    -            def handle_events(client, context):
    -                client.chat_postMessage(
    -                    channel=context.channel_id,
    -                    text="Thanks!",
    -                )
    -
    -        Returns:
    -            `WebClient` instance
    -        """
    -        if "client" not in self:
    -            self["client"] = WebClient(token=None)
    -        return self["client"]
    -
    -    @property
    -    def ack(self) -> Ack:
    -        """`ack()` function for this request.
    -
    -            @app.action("button")
    -            def handle_button_clicks(context):
    -                context.ack()
    -
    -            # You can access "ack" this way too.
    -            @app.action("button")
    -            def handle_button_clicks(ack):
    -                ack()
    -
    -        Returns:
    -            Callable `ack()` function
    -        """
    -        if "ack" not in self:
    -            self["ack"] = Ack()
    -        return self["ack"]
    -
    -    @property
    -    def say(self) -> Say:
    -        """`say()` function for this request.
    -
    -            @app.action("button")
    -            def handle_button_clicks(context):
    -                context.ack()
    -                context.say("Hi!")
    -
    -            # You can access "ack" this way too.
    -            @app.action("button")
    -            def handle_button_clicks(ack, say):
    -                ack()
    -                say("Hi!")
    -
    -        Returns:
    -            Callable `say()` function
    -        """
    -        if "say" not in self:
    -            self["say"] = Say(client=self.client, channel=self.channel_id)
    -        return self["say"]
    -
    -    @property
    -    def respond(self) -> Optional[Respond]:
    -        """`respond()` function for this request.
    -
    -            @app.action("button")
    -            def handle_button_clicks(context):
    -                context.ack()
    -                context.respond("Hi!")
    -
    -            # You can access "ack" this way too.
    -            @app.action("button")
    -            def handle_button_clicks(ack, respond):
    -                ack()
    -                respond("Hi!")
    -
    -        Returns:
    -            Callable `respond()` function
    -        """
    -        if "respond" not in self:
    -            self["respond"] = Respond(
    -                response_url=self.response_url,
    -                proxy=self.client.proxy,
    -                ssl=self.client.ssl,
    -            )
    -        return self["respond"]
    -
    -

    Ancestors

    - -

    Instance variables

    -
    -
    var ack :ย Ack
    -
    -

    ack() function for this request.

    -
    @app.action("button")
    -def handle_button_clicks(context):
    -    context.ack()
    -
    -# You can access "ack" this way too.
    -@app.action("button")
    -def handle_button_clicks(ack):
    -    ack()
    -
    -

    Returns

    -

    Callable ack() function

    -
    - -Expand source code - -
    @property
    -def ack(self) -> Ack:
    -    """`ack()` function for this request.
    -
    -        @app.action("button")
    -        def handle_button_clicks(context):
    -            context.ack()
    -
    -        # You can access "ack" this way too.
    -        @app.action("button")
    -        def handle_button_clicks(ack):
    -            ack()
    -
    -    Returns:
    -        Callable `ack()` function
    -    """
    -    if "ack" not in self:
    -        self["ack"] = Ack()
    -    return self["ack"]
    -
    -
    -
    var client :ย Optional[slack_sdk.web.client.WebClient]
    -
    -

    The WebClient instance available for this request.

    -
    @app.event("app_mention")
    -def handle_events(context):
    -    context.client.chat_postMessage(
    -        channel=context.channel_id,
    -        text="Thanks!",
    -    )
    -
    -# You can access "client" this way too.
    -@app.event("app_mention")
    -def handle_events(client, context):
    -    client.chat_postMessage(
    -        channel=context.channel_id,
    -        text="Thanks!",
    -    )
    -
    -

    Returns

    -

    WebClient instance

    -
    - -Expand source code - -
    @property
    -def client(self) -> Optional[WebClient]:
    -    """The `WebClient` instance available for this request.
    -
    -        @app.event("app_mention")
    -        def handle_events(context):
    -            context.client.chat_postMessage(
    -                channel=context.channel_id,
    -                text="Thanks!",
    -            )
    -
    -        # You can access "client" this way too.
    -        @app.event("app_mention")
    -        def handle_events(client, context):
    -            client.chat_postMessage(
    -                channel=context.channel_id,
    -                text="Thanks!",
    -            )
    -
    -    Returns:
    -        `WebClient` instance
    -    """
    -    if "client" not in self:
    -        self["client"] = WebClient(token=None)
    -    return self["client"]
    -
    -
    -
    var respond :ย Optional[Respond]
    -
    -

    respond() function for this request.

    -
    @app.action("button")
    -def handle_button_clicks(context):
    -    context.ack()
    -    context.respond("Hi!")
    -
    -# You can access "ack" this way too.
    -@app.action("button")
    -def handle_button_clicks(ack, respond):
    -    ack()
    -    respond("Hi!")
    -
    -

    Returns

    -

    Callable respond() function

    -
    - -Expand source code - -
    @property
    -def respond(self) -> Optional[Respond]:
    -    """`respond()` function for this request.
    -
    -        @app.action("button")
    -        def handle_button_clicks(context):
    -            context.ack()
    -            context.respond("Hi!")
    -
    -        # You can access "ack" this way too.
    -        @app.action("button")
    -        def handle_button_clicks(ack, respond):
    -            ack()
    -            respond("Hi!")
    -
    -    Returns:
    -        Callable `respond()` function
    -    """
    -    if "respond" not in self:
    -        self["respond"] = Respond(
    -            response_url=self.response_url,
    -            proxy=self.client.proxy,
    -            ssl=self.client.ssl,
    -        )
    -    return self["respond"]
    -
    -
    -
    var say :ย Say
    -
    -

    say() function for this request.

    -
    @app.action("button")
    -def handle_button_clicks(context):
    -    context.ack()
    -    context.say("Hi!")
    -
    -# You can access "ack" this way too.
    -@app.action("button")
    -def handle_button_clicks(ack, say):
    -    ack()
    -    say("Hi!")
    -
    -

    Returns

    -

    Callable say() function

    -
    - -Expand source code - -
    @property
    -def say(self) -> Say:
    -    """`say()` function for this request.
    -
    -        @app.action("button")
    -        def handle_button_clicks(context):
    -            context.ack()
    -            context.say("Hi!")
    -
    -        # You can access "ack" this way too.
    -        @app.action("button")
    -        def handle_button_clicks(ack, say):
    -            ack()
    -            say("Hi!")
    -
    -    Returns:
    -        Callable `say()` function
    -    """
    -    if "say" not in self:
    -        self["say"] = Say(client=self.client, channel=self.channel_id)
    -    return self["say"]
    -
    -
    -
    -

    Methods

    -
    -
    -def to_copyable(self) โ€‘>ย BoltContext -
    -
    -
    -
    - -Expand source code - -
    def to_copyable(self) -> "BoltContext":
    -    new_dict = {}
    -    for prop_name, prop_value in self.items():
    -        if prop_name in self.standard_property_names:
    -            # all the standard properties are copiable
    -            new_dict[prop_name] = prop_value
    -        else:
    -            try:
    -                copied_value = create_copy(prop_value)
    -                new_dict[prop_name] = copied_value
    -            except TypeError as te:
    -                self.logger.warning(
    -                    f"Skipped setting '{prop_name}' to a copied request for lazy listeners "
    -                    "due to a deep-copy creation error. Consider passing the value not as part of context object "
    -                    f"(error: {te})"
    -                )
    -    return BoltContext(new_dict)
    -
    -
    -
    -

    Inherited members

    - -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/context/index.html b/docs/api-docs/slack_bolt/context/index.html deleted file mode 100644 index ed9fc8e88..000000000 --- a/docs/api-docs/slack_bolt/context/index.html +++ /dev/null @@ -1,499 +0,0 @@ - - - - - - -slack_bolt.context API documentation - - - - - - - - - - - -
    -
    -
    -

    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.

    -
    - -Expand source code - -
    """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.
    -"""
    -
    -# Don't add async module imports here
    -from .context import BoltContext
    -
    -__all__ = [
    -    "BoltContext",
    -]
    -
    -
    -
    -

    Sub-modules

    -
    -
    slack_bolt.context.ack
    -
    -
    -
    -
    slack_bolt.context.async_context
    -
    -
    -
    -
    slack_bolt.context.base_context
    -
    -
    -
    -
    slack_bolt.context.context
    -
    -
    -
    -
    slack_bolt.context.respond
    -
    -
    -
    -
    slack_bolt.context.say
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class BoltContext -(*args, **kwargs) -
    -
    -

    Context object associated with a request from Slack.

    -
    - -Expand source code - -
    class BoltContext(BaseContext):
    -    """Context object associated with a request from Slack."""
    -
    -    def to_copyable(self) -> "BoltContext":
    -        new_dict = {}
    -        for prop_name, prop_value in self.items():
    -            if prop_name in self.standard_property_names:
    -                # all the standard properties are copiable
    -                new_dict[prop_name] = prop_value
    -            else:
    -                try:
    -                    copied_value = create_copy(prop_value)
    -                    new_dict[prop_name] = copied_value
    -                except TypeError as te:
    -                    self.logger.warning(
    -                        f"Skipped setting '{prop_name}' to a copied request for lazy listeners "
    -                        "due to a deep-copy creation error. Consider passing the value not as part of context object "
    -                        f"(error: {te})"
    -                    )
    -        return BoltContext(new_dict)
    -
    -    @property
    -    def client(self) -> Optional[WebClient]:
    -        """The `WebClient` instance available for this request.
    -
    -            @app.event("app_mention")
    -            def handle_events(context):
    -                context.client.chat_postMessage(
    -                    channel=context.channel_id,
    -                    text="Thanks!",
    -                )
    -
    -            # You can access "client" this way too.
    -            @app.event("app_mention")
    -            def handle_events(client, context):
    -                client.chat_postMessage(
    -                    channel=context.channel_id,
    -                    text="Thanks!",
    -                )
    -
    -        Returns:
    -            `WebClient` instance
    -        """
    -        if "client" not in self:
    -            self["client"] = WebClient(token=None)
    -        return self["client"]
    -
    -    @property
    -    def ack(self) -> Ack:
    -        """`ack()` function for this request.
    -
    -            @app.action("button")
    -            def handle_button_clicks(context):
    -                context.ack()
    -
    -            # You can access "ack" this way too.
    -            @app.action("button")
    -            def handle_button_clicks(ack):
    -                ack()
    -
    -        Returns:
    -            Callable `ack()` function
    -        """
    -        if "ack" not in self:
    -            self["ack"] = Ack()
    -        return self["ack"]
    -
    -    @property
    -    def say(self) -> Say:
    -        """`say()` function for this request.
    -
    -            @app.action("button")
    -            def handle_button_clicks(context):
    -                context.ack()
    -                context.say("Hi!")
    -
    -            # You can access "ack" this way too.
    -            @app.action("button")
    -            def handle_button_clicks(ack, say):
    -                ack()
    -                say("Hi!")
    -
    -        Returns:
    -            Callable `say()` function
    -        """
    -        if "say" not in self:
    -            self["say"] = Say(client=self.client, channel=self.channel_id)
    -        return self["say"]
    -
    -    @property
    -    def respond(self) -> Optional[Respond]:
    -        """`respond()` function for this request.
    -
    -            @app.action("button")
    -            def handle_button_clicks(context):
    -                context.ack()
    -                context.respond("Hi!")
    -
    -            # You can access "ack" this way too.
    -            @app.action("button")
    -            def handle_button_clicks(ack, respond):
    -                ack()
    -                respond("Hi!")
    -
    -        Returns:
    -            Callable `respond()` function
    -        """
    -        if "respond" not in self:
    -            self["respond"] = Respond(
    -                response_url=self.response_url,
    -                proxy=self.client.proxy,
    -                ssl=self.client.ssl,
    -            )
    -        return self["respond"]
    -
    -

    Ancestors

    - -

    Instance variables

    -
    -
    var ack :ย Ack
    -
    -

    slack_bolt.context.ack function for this request.

    -
    @app.action("button")
    -def handle_button_clicks(context):
    -    context.ack()
    -
    -# You can access "ack" this way too.
    -@app.action("button")
    -def handle_button_clicks(ack):
    -    ack()
    -
    -

    Returns

    -

    Callable slack_bolt.context.ack function

    -
    - -Expand source code - -
    @property
    -def ack(self) -> Ack:
    -    """`ack()` function for this request.
    -
    -        @app.action("button")
    -        def handle_button_clicks(context):
    -            context.ack()
    -
    -        # You can access "ack" this way too.
    -        @app.action("button")
    -        def handle_button_clicks(ack):
    -            ack()
    -
    -    Returns:
    -        Callable `ack()` function
    -    """
    -    if "ack" not in self:
    -        self["ack"] = Ack()
    -    return self["ack"]
    -
    -
    -
    var client :ย Optional[slack_sdk.web.client.WebClient]
    -
    -

    The WebClient instance available for this request.

    -
    @app.event("app_mention")
    -def handle_events(context):
    -    context.client.chat_postMessage(
    -        channel=context.channel_id,
    -        text="Thanks!",
    -    )
    -
    -# You can access "client" this way too.
    -@app.event("app_mention")
    -def handle_events(client, context):
    -    client.chat_postMessage(
    -        channel=context.channel_id,
    -        text="Thanks!",
    -    )
    -
    -

    Returns

    -

    WebClient instance

    -
    - -Expand source code - -
    @property
    -def client(self) -> Optional[WebClient]:
    -    """The `WebClient` instance available for this request.
    -
    -        @app.event("app_mention")
    -        def handle_events(context):
    -            context.client.chat_postMessage(
    -                channel=context.channel_id,
    -                text="Thanks!",
    -            )
    -
    -        # You can access "client" this way too.
    -        @app.event("app_mention")
    -        def handle_events(client, context):
    -            client.chat_postMessage(
    -                channel=context.channel_id,
    -                text="Thanks!",
    -            )
    -
    -    Returns:
    -        `WebClient` instance
    -    """
    -    if "client" not in self:
    -        self["client"] = WebClient(token=None)
    -    return self["client"]
    -
    -
    -
    var respond :ย Optional[Respond]
    -
    -

    slack_bolt.context.respond function for this request.

    -
    @app.action("button")
    -def handle_button_clicks(context):
    -    context.ack()
    -    context.respond("Hi!")
    -
    -# You can access "ack" this way too.
    -@app.action("button")
    -def handle_button_clicks(ack, respond):
    -    ack()
    -    respond("Hi!")
    -
    -

    Returns

    -

    Callable slack_bolt.context.respond function

    -
    - -Expand source code - -
    @property
    -def respond(self) -> Optional[Respond]:
    -    """`respond()` function for this request.
    -
    -        @app.action("button")
    -        def handle_button_clicks(context):
    -            context.ack()
    -            context.respond("Hi!")
    -
    -        # You can access "ack" this way too.
    -        @app.action("button")
    -        def handle_button_clicks(ack, respond):
    -            ack()
    -            respond("Hi!")
    -
    -    Returns:
    -        Callable `respond()` function
    -    """
    -    if "respond" not in self:
    -        self["respond"] = Respond(
    -            response_url=self.response_url,
    -            proxy=self.client.proxy,
    -            ssl=self.client.ssl,
    -        )
    -    return self["respond"]
    -
    -
    -
    var say :ย Say
    -
    -

    slack_bolt.context.say function for this request.

    -
    @app.action("button")
    -def handle_button_clicks(context):
    -    context.ack()
    -    context.say("Hi!")
    -
    -# You can access "ack" this way too.
    -@app.action("button")
    -def handle_button_clicks(ack, say):
    -    ack()
    -    say("Hi!")
    -
    -

    Returns

    -

    Callable slack_bolt.context.say function

    -
    - -Expand source code - -
    @property
    -def say(self) -> Say:
    -    """`say()` function for this request.
    -
    -        @app.action("button")
    -        def handle_button_clicks(context):
    -            context.ack()
    -            context.say("Hi!")
    -
    -        # You can access "ack" this way too.
    -        @app.action("button")
    -        def handle_button_clicks(ack, say):
    -            ack()
    -            say("Hi!")
    -
    -    Returns:
    -        Callable `say()` function
    -    """
    -    if "say" not in self:
    -        self["say"] = Say(client=self.client, channel=self.channel_id)
    -    return self["say"]
    -
    -
    -
    -

    Methods

    -
    -
    -def to_copyable(self) โ€‘>ย BoltContext -
    -
    -
    -
    - -Expand source code - -
    def to_copyable(self) -> "BoltContext":
    -    new_dict = {}
    -    for prop_name, prop_value in self.items():
    -        if prop_name in self.standard_property_names:
    -            # all the standard properties are copiable
    -            new_dict[prop_name] = prop_value
    -        else:
    -            try:
    -                copied_value = create_copy(prop_value)
    -                new_dict[prop_name] = copied_value
    -            except TypeError as te:
    -                self.logger.warning(
    -                    f"Skipped setting '{prop_name}' to a copied request for lazy listeners "
    -                    "due to a deep-copy creation error. Consider passing the value not as part of context object "
    -                    f"(error: {te})"
    -                )
    -    return BoltContext(new_dict)
    -
    -
    -
    -

    Inherited members

    - -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/context/respond/async_respond.html b/docs/api-docs/slack_bolt/context/respond/async_respond.html deleted file mode 100644 index 11e3ae88c..000000000 --- a/docs/api-docs/slack_bolt/context/respond/async_respond.html +++ /dev/null @@ -1,222 +0,0 @@ - - - - - - -slack_bolt.context.respond.async_respond API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.context.respond.async_respond

    -
    -
    -
    - -Expand source code - -
    from typing import Optional, Union, Sequence
    -from ssl import SSLContext
    -
    -from slack_sdk.models.attachments import Attachment
    -from slack_sdk.models.blocks import Block
    -from slack_sdk.webhook.async_client import AsyncWebhookClient, WebhookResponse
    -
    -from slack_bolt.context.respond.internals import _build_message
    -
    -
    -class AsyncRespond:
    -    response_url: Optional[str]
    -    proxy: Optional[str]
    -    ssl: Optional[SSLContext]
    -
    -    def __init__(
    -        self,
    -        *,
    -        response_url: Optional[str],
    -        proxy: Optional[str] = None,
    -        ssl: Optional[SSLContext] = None,
    -    ):
    -        self.response_url = response_url
    -        self.proxy = proxy
    -        self.ssl = ssl
    -
    -    async def __call__(
    -        self,
    -        text: Union[str, dict] = "",
    -        blocks: Optional[Sequence[Union[dict, Block]]] = None,
    -        attachments: Optional[Sequence[Union[dict, Attachment]]] = None,
    -        response_type: Optional[str] = None,
    -        replace_original: Optional[bool] = None,
    -        delete_original: Optional[bool] = None,
    -        unfurl_links: Optional[bool] = None,
    -        unfurl_media: Optional[bool] = None,
    -        thread_ts: Optional[str] = None,
    -    ) -> WebhookResponse:
    -        if self.response_url is not None:
    -            client = AsyncWebhookClient(
    -                url=self.response_url,
    -                proxy=self.proxy,
    -                ssl=self.ssl,
    -            )
    -            text_or_whole_response: Union[str, dict] = text
    -            if isinstance(text_or_whole_response, str):
    -                message = _build_message(
    -                    text=text,
    -                    blocks=blocks,
    -                    attachments=attachments,
    -                    response_type=response_type,
    -                    replace_original=replace_original,
    -                    delete_original=delete_original,
    -                    unfurl_links=unfurl_links,
    -                    unfurl_media=unfurl_media,
    -                    thread_ts=thread_ts,
    -                )
    -                return await client.send_dict(message)
    -            elif isinstance(text_or_whole_response, dict):
    -                whole_response: dict = text_or_whole_response
    -                message = _build_message(**whole_response)
    -                return await client.send_dict(message)
    -            else:
    -                raise ValueError(f"The arg is unexpected type ({type(text)})")
    -        else:
    -            raise ValueError("respond is unsupported here as there is no response_url")
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class AsyncRespond -(*, response_url:ย Optional[str], proxy:ย Optional[str]ย =ย None, ssl:ย Optional[ssl.SSLContext]ย =ย None) -
    -
    -
    -
    - -Expand source code - -
    class AsyncRespond:
    -    response_url: Optional[str]
    -    proxy: Optional[str]
    -    ssl: Optional[SSLContext]
    -
    -    def __init__(
    -        self,
    -        *,
    -        response_url: Optional[str],
    -        proxy: Optional[str] = None,
    -        ssl: Optional[SSLContext] = None,
    -    ):
    -        self.response_url = response_url
    -        self.proxy = proxy
    -        self.ssl = ssl
    -
    -    async def __call__(
    -        self,
    -        text: Union[str, dict] = "",
    -        blocks: Optional[Sequence[Union[dict, Block]]] = None,
    -        attachments: Optional[Sequence[Union[dict, Attachment]]] = None,
    -        response_type: Optional[str] = None,
    -        replace_original: Optional[bool] = None,
    -        delete_original: Optional[bool] = None,
    -        unfurl_links: Optional[bool] = None,
    -        unfurl_media: Optional[bool] = None,
    -        thread_ts: Optional[str] = None,
    -    ) -> WebhookResponse:
    -        if self.response_url is not None:
    -            client = AsyncWebhookClient(
    -                url=self.response_url,
    -                proxy=self.proxy,
    -                ssl=self.ssl,
    -            )
    -            text_or_whole_response: Union[str, dict] = text
    -            if isinstance(text_or_whole_response, str):
    -                message = _build_message(
    -                    text=text,
    -                    blocks=blocks,
    -                    attachments=attachments,
    -                    response_type=response_type,
    -                    replace_original=replace_original,
    -                    delete_original=delete_original,
    -                    unfurl_links=unfurl_links,
    -                    unfurl_media=unfurl_media,
    -                    thread_ts=thread_ts,
    -                )
    -                return await client.send_dict(message)
    -            elif isinstance(text_or_whole_response, dict):
    -                whole_response: dict = text_or_whole_response
    -                message = _build_message(**whole_response)
    -                return await client.send_dict(message)
    -            else:
    -                raise ValueError(f"The arg is unexpected type ({type(text)})")
    -        else:
    -            raise ValueError("respond is unsupported here as there is no response_url")
    -
    -

    Class variables

    -
    -
    var proxy :ย Optional[str]
    -
    -
    -
    -
    var response_url :ย Optional[str]
    -
    -
    -
    -
    var ssl :ย Optional[ssl.SSLContext]
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/context/respond/internals.html b/docs/api-docs/slack_bolt/context/respond/internals.html deleted file mode 100644 index 70ed92d79..000000000 --- a/docs/api-docs/slack_bolt/context/respond/internals.html +++ /dev/null @@ -1,95 +0,0 @@ - - - - - - -slack_bolt.context.respond.internals API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.context.respond.internals

    -
    -
    -
    - -Expand source code - -
    from typing import Optional, Dict, Union, Any, Sequence
    -
    -from slack_sdk.models.attachments import Attachment
    -from slack_sdk.models.blocks import Block
    -
    -from slack_bolt.util.utils import convert_to_dict_list
    -
    -
    -def _build_message(
    -    text: str = "",
    -    blocks: Optional[Sequence[Union[dict, Block]]] = None,
    -    attachments: Optional[Sequence[Union[dict, Attachment]]] = None,
    -    response_type: Optional[str] = None,
    -    replace_original: Optional[bool] = None,
    -    delete_original: Optional[bool] = None,
    -    unfurl_links: Optional[bool] = None,
    -    unfurl_media: Optional[bool] = None,
    -    thread_ts: Optional[str] = None,
    -) -> Dict[str, Any]:
    -    message = {"text": text}
    -    if blocks is not None and len(blocks) > 0:
    -        message["blocks"] = convert_to_dict_list(blocks)
    -    if attachments is not None and len(attachments) > 0:
    -        message["attachments"] = convert_to_dict_list(attachments)
    -    if response_type is not None:
    -        message["response_type"] = response_type
    -    if replace_original is not None:
    -        message["replace_original"] = replace_original
    -    if delete_original is not None:
    -        message["delete_original"] = delete_original
    -    if unfurl_links is not None:
    -        message["unfurl_links"] = unfurl_links
    -    if unfurl_media is not None:
    -        message["unfurl_media"] = unfurl_media
    -    if thread_ts is not None:
    -        message["thread_ts"] = thread_ts
    -    return message
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/context/respond/respond.html b/docs/api-docs/slack_bolt/context/respond/respond.html deleted file mode 100644 index 75d6b2bdf..000000000 --- a/docs/api-docs/slack_bolt/context/respond/respond.html +++ /dev/null @@ -1,222 +0,0 @@ - - - - - - -slack_bolt.context.respond.respond API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.context.respond.respond

    -
    -
    -
    - -Expand source code - -
    from typing import Optional, Union, Sequence
    -from ssl import SSLContext
    -
    -from slack_sdk.models.attachments import Attachment
    -from slack_sdk.models.blocks import Block
    -from slack_sdk.webhook import WebhookClient, WebhookResponse
    -
    -from slack_bolt.context.respond.internals import _build_message
    -
    -
    -class Respond:
    -    response_url: Optional[str]
    -    proxy: Optional[str]
    -    ssl: Optional[SSLContext]
    -
    -    def __init__(
    -        self,
    -        *,
    -        response_url: Optional[str],
    -        proxy: Optional[str] = None,
    -        ssl: Optional[SSLContext] = None,
    -    ):
    -        self.response_url = response_url
    -        self.proxy = proxy
    -        self.ssl = ssl
    -
    -    def __call__(
    -        self,
    -        text: Union[str, dict] = "",
    -        blocks: Optional[Sequence[Union[dict, Block]]] = None,
    -        attachments: Optional[Sequence[Union[dict, Attachment]]] = None,
    -        response_type: Optional[str] = None,
    -        replace_original: Optional[bool] = None,
    -        delete_original: Optional[bool] = None,
    -        unfurl_links: Optional[bool] = None,
    -        unfurl_media: Optional[bool] = None,
    -        thread_ts: Optional[str] = None,
    -    ) -> WebhookResponse:
    -        if self.response_url is not None:
    -            client = WebhookClient(
    -                url=self.response_url,
    -                proxy=self.proxy,
    -                ssl=self.ssl,
    -            )
    -            text_or_whole_response: Union[str, dict] = text
    -            if isinstance(text_or_whole_response, str):
    -                text = text_or_whole_response
    -                message = _build_message(
    -                    text=text,
    -                    blocks=blocks,
    -                    attachments=attachments,
    -                    response_type=response_type,
    -                    replace_original=replace_original,
    -                    delete_original=delete_original,
    -                    unfurl_links=unfurl_links,
    -                    unfurl_media=unfurl_media,
    -                    thread_ts=thread_ts,
    -                )
    -                return client.send_dict(message)
    -            elif isinstance(text_or_whole_response, dict):
    -                message = _build_message(**text_or_whole_response)
    -                return client.send_dict(message)
    -            else:
    -                raise ValueError(f"The arg is unexpected type ({type(text_or_whole_response)})")
    -        else:
    -            raise ValueError("respond is unsupported here as there is no response_url")
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class Respond -(*, response_url:ย Optional[str], proxy:ย Optional[str]ย =ย None, ssl:ย Optional[ssl.SSLContext]ย =ย None) -
    -
    -
    -
    - -Expand source code - -
    class Respond:
    -    response_url: Optional[str]
    -    proxy: Optional[str]
    -    ssl: Optional[SSLContext]
    -
    -    def __init__(
    -        self,
    -        *,
    -        response_url: Optional[str],
    -        proxy: Optional[str] = None,
    -        ssl: Optional[SSLContext] = None,
    -    ):
    -        self.response_url = response_url
    -        self.proxy = proxy
    -        self.ssl = ssl
    -
    -    def __call__(
    -        self,
    -        text: Union[str, dict] = "",
    -        blocks: Optional[Sequence[Union[dict, Block]]] = None,
    -        attachments: Optional[Sequence[Union[dict, Attachment]]] = None,
    -        response_type: Optional[str] = None,
    -        replace_original: Optional[bool] = None,
    -        delete_original: Optional[bool] = None,
    -        unfurl_links: Optional[bool] = None,
    -        unfurl_media: Optional[bool] = None,
    -        thread_ts: Optional[str] = None,
    -    ) -> WebhookResponse:
    -        if self.response_url is not None:
    -            client = WebhookClient(
    -                url=self.response_url,
    -                proxy=self.proxy,
    -                ssl=self.ssl,
    -            )
    -            text_or_whole_response: Union[str, dict] = text
    -            if isinstance(text_or_whole_response, str):
    -                text = text_or_whole_response
    -                message = _build_message(
    -                    text=text,
    -                    blocks=blocks,
    -                    attachments=attachments,
    -                    response_type=response_type,
    -                    replace_original=replace_original,
    -                    delete_original=delete_original,
    -                    unfurl_links=unfurl_links,
    -                    unfurl_media=unfurl_media,
    -                    thread_ts=thread_ts,
    -                )
    -                return client.send_dict(message)
    -            elif isinstance(text_or_whole_response, dict):
    -                message = _build_message(**text_or_whole_response)
    -                return client.send_dict(message)
    -            else:
    -                raise ValueError(f"The arg is unexpected type ({type(text_or_whole_response)})")
    -        else:
    -            raise ValueError("respond is unsupported here as there is no response_url")
    -
    -

    Class variables

    -
    -
    var proxy :ย Optional[str]
    -
    -
    -
    -
    var response_url :ย Optional[str]
    -
    -
    -
    -
    var ssl :ย Optional[ssl.SSLContext]
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/context/say/async_say.html b/docs/api-docs/slack_bolt/context/say/async_say.html deleted file mode 100644 index a4607968a..000000000 --- a/docs/api-docs/slack_bolt/context/say/async_say.html +++ /dev/null @@ -1,235 +0,0 @@ - - - - - - -slack_bolt.context.say.async_say API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.context.say.async_say

    -
    -
    -
    - -Expand source code - -
    from typing import Optional, Union, Dict, Sequence
    -
    -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.web.async_client import AsyncWebClient
    -from slack_sdk.web.async_slack_response import AsyncSlackResponse
    -
    -
    -class AsyncSay:
    -    client: Optional[AsyncWebClient]
    -    channel: Optional[str]
    -
    -    def __init__(
    -        self,
    -        client: Optional[AsyncWebClient],
    -        channel: Optional[str],
    -    ):
    -        self.client = client
    -        self.channel = channel
    -
    -    async def __call__(
    -        self,
    -        text: Union[str, dict] = "",
    -        blocks: Optional[Sequence[Union[Dict, Block]]] = None,
    -        attachments: Optional[Sequence[Union[Dict, Attachment]]] = None,
    -        channel: Optional[str] = None,
    -        as_user: Optional[bool] = None,
    -        thread_ts: Optional[str] = None,
    -        reply_broadcast: Optional[bool] = None,
    -        unfurl_links: Optional[bool] = None,
    -        unfurl_media: Optional[bool] = None,
    -        icon_emoji: Optional[str] = None,
    -        icon_url: Optional[str] = None,
    -        username: Optional[str] = None,
    -        mrkdwn: Optional[bool] = None,
    -        link_names: Optional[bool] = None,
    -        parse: Optional[str] = None,  # none, full
    -        metadata: Optional[Union[Dict, Metadata]] = None,
    -        **kwargs,
    -    ) -> AsyncSlackResponse:
    -        if _can_say(self, channel):
    -            text_or_whole_response: Union[str, dict] = text
    -            if isinstance(text_or_whole_response, str):
    -                text = text_or_whole_response
    -                return await self.client.chat_postMessage(
    -                    channel=channel or self.channel,
    -                    text=text,
    -                    blocks=blocks,
    -                    attachments=attachments,
    -                    as_user=as_user,
    -                    thread_ts=thread_ts,
    -                    reply_broadcast=reply_broadcast,
    -                    unfurl_links=unfurl_links,
    -                    unfurl_media=unfurl_media,
    -                    icon_emoji=icon_emoji,
    -                    icon_url=icon_url,
    -                    username=username,
    -                    mrkdwn=mrkdwn,
    -                    link_names=link_names,
    -                    parse=parse,
    -                    metadata=metadata,
    -                    **kwargs,
    -                )
    -            elif isinstance(text_or_whole_response, dict):
    -                message: dict = create_copy(text_or_whole_response)
    -                if "channel" not in message:
    -                    message["channel"] = channel or self.channel
    -                return await self.client.chat_postMessage(**message)
    -            else:
    -                raise ValueError(f"The arg is unexpected type ({type(text_or_whole_response)})")
    -        else:
    -            raise ValueError("say without channel_id here is unsupported")
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class AsyncSay -(client:ย Optional[slack_sdk.web.async_client.AsyncWebClient], channel:ย Optional[str]) -
    -
    -
    -
    - -Expand source code - -
    class AsyncSay:
    -    client: Optional[AsyncWebClient]
    -    channel: Optional[str]
    -
    -    def __init__(
    -        self,
    -        client: Optional[AsyncWebClient],
    -        channel: Optional[str],
    -    ):
    -        self.client = client
    -        self.channel = channel
    -
    -    async def __call__(
    -        self,
    -        text: Union[str, dict] = "",
    -        blocks: Optional[Sequence[Union[Dict, Block]]] = None,
    -        attachments: Optional[Sequence[Union[Dict, Attachment]]] = None,
    -        channel: Optional[str] = None,
    -        as_user: Optional[bool] = None,
    -        thread_ts: Optional[str] = None,
    -        reply_broadcast: Optional[bool] = None,
    -        unfurl_links: Optional[bool] = None,
    -        unfurl_media: Optional[bool] = None,
    -        icon_emoji: Optional[str] = None,
    -        icon_url: Optional[str] = None,
    -        username: Optional[str] = None,
    -        mrkdwn: Optional[bool] = None,
    -        link_names: Optional[bool] = None,
    -        parse: Optional[str] = None,  # none, full
    -        metadata: Optional[Union[Dict, Metadata]] = None,
    -        **kwargs,
    -    ) -> AsyncSlackResponse:
    -        if _can_say(self, channel):
    -            text_or_whole_response: Union[str, dict] = text
    -            if isinstance(text_or_whole_response, str):
    -                text = text_or_whole_response
    -                return await self.client.chat_postMessage(
    -                    channel=channel or self.channel,
    -                    text=text,
    -                    blocks=blocks,
    -                    attachments=attachments,
    -                    as_user=as_user,
    -                    thread_ts=thread_ts,
    -                    reply_broadcast=reply_broadcast,
    -                    unfurl_links=unfurl_links,
    -                    unfurl_media=unfurl_media,
    -                    icon_emoji=icon_emoji,
    -                    icon_url=icon_url,
    -                    username=username,
    -                    mrkdwn=mrkdwn,
    -                    link_names=link_names,
    -                    parse=parse,
    -                    metadata=metadata,
    -                    **kwargs,
    -                )
    -            elif isinstance(text_or_whole_response, dict):
    -                message: dict = create_copy(text_or_whole_response)
    -                if "channel" not in message:
    -                    message["channel"] = channel or self.channel
    -                return await self.client.chat_postMessage(**message)
    -            else:
    -                raise ValueError(f"The arg is unexpected type ({type(text_or_whole_response)})")
    -        else:
    -            raise ValueError("say without channel_id here is unsupported")
    -
    -

    Class variables

    -
    -
    var channel :ย Optional[str]
    -
    -
    -
    -
    var client :ย Optional[slack_sdk.web.async_client.AsyncWebClient]
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/context/say/index.html b/docs/api-docs/slack_bolt/context/say/index.html deleted file mode 100644 index 801d7f06a..000000000 --- a/docs/api-docs/slack_bolt/context/say/index.html +++ /dev/null @@ -1,187 +0,0 @@ - - - - - - -slack_bolt.context.say API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.context.say

    -
    -
    -
    - -Expand source code - -
    # Don't add async module imports here
    -from .say import Say
    -
    -__all__ = [
    -    "Say",
    -]
    -
    -
    -
    -

    Sub-modules

    -
    -
    slack_bolt.context.say.async_say
    -
    -
    -
    -
    slack_bolt.context.say.internals
    -
    -
    -
    -
    slack_bolt.context.say.say
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class Say -(client:ย Optional[slack_sdk.web.client.WebClient], channel:ย Optional[str]) -
    -
    -
    -
    - -Expand source code - -
    class Say:
    -    client: Optional[WebClient]
    -    channel: Optional[str]
    -
    -    def __init__(
    -        self,
    -        client: Optional[WebClient],
    -        channel: Optional[str],
    -    ):
    -        self.client = client
    -        self.channel = channel
    -
    -    def __call__(
    -        self,
    -        text: Union[str, dict] = "",
    -        blocks: Optional[Sequence[Union[Dict, Block]]] = None,
    -        attachments: Optional[Sequence[Union[Dict, Attachment]]] = None,
    -        channel: Optional[str] = None,
    -        as_user: Optional[bool] = None,
    -        thread_ts: Optional[str] = None,
    -        reply_broadcast: Optional[bool] = None,
    -        unfurl_links: Optional[bool] = None,
    -        unfurl_media: Optional[bool] = None,
    -        icon_emoji: Optional[str] = None,
    -        icon_url: Optional[str] = None,
    -        username: Optional[str] = None,
    -        mrkdwn: Optional[bool] = None,
    -        link_names: Optional[bool] = None,
    -        parse: Optional[str] = None,  # none, full
    -        metadata: Optional[Union[Dict, Metadata]] = None,
    -        **kwargs,
    -    ) -> SlackResponse:
    -        if _can_say(self, channel):
    -            text_or_whole_response: Union[str, dict] = text
    -            if isinstance(text_or_whole_response, str):
    -                text = text_or_whole_response
    -                return self.client.chat_postMessage(
    -                    channel=channel or self.channel,
    -                    text=text,
    -                    blocks=blocks,
    -                    attachments=attachments,
    -                    as_user=as_user,
    -                    thread_ts=thread_ts,
    -                    reply_broadcast=reply_broadcast,
    -                    unfurl_links=unfurl_links,
    -                    unfurl_media=unfurl_media,
    -                    icon_emoji=icon_emoji,
    -                    icon_url=icon_url,
    -                    username=username,
    -                    mrkdwn=mrkdwn,
    -                    link_names=link_names,
    -                    parse=parse,
    -                    metadata=metadata,
    -                    **kwargs,
    -                )
    -            elif isinstance(text_or_whole_response, dict):
    -                message: dict = create_copy(text_or_whole_response)
    -                if "channel" not in message:
    -                    message["channel"] = channel or self.channel
    -                return self.client.chat_postMessage(**message)
    -            else:
    -                raise ValueError(f"The arg is unexpected type ({type(text_or_whole_response)})")
    -        else:
    -            raise ValueError("say without channel_id here is unsupported")
    -
    -

    Class variables

    -
    -
    var channel :ย Optional[str]
    -
    -
    -
    -
    var client :ย Optional[slack_sdk.web.client.WebClient]
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/context/say/internals.html b/docs/api-docs/slack_bolt/context/say/internals.html deleted file mode 100644 index d238d220c..000000000 --- a/docs/api-docs/slack_bolt/context/say/internals.html +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - -slack_bolt.context.say.internals API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.context.say.internals

    -
    -
    -
    - -Expand source code - -
    from typing import Optional, Any
    -
    -
    -def _can_say(self: Any, channel: Optional[str]) -> bool:
    -    return hasattr(self, "client") and self.client is not None and (channel or self.channel) is not None
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/context/say/say.html b/docs/api-docs/slack_bolt/context/say/say.html deleted file mode 100644 index 12edcbbea..000000000 --- a/docs/api-docs/slack_bolt/context/say/say.html +++ /dev/null @@ -1,235 +0,0 @@ - - - - - - -slack_bolt.context.say.say API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.context.say.say

    -
    -
    -
    - -Expand source code - -
    from typing import Optional, Union, Dict, Sequence
    -
    -from slack_sdk import WebClient
    -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 import SlackResponse
    -
    -from slack_bolt.context.say.internals import _can_say
    -from slack_bolt.util.utils import create_copy
    -
    -
    -class Say:
    -    client: Optional[WebClient]
    -    channel: Optional[str]
    -
    -    def __init__(
    -        self,
    -        client: Optional[WebClient],
    -        channel: Optional[str],
    -    ):
    -        self.client = client
    -        self.channel = channel
    -
    -    def __call__(
    -        self,
    -        text: Union[str, dict] = "",
    -        blocks: Optional[Sequence[Union[Dict, Block]]] = None,
    -        attachments: Optional[Sequence[Union[Dict, Attachment]]] = None,
    -        channel: Optional[str] = None,
    -        as_user: Optional[bool] = None,
    -        thread_ts: Optional[str] = None,
    -        reply_broadcast: Optional[bool] = None,
    -        unfurl_links: Optional[bool] = None,
    -        unfurl_media: Optional[bool] = None,
    -        icon_emoji: Optional[str] = None,
    -        icon_url: Optional[str] = None,
    -        username: Optional[str] = None,
    -        mrkdwn: Optional[bool] = None,
    -        link_names: Optional[bool] = None,
    -        parse: Optional[str] = None,  # none, full
    -        metadata: Optional[Union[Dict, Metadata]] = None,
    -        **kwargs,
    -    ) -> SlackResponse:
    -        if _can_say(self, channel):
    -            text_or_whole_response: Union[str, dict] = text
    -            if isinstance(text_or_whole_response, str):
    -                text = text_or_whole_response
    -                return self.client.chat_postMessage(
    -                    channel=channel or self.channel,
    -                    text=text,
    -                    blocks=blocks,
    -                    attachments=attachments,
    -                    as_user=as_user,
    -                    thread_ts=thread_ts,
    -                    reply_broadcast=reply_broadcast,
    -                    unfurl_links=unfurl_links,
    -                    unfurl_media=unfurl_media,
    -                    icon_emoji=icon_emoji,
    -                    icon_url=icon_url,
    -                    username=username,
    -                    mrkdwn=mrkdwn,
    -                    link_names=link_names,
    -                    parse=parse,
    -                    metadata=metadata,
    -                    **kwargs,
    -                )
    -            elif isinstance(text_or_whole_response, dict):
    -                message: dict = create_copy(text_or_whole_response)
    -                if "channel" not in message:
    -                    message["channel"] = channel or self.channel
    -                return self.client.chat_postMessage(**message)
    -            else:
    -                raise ValueError(f"The arg is unexpected type ({type(text_or_whole_response)})")
    -        else:
    -            raise ValueError("say without channel_id here is unsupported")
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class Say -(client:ย Optional[slack_sdk.web.client.WebClient], channel:ย Optional[str]) -
    -
    -
    -
    - -Expand source code - -
    class Say:
    -    client: Optional[WebClient]
    -    channel: Optional[str]
    -
    -    def __init__(
    -        self,
    -        client: Optional[WebClient],
    -        channel: Optional[str],
    -    ):
    -        self.client = client
    -        self.channel = channel
    -
    -    def __call__(
    -        self,
    -        text: Union[str, dict] = "",
    -        blocks: Optional[Sequence[Union[Dict, Block]]] = None,
    -        attachments: Optional[Sequence[Union[Dict, Attachment]]] = None,
    -        channel: Optional[str] = None,
    -        as_user: Optional[bool] = None,
    -        thread_ts: Optional[str] = None,
    -        reply_broadcast: Optional[bool] = None,
    -        unfurl_links: Optional[bool] = None,
    -        unfurl_media: Optional[bool] = None,
    -        icon_emoji: Optional[str] = None,
    -        icon_url: Optional[str] = None,
    -        username: Optional[str] = None,
    -        mrkdwn: Optional[bool] = None,
    -        link_names: Optional[bool] = None,
    -        parse: Optional[str] = None,  # none, full
    -        metadata: Optional[Union[Dict, Metadata]] = None,
    -        **kwargs,
    -    ) -> SlackResponse:
    -        if _can_say(self, channel):
    -            text_or_whole_response: Union[str, dict] = text
    -            if isinstance(text_or_whole_response, str):
    -                text = text_or_whole_response
    -                return self.client.chat_postMessage(
    -                    channel=channel or self.channel,
    -                    text=text,
    -                    blocks=blocks,
    -                    attachments=attachments,
    -                    as_user=as_user,
    -                    thread_ts=thread_ts,
    -                    reply_broadcast=reply_broadcast,
    -                    unfurl_links=unfurl_links,
    -                    unfurl_media=unfurl_media,
    -                    icon_emoji=icon_emoji,
    -                    icon_url=icon_url,
    -                    username=username,
    -                    mrkdwn=mrkdwn,
    -                    link_names=link_names,
    -                    parse=parse,
    -                    metadata=metadata,
    -                    **kwargs,
    -                )
    -            elif isinstance(text_or_whole_response, dict):
    -                message: dict = create_copy(text_or_whole_response)
    -                if "channel" not in message:
    -                    message["channel"] = channel or self.channel
    -                return self.client.chat_postMessage(**message)
    -            else:
    -                raise ValueError(f"The arg is unexpected type ({type(text_or_whole_response)})")
    -        else:
    -            raise ValueError("say without channel_id here is unsupported")
    -
    -

    Class variables

    -
    -
    var channel :ย Optional[str]
    -
    -
    -
    -
    var client :ย Optional[slack_sdk.web.client.WebClient]
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/error/index.html b/docs/api-docs/slack_bolt/error/index.html deleted file mode 100644 index fcc4de3fe..000000000 --- a/docs/api-docs/slack_bolt/error/index.html +++ /dev/null @@ -1,186 +0,0 @@ - - - - - - -slack_bolt.error API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.error

    -
    -
    -

    Bolt specific error types.

    -
    - -Expand source code - -
    """Bolt specific error types."""
    -from typing import Optional, Union
    -
    -
    -class BoltError(Exception):
    -    """General class in a Bolt app"""
    -
    -
    -class BoltUnhandledRequestError(BoltError):
    -    request: "BoltRequest"  # type: ignore
    -    body: dict
    -    current_response: Optional["BoltResponse"]  # type: ignore
    -    last_global_middleware_name: Optional[str]
    -
    -    def __init__(  # type: ignore
    -        self,
    -        *,
    -        request: Union["BoltRequest", "AsyncBoltRequest"],  # type: ignore
    -        current_response: Optional["BoltResponse"],  # type: ignore
    -        last_global_middleware_name: Optional[str] = None,
    -    ):
    -        self.request = request
    -        self.body = request.body if request is not None else {}
    -        self.current_response = current_response
    -        self.last_global_middleware_name = last_global_middleware_name
    -
    -    def __str__(self) -> str:
    -        return "unhandled request error"
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class BoltError -(*args, **kwargs) -
    -
    -

    General class in a Bolt app

    -
    - -Expand source code - -
    class BoltError(Exception):
    -    """General class in a Bolt app"""
    -
    -

    Ancestors

    -
      -
    • builtins.Exception
    • -
    • builtins.BaseException
    • -
    -

    Subclasses

    - -
    -
    -class BoltUnhandledRequestError -(*, request:ย Union[ForwardRef('BoltRequest'),ย ForwardRef('AsyncBoltRequest')], current_response:ย Optional[ForwardRef('BoltResponse')], last_global_middleware_name:ย Optional[str]ย =ย None) -
    -
    -

    General class in a Bolt app

    -
    - -Expand source code - -
    class BoltUnhandledRequestError(BoltError):
    -    request: "BoltRequest"  # type: ignore
    -    body: dict
    -    current_response: Optional["BoltResponse"]  # type: ignore
    -    last_global_middleware_name: Optional[str]
    -
    -    def __init__(  # type: ignore
    -        self,
    -        *,
    -        request: Union["BoltRequest", "AsyncBoltRequest"],  # type: ignore
    -        current_response: Optional["BoltResponse"],  # type: ignore
    -        last_global_middleware_name: Optional[str] = None,
    -    ):
    -        self.request = request
    -        self.body = request.body if request is not None else {}
    -        self.current_response = current_response
    -        self.last_global_middleware_name = last_global_middleware_name
    -
    -    def __str__(self) -> str:
    -        return "unhandled request error"
    -
    -

    Ancestors

    -
      -
    • BoltError
    • -
    • builtins.Exception
    • -
    • builtins.BaseException
    • -
    -

    Class variables

    -
    -
    var body :ย dict
    -
    -
    -
    -
    var current_response :ย Optional[BoltResponse]
    -
    -
    -
    -
    var last_global_middleware_name :ย Optional[str]
    -
    -
    -
    -
    var request :ย BoltRequest
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/kwargs_injection/async_utils.html b/docs/api-docs/slack_bolt/kwargs_injection/async_utils.html deleted file mode 100644 index defdd3f5a..000000000 --- a/docs/api-docs/slack_bolt/kwargs_injection/async_utils.html +++ /dev/null @@ -1,268 +0,0 @@ - - - - - - -slack_bolt.kwargs_injection.async_utils API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.kwargs_injection.async_utils

    -
    -
    -
    - -Expand source code - -
    # pytype: skip-file
    -import inspect
    -import logging
    -from typing import Callable, Dict, Optional, Any, Sequence
    -
    -from slack_bolt.request.async_request import AsyncBoltRequest
    -from slack_bolt.response import BoltResponse
    -from .async_args import AsyncArgs
    -from slack_bolt.request.payload_utils import (
    -    to_options,
    -    to_shortcut,
    -    to_action,
    -    to_view,
    -    to_command,
    -    to_event,
    -    to_message,
    -    to_step,
    -)
    -from ..logger.messages import warning_skip_uncommon_arg_name
    -
    -
    -def build_async_required_kwargs(
    -    *,
    -    logger: logging.Logger,
    -    required_arg_names: Sequence[str],
    -    request: AsyncBoltRequest,
    -    response: Optional[BoltResponse],
    -    next_func: Callable[[], None] = None,
    -    this_func: Optional[Callable] = None,
    -    error: Optional[Exception] = None,  # for error handlers
    -    next_keys_required: bool = True,  # False for listeners / middleware / error handlers
    -) -> Dict[str, Any]:
    -    all_available_args = {
    -        "logger": logger,
    -        "client": request.context.client,
    -        "req": request,
    -        "request": request,
    -        "resp": response,
    -        "response": response,
    -        "context": request.context,
    -        "body": request.body,
    -        # payload
    -        "options": to_options(request.body),
    -        "shortcut": to_shortcut(request.body),
    -        "action": to_action(request.body),
    -        "view": to_view(request.body),
    -        "command": to_command(request.body),
    -        "event": to_event(request.body),
    -        "message": to_message(request.body),
    -        "step": to_step(request.body),
    -        # utilities
    -        "ack": request.context.ack,
    -        "say": request.context.say,
    -        "respond": request.context.respond,
    -        # middleware
    -        "next": next_func,
    -        "next_": next_func,  # for the middleware using Python's built-in `next()` function
    -        # error handler
    -        "error": error,  # Exception
    -    }
    -    if not next_keys_required:
    -        all_available_args.pop("next")
    -        all_available_args.pop("next_")
    -
    -    all_available_args["payload"] = (
    -        all_available_args["options"]
    -        or all_available_args["shortcut"]
    -        or all_available_args["action"]
    -        or all_available_args["view"]
    -        or all_available_args["command"]
    -        or all_available_args["event"]
    -        or all_available_args["message"]
    -        or all_available_args["step"]
    -        or request.body
    -    )
    -    for k, v in request.context.items():
    -        if k not in all_available_args:
    -            all_available_args[k] = v
    -
    -    if len(required_arg_names) > 0:
    -        # To support instance/class methods in a class for listeners/middleware,
    -        # check if the first argument is either self or cls
    -        first_arg_name = required_arg_names[0]
    -        if first_arg_name in {"self", "cls"}:
    -            required_arg_names.pop(0)
    -        elif first_arg_name not in all_available_args.keys() and first_arg_name != "args":
    -            if this_func is None:
    -                logger.warning(warning_skip_uncommon_arg_name(first_arg_name))
    -                required_arg_names.pop(0)
    -            elif inspect.ismethod(this_func):
    -                # We are sure that we should skip manipulating this arg
    -                required_arg_names.pop(0)
    -
    -    kwargs: Dict[str, Any] = {k: v for k, v in all_available_args.items() if k in required_arg_names}
    -    found_arg_names = kwargs.keys()
    -    for name in required_arg_names:
    -        if name == "args":
    -            if isinstance(request, AsyncBoltRequest):
    -                kwargs[name] = AsyncArgs(**all_available_args)
    -            else:
    -                logger.warning(f"Unknown Request object type detected ({type(request)})")
    -
    -        elif name not in found_arg_names:
    -            logger.warning(f"{name} is not a valid argument")
    -            kwargs[name] = None
    -    return kwargs
    -
    -
    -
    -
    -
    -
    -
    -

    Functions

    -
    -
    -def build_async_required_kwargs(*, logger:ย logging.Logger, required_arg_names:ย Sequence[str], request:ย AsyncBoltRequest, response:ย Optional[BoltResponse], next_func:ย Callable[[],ย None]ย =ย None, this_func:ย Optional[Callable]ย =ย None, error:ย Optional[Exception]ย =ย None, next_keys_required:ย boolย =ย True) โ€‘>ย Dict[str,ย Any] -
    -
    -
    -
    - -Expand source code - -
    def build_async_required_kwargs(
    -    *,
    -    logger: logging.Logger,
    -    required_arg_names: Sequence[str],
    -    request: AsyncBoltRequest,
    -    response: Optional[BoltResponse],
    -    next_func: Callable[[], None] = None,
    -    this_func: Optional[Callable] = None,
    -    error: Optional[Exception] = None,  # for error handlers
    -    next_keys_required: bool = True,  # False for listeners / middleware / error handlers
    -) -> Dict[str, Any]:
    -    all_available_args = {
    -        "logger": logger,
    -        "client": request.context.client,
    -        "req": request,
    -        "request": request,
    -        "resp": response,
    -        "response": response,
    -        "context": request.context,
    -        "body": request.body,
    -        # payload
    -        "options": to_options(request.body),
    -        "shortcut": to_shortcut(request.body),
    -        "action": to_action(request.body),
    -        "view": to_view(request.body),
    -        "command": to_command(request.body),
    -        "event": to_event(request.body),
    -        "message": to_message(request.body),
    -        "step": to_step(request.body),
    -        # utilities
    -        "ack": request.context.ack,
    -        "say": request.context.say,
    -        "respond": request.context.respond,
    -        # middleware
    -        "next": next_func,
    -        "next_": next_func,  # for the middleware using Python's built-in `next()` function
    -        # error handler
    -        "error": error,  # Exception
    -    }
    -    if not next_keys_required:
    -        all_available_args.pop("next")
    -        all_available_args.pop("next_")
    -
    -    all_available_args["payload"] = (
    -        all_available_args["options"]
    -        or all_available_args["shortcut"]
    -        or all_available_args["action"]
    -        or all_available_args["view"]
    -        or all_available_args["command"]
    -        or all_available_args["event"]
    -        or all_available_args["message"]
    -        or all_available_args["step"]
    -        or request.body
    -    )
    -    for k, v in request.context.items():
    -        if k not in all_available_args:
    -            all_available_args[k] = v
    -
    -    if len(required_arg_names) > 0:
    -        # To support instance/class methods in a class for listeners/middleware,
    -        # check if the first argument is either self or cls
    -        first_arg_name = required_arg_names[0]
    -        if first_arg_name in {"self", "cls"}:
    -            required_arg_names.pop(0)
    -        elif first_arg_name not in all_available_args.keys() and first_arg_name != "args":
    -            if this_func is None:
    -                logger.warning(warning_skip_uncommon_arg_name(first_arg_name))
    -                required_arg_names.pop(0)
    -            elif inspect.ismethod(this_func):
    -                # We are sure that we should skip manipulating this arg
    -                required_arg_names.pop(0)
    -
    -    kwargs: Dict[str, Any] = {k: v for k, v in all_available_args.items() if k in required_arg_names}
    -    found_arg_names = kwargs.keys()
    -    for name in required_arg_names:
    -        if name == "args":
    -            if isinstance(request, AsyncBoltRequest):
    -                kwargs[name] = AsyncArgs(**all_available_args)
    -            else:
    -                logger.warning(f"Unknown Request object type detected ({type(request)})")
    -
    -        elif name not in found_arg_names:
    -            logger.warning(f"{name} is not a valid argument")
    -            kwargs[name] = None
    -    return kwargs
    -
    -
    -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/kwargs_injection/utils.html b/docs/api-docs/slack_bolt/kwargs_injection/utils.html deleted file mode 100644 index 6fee6e4fc..000000000 --- a/docs/api-docs/slack_bolt/kwargs_injection/utils.html +++ /dev/null @@ -1,268 +0,0 @@ - - - - - - -slack_bolt.kwargs_injection.utils API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.kwargs_injection.utils

    -
    -
    -
    - -Expand source code - -
    # pytype: skip-file
    -import inspect
    -import logging
    -from typing import Callable, Dict, Optional, Any, Sequence
    -
    -from slack_bolt.request import BoltRequest
    -from slack_bolt.response import BoltResponse
    -from .args import Args
    -from slack_bolt.request.payload_utils import (
    -    to_options,
    -    to_shortcut,
    -    to_action,
    -    to_view,
    -    to_command,
    -    to_event,
    -    to_message,
    -    to_step,
    -)
    -from ..logger.messages import warning_skip_uncommon_arg_name
    -
    -
    -def build_required_kwargs(
    -    *,
    -    logger: logging.Logger,
    -    required_arg_names: Sequence[str],
    -    request: BoltRequest,
    -    response: Optional[BoltResponse],
    -    next_func: Callable[[], None] = None,
    -    this_func: Optional[Callable] = None,
    -    error: Optional[Exception] = None,  # for error handlers
    -    next_keys_required: bool = True,  # False for listeners / middleware / error handlers
    -) -> Dict[str, Any]:
    -    all_available_args = {
    -        "logger": logger,
    -        "client": request.context.client,
    -        "req": request,
    -        "request": request,
    -        "resp": response,
    -        "response": response,
    -        "context": request.context,
    -        # payload
    -        "body": request.body,
    -        "options": to_options(request.body),
    -        "shortcut": to_shortcut(request.body),
    -        "action": to_action(request.body),
    -        "view": to_view(request.body),
    -        "command": to_command(request.body),
    -        "event": to_event(request.body),
    -        "message": to_message(request.body),
    -        "step": to_step(request.body),
    -        # utilities
    -        "ack": request.context.ack,
    -        "say": request.context.say,
    -        "respond": request.context.respond,
    -        # middleware
    -        "next": next_func,
    -        "next_": next_func,  # for the middleware using Python's built-in `next()` function
    -        # error handler
    -        "error": error,  # Exception
    -    }
    -    if not next_keys_required:
    -        all_available_args.pop("next")
    -        all_available_args.pop("next_")
    -
    -    all_available_args["payload"] = (
    -        all_available_args["options"]
    -        or all_available_args["shortcut"]
    -        or all_available_args["action"]
    -        or all_available_args["view"]
    -        or all_available_args["command"]
    -        or all_available_args["event"]
    -        or all_available_args["message"]
    -        or all_available_args["step"]
    -        or request.body
    -    )
    -    for k, v in request.context.items():
    -        if k not in all_available_args:
    -            all_available_args[k] = v
    -
    -    if len(required_arg_names) > 0:
    -        # To support instance/class methods in a class for listeners/middleware,
    -        # check if the first argument is either self or cls
    -        first_arg_name = required_arg_names[0]
    -        if first_arg_name in {"self", "cls"}:
    -            required_arg_names.pop(0)
    -        elif first_arg_name not in all_available_args.keys() and first_arg_name != "args":
    -            if this_func is None:
    -                logger.warning(warning_skip_uncommon_arg_name(first_arg_name))
    -                required_arg_names.pop(0)
    -            elif inspect.ismethod(this_func):
    -                # We are sure that we should skip manipulating this arg
    -                required_arg_names.pop(0)
    -
    -    kwargs: Dict[str, Any] = {k: v for k, v in all_available_args.items() if k in required_arg_names}
    -    found_arg_names = kwargs.keys()
    -    for name in required_arg_names:
    -        if name == "args":
    -            if isinstance(request, BoltRequest):
    -                kwargs[name] = Args(**all_available_args)
    -            else:
    -                logger.warning(f"Unknown Request object type detected ({type(request)})")
    -
    -        elif name not in found_arg_names:
    -            logger.warning(f"{name} is not a valid argument")
    -            kwargs[name] = None
    -    return kwargs
    -
    -
    -
    -
    -
    -
    -
    -

    Functions

    -
    -
    -def build_required_kwargs(*, logger:ย logging.Logger, required_arg_names:ย Sequence[str], request:ย BoltRequest, response:ย Optional[BoltResponse], next_func:ย Callable[[],ย None]ย =ย None, this_func:ย Optional[Callable]ย =ย None, error:ย Optional[Exception]ย =ย None, next_keys_required:ย boolย =ย True) โ€‘>ย Dict[str,ย Any] -
    -
    -
    -
    - -Expand source code - -
    def build_required_kwargs(
    -    *,
    -    logger: logging.Logger,
    -    required_arg_names: Sequence[str],
    -    request: BoltRequest,
    -    response: Optional[BoltResponse],
    -    next_func: Callable[[], None] = None,
    -    this_func: Optional[Callable] = None,
    -    error: Optional[Exception] = None,  # for error handlers
    -    next_keys_required: bool = True,  # False for listeners / middleware / error handlers
    -) -> Dict[str, Any]:
    -    all_available_args = {
    -        "logger": logger,
    -        "client": request.context.client,
    -        "req": request,
    -        "request": request,
    -        "resp": response,
    -        "response": response,
    -        "context": request.context,
    -        # payload
    -        "body": request.body,
    -        "options": to_options(request.body),
    -        "shortcut": to_shortcut(request.body),
    -        "action": to_action(request.body),
    -        "view": to_view(request.body),
    -        "command": to_command(request.body),
    -        "event": to_event(request.body),
    -        "message": to_message(request.body),
    -        "step": to_step(request.body),
    -        # utilities
    -        "ack": request.context.ack,
    -        "say": request.context.say,
    -        "respond": request.context.respond,
    -        # middleware
    -        "next": next_func,
    -        "next_": next_func,  # for the middleware using Python's built-in `next()` function
    -        # error handler
    -        "error": error,  # Exception
    -    }
    -    if not next_keys_required:
    -        all_available_args.pop("next")
    -        all_available_args.pop("next_")
    -
    -    all_available_args["payload"] = (
    -        all_available_args["options"]
    -        or all_available_args["shortcut"]
    -        or all_available_args["action"]
    -        or all_available_args["view"]
    -        or all_available_args["command"]
    -        or all_available_args["event"]
    -        or all_available_args["message"]
    -        or all_available_args["step"]
    -        or request.body
    -    )
    -    for k, v in request.context.items():
    -        if k not in all_available_args:
    -            all_available_args[k] = v
    -
    -    if len(required_arg_names) > 0:
    -        # To support instance/class methods in a class for listeners/middleware,
    -        # check if the first argument is either self or cls
    -        first_arg_name = required_arg_names[0]
    -        if first_arg_name in {"self", "cls"}:
    -            required_arg_names.pop(0)
    -        elif first_arg_name not in all_available_args.keys() and first_arg_name != "args":
    -            if this_func is None:
    -                logger.warning(warning_skip_uncommon_arg_name(first_arg_name))
    -                required_arg_names.pop(0)
    -            elif inspect.ismethod(this_func):
    -                # We are sure that we should skip manipulating this arg
    -                required_arg_names.pop(0)
    -
    -    kwargs: Dict[str, Any] = {k: v for k, v in all_available_args.items() if k in required_arg_names}
    -    found_arg_names = kwargs.keys()
    -    for name in required_arg_names:
    -        if name == "args":
    -            if isinstance(request, BoltRequest):
    -                kwargs[name] = Args(**all_available_args)
    -            else:
    -                logger.warning(f"Unknown Request object type detected ({type(request)})")
    -
    -        elif name not in found_arg_names:
    -            logger.warning(f"{name} is not a valid argument")
    -            kwargs[name] = None
    -    return kwargs
    -
    -
    -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/lazy_listener/async_internals.html b/docs/api-docs/slack_bolt/lazy_listener/async_internals.html deleted file mode 100644 index 1c401af9f..000000000 --- a/docs/api-docs/slack_bolt/lazy_listener/async_internals.html +++ /dev/null @@ -1,132 +0,0 @@ - - - - - - -slack_bolt.lazy_listener.async_internals API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.lazy_listener.async_internals

    -
    -
    -
    - -Expand source code - -
    from functools import wraps
    -from logging import Logger
    -from typing import Callable, Awaitable
    -
    -from slack_bolt.kwargs_injection.async_utils import build_async_required_kwargs
    -from slack_bolt.request.async_request import AsyncBoltRequest
    -from slack_bolt.util.utils import get_arg_names_of_callable
    -
    -
    -async def to_runnable_function(
    -    internal_func: Callable[..., Awaitable[None]],
    -    logger: Logger,
    -    request: AsyncBoltRequest,
    -):
    -    arg_names = get_arg_names_of_callable(internal_func)
    -
    -    @wraps(internal_func)
    -    async def request_wired_wrapper() -> None:
    -        try:
    -            await internal_func(
    -                **build_async_required_kwargs(
    -                    logger=logger,
    -                    required_arg_names=arg_names,
    -                    request=request,
    -                    response=None,
    -                    this_func=internal_func,
    -                )
    -            )
    -        except Exception as e:
    -            logger.error(f"Failed to run an internal function ({e})")
    -
    -    return await request_wired_wrapper()
    -
    -
    -
    -
    -
    -
    -
    -

    Functions

    -
    -
    -async def to_runnable_function(internal_func:ย Callable[...,ย Awaitable[None]], logger:ย logging.Logger, request:ย AsyncBoltRequest) -
    -
    -
    -
    - -Expand source code - -
    async def to_runnable_function(
    -    internal_func: Callable[..., Awaitable[None]],
    -    logger: Logger,
    -    request: AsyncBoltRequest,
    -):
    -    arg_names = get_arg_names_of_callable(internal_func)
    -
    -    @wraps(internal_func)
    -    async def request_wired_wrapper() -> None:
    -        try:
    -            await internal_func(
    -                **build_async_required_kwargs(
    -                    logger=logger,
    -                    required_arg_names=arg_names,
    -                    request=request,
    -                    response=None,
    -                    this_func=internal_func,
    -                )
    -            )
    -        except Exception as e:
    -            logger.error(f"Failed to run an internal function ({e})")
    -
    -    return await request_wired_wrapper()
    -
    -
    -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/lazy_listener/asyncio_runner.html b/docs/api-docs/slack_bolt/lazy_listener/asyncio_runner.html deleted file mode 100644 index 73b5c887c..000000000 --- a/docs/api-docs/slack_bolt/lazy_listener/asyncio_runner.html +++ /dev/null @@ -1,146 +0,0 @@ - - - - - - -slack_bolt.lazy_listener.asyncio_runner API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.lazy_listener.asyncio_runner

    -
    -
    -
    - -Expand source code - -
    import asyncio
    -from logging import Logger
    -from typing import Callable, Awaitable
    -
    -from slack_bolt.lazy_listener.async_internals import to_runnable_function
    -from slack_bolt.lazy_listener.async_runner import AsyncLazyListenerRunner
    -from slack_bolt.request.async_request import AsyncBoltRequest
    -
    -
    -class AsyncioLazyListenerRunner(AsyncLazyListenerRunner):
    -    logger: Logger
    -
    -    def __init__(
    -        self,
    -        logger: Logger,
    -    ):
    -        self.logger = logger
    -
    -    def start(self, function: Callable[..., Awaitable[None]], request: AsyncBoltRequest) -> None:
    -        asyncio.ensure_future(
    -            to_runnable_function(
    -                internal_func=function,
    -                logger=self.logger,
    -                request=request,
    -            )
    -        )
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class AsyncioLazyListenerRunner -(logger:ย logging.Logger) -
    -
    -
    -
    - -Expand source code - -
    class AsyncioLazyListenerRunner(AsyncLazyListenerRunner):
    -    logger: Logger
    -
    -    def __init__(
    -        self,
    -        logger: Logger,
    -    ):
    -        self.logger = logger
    -
    -    def start(self, function: Callable[..., Awaitable[None]], request: AsyncBoltRequest) -> None:
    -        asyncio.ensure_future(
    -            to_runnable_function(
    -                internal_func=function,
    -                logger=self.logger,
    -                request=request,
    -            )
    -        )
    -
    -

    Ancestors

    - -

    Class variables

    -
    -
    var logger :ย logging.Logger
    -
    -
    -
    -
    -

    Inherited members

    - -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/lazy_listener/internals.html b/docs/api-docs/slack_bolt/lazy_listener/internals.html deleted file mode 100644 index 474e0b5f5..000000000 --- a/docs/api-docs/slack_bolt/lazy_listener/internals.html +++ /dev/null @@ -1,132 +0,0 @@ - - - - - - -slack_bolt.lazy_listener.internals API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.lazy_listener.internals

    -
    -
    -
    - -Expand source code - -
    from functools import wraps
    -from logging import Logger
    -from typing import Callable
    -
    -from slack_bolt.kwargs_injection import build_required_kwargs
    -from slack_bolt.request import BoltRequest
    -from slack_bolt.util.utils import get_arg_names_of_callable
    -
    -
    -def build_runnable_function(
    -    func: Callable[..., None],
    -    logger: Logger,
    -    request: BoltRequest,
    -) -> Callable[[], None]:
    -    arg_names = get_arg_names_of_callable(func)
    -
    -    @wraps(func)
    -    def request_wired_func_wrapper() -> None:
    -        try:
    -            func(
    -                **build_required_kwargs(
    -                    logger=logger,
    -                    required_arg_names=arg_names,
    -                    request=request,
    -                    response=None,
    -                    this_func=func,
    -                )
    -            )
    -        except Exception as e:
    -            logger.error(f"Failed to run an internal function ({e})")
    -
    -    return request_wired_func_wrapper
    -
    -
    -
    -
    -
    -
    -
    -

    Functions

    -
    -
    -def build_runnable_function(func:ย Callable[...,ย None], logger:ย logging.Logger, request:ย BoltRequest) โ€‘>ย Callable[[],ย None] -
    -
    -
    -
    - -Expand source code - -
    def build_runnable_function(
    -    func: Callable[..., None],
    -    logger: Logger,
    -    request: BoltRequest,
    -) -> Callable[[], None]:
    -    arg_names = get_arg_names_of_callable(func)
    -
    -    @wraps(func)
    -    def request_wired_func_wrapper() -> None:
    -        try:
    -            func(
    -                **build_required_kwargs(
    -                    logger=logger,
    -                    required_arg_names=arg_names,
    -                    request=request,
    -                    response=None,
    -                    this_func=func,
    -                )
    -            )
    -        except Exception as e:
    -            logger.error(f"Failed to run an internal function ({e})")
    -
    -    return request_wired_func_wrapper
    -
    -
    -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/lazy_listener/thread_runner.html b/docs/api-docs/slack_bolt/lazy_listener/thread_runner.html deleted file mode 100644 index 8644037b2..000000000 --- a/docs/api-docs/slack_bolt/lazy_listener/thread_runner.html +++ /dev/null @@ -1,154 +0,0 @@ - - - - - - -slack_bolt.lazy_listener.thread_runner API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.lazy_listener.thread_runner

    -
    -
    -
    - -Expand source code - -
    from concurrent.futures import Executor
    -from logging import Logger
    -from typing import Callable
    -
    -from slack_bolt.lazy_listener.internals import build_runnable_function
    -from slack_bolt.lazy_listener.runner import LazyListenerRunner
    -from slack_bolt.request import BoltRequest
    -
    -
    -class ThreadLazyListenerRunner(LazyListenerRunner):
    -    logger: Logger
    -
    -    def __init__(
    -        self,
    -        logger: Logger,
    -        executor: Executor,
    -    ):
    -        self.logger = logger
    -        self.executor = executor
    -
    -    def start(self, function: Callable[..., None], request: BoltRequest) -> None:
    -        self.executor.submit(
    -            build_runnable_function(
    -                func=function,
    -                logger=self.logger,
    -                request=request,
    -            )
    -        )
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class ThreadLazyListenerRunner -(logger:ย logging.Logger, executor:ย concurrent.futures._base.Executor) -
    -
    -
    -
    - -Expand source code - -
    class ThreadLazyListenerRunner(LazyListenerRunner):
    -    logger: Logger
    -
    -    def __init__(
    -        self,
    -        logger: Logger,
    -        executor: Executor,
    -    ):
    -        self.logger = logger
    -        self.executor = executor
    -
    -    def start(self, function: Callable[..., None], request: BoltRequest) -> None:
    -        self.executor.submit(
    -            build_runnable_function(
    -                func=function,
    -                logger=self.logger,
    -                request=request,
    -            )
    -        )
    -
    -

    Ancestors

    - -

    Subclasses

    - -

    Class variables

    -
    -
    var logger :ย logging.Logger
    -
    -
    -
    -
    -

    Inherited members

    - -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/listener/custom_listener.html b/docs/api-docs/slack_bolt/listener/custom_listener.html deleted file mode 100644 index 82acb85ec..000000000 --- a/docs/api-docs/slack_bolt/listener/custom_listener.html +++ /dev/null @@ -1,241 +0,0 @@ - - - - - - -slack_bolt.listener.custom_listener API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.listener.custom_listener

    -
    -
    -
    - -Expand source code - -
    from logging import Logger
    -from typing import Callable, Optional, Sequence
    -
    -from slack_bolt.kwargs_injection import build_required_kwargs
    -from slack_bolt.listener_matcher import ListenerMatcher
    -from slack_bolt.request import BoltRequest
    -from slack_bolt.response import BoltResponse
    -from .listener import Listener
    -from ..logger import get_bolt_app_logger
    -from ..middleware import Middleware
    -from ..util.utils import get_arg_names_of_callable
    -
    -
    -class CustomListener(Listener):
    -    app_name: str
    -    ack_function: Callable[..., Optional[BoltResponse]]
    -    lazy_functions: Sequence[Callable[..., None]]
    -    matchers: Sequence[ListenerMatcher]
    -    middleware: Sequence[Middleware]  # type: ignore
    -    auto_acknowledgement: bool
    -    arg_names: Sequence[str]
    -    logger: Logger
    -
    -    def __init__(
    -        self,
    -        *,
    -        app_name: str,
    -        ack_function: Callable[..., Optional[BoltResponse]],
    -        lazy_functions: Sequence[Callable[..., None]],
    -        matchers: Sequence[ListenerMatcher],
    -        middleware: Sequence[Middleware],  # type: ignore
    -        auto_acknowledgement: bool = False,
    -        base_logger: Optional[Logger] = None,
    -    ):
    -        self.app_name = app_name
    -        self.ack_function = ack_function
    -        self.lazy_functions = lazy_functions
    -        self.matchers = matchers
    -        self.middleware = middleware
    -        self.auto_acknowledgement = auto_acknowledgement
    -        self.arg_names = get_arg_names_of_callable(ack_function)
    -        self.logger = get_bolt_app_logger(app_name, self.ack_function, base_logger)
    -
    -    def run_ack_function(
    -        self,
    -        *,
    -        request: BoltRequest,
    -        response: BoltResponse,
    -    ) -> Optional[BoltResponse]:
    -        return self.ack_function(
    -            **build_required_kwargs(
    -                logger=self.logger,
    -                required_arg_names=self.arg_names,
    -                request=request,
    -                response=response,
    -                this_func=self.ack_function,
    -            )
    -        )
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class CustomListener -(*, app_name:ย str, ack_function:ย Callable[...,ย Optional[BoltResponse]], lazy_functions:ย Sequence[Callable[...,ย None]], matchers:ย Sequence[ListenerMatcher], middleware:ย Sequence[Middleware], auto_acknowledgement:ย boolย =ย False, base_logger:ย Optional[logging.Logger]ย =ย None) -
    -
    -
    -
    - -Expand source code - -
    class CustomListener(Listener):
    -    app_name: str
    -    ack_function: Callable[..., Optional[BoltResponse]]
    -    lazy_functions: Sequence[Callable[..., None]]
    -    matchers: Sequence[ListenerMatcher]
    -    middleware: Sequence[Middleware]  # type: ignore
    -    auto_acknowledgement: bool
    -    arg_names: Sequence[str]
    -    logger: Logger
    -
    -    def __init__(
    -        self,
    -        *,
    -        app_name: str,
    -        ack_function: Callable[..., Optional[BoltResponse]],
    -        lazy_functions: Sequence[Callable[..., None]],
    -        matchers: Sequence[ListenerMatcher],
    -        middleware: Sequence[Middleware],  # type: ignore
    -        auto_acknowledgement: bool = False,
    -        base_logger: Optional[Logger] = None,
    -    ):
    -        self.app_name = app_name
    -        self.ack_function = ack_function
    -        self.lazy_functions = lazy_functions
    -        self.matchers = matchers
    -        self.middleware = middleware
    -        self.auto_acknowledgement = auto_acknowledgement
    -        self.arg_names = get_arg_names_of_callable(ack_function)
    -        self.logger = get_bolt_app_logger(app_name, self.ack_function, base_logger)
    -
    -    def run_ack_function(
    -        self,
    -        *,
    -        request: BoltRequest,
    -        response: BoltResponse,
    -    ) -> Optional[BoltResponse]:
    -        return self.ack_function(
    -            **build_required_kwargs(
    -                logger=self.logger,
    -                required_arg_names=self.arg_names,
    -                request=request,
    -                response=response,
    -                this_func=self.ack_function,
    -            )
    -        )
    -
    -

    Ancestors

    - -

    Class variables

    -
    -
    var ack_function :ย Callable[...,ย Optional[BoltResponse]]
    -
    -
    -
    -
    var app_name :ย str
    -
    -
    -
    -
    var arg_names :ย Sequence[str]
    -
    -
    -
    -
    var auto_acknowledgement :ย bool
    -
    -
    -
    -
    var lazy_functions :ย Sequence[Callable[...,ย None]]
    -
    -
    -
    -
    var logger :ย logging.Logger
    -
    -
    -
    -
    var matchers :ย Sequence[ListenerMatcher]
    -
    -
    -
    -
    var middleware :ย Sequence[Middleware]
    -
    -
    -
    -
    -

    Inherited members

    - -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/listener_matcher/async_builtins.html b/docs/api-docs/slack_bolt/listener_matcher/async_builtins.html deleted file mode 100644 index 2bf7a8a39..000000000 --- a/docs/api-docs/slack_bolt/listener_matcher/async_builtins.html +++ /dev/null @@ -1,129 +0,0 @@ - - - - - - -slack_bolt.listener_matcher.async_builtins API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.listener_matcher.async_builtins

    -
    -
    -
    - -Expand source code - -
    # pytype: skip-file
    -from slack_bolt.request.async_request import AsyncBoltRequest
    -from slack_bolt.response import BoltResponse
    -from .async_listener_matcher import AsyncListenerMatcher
    -from .builtins import BuiltinListenerMatcher
    -from ..kwargs_injection.async_utils import build_async_required_kwargs
    -
    -
    -class AsyncBuiltinListenerMatcher(BuiltinListenerMatcher, AsyncListenerMatcher):
    -    async def async_matches(self, req: AsyncBoltRequest, resp: BoltResponse) -> bool:
    -        return await self.func(
    -            **build_async_required_kwargs(
    -                logger=self.logger,
    -                required_arg_names=self.arg_names,
    -                request=req,
    -                response=resp,
    -                this_func=self.func,
    -            )
    -        )
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class AsyncBuiltinListenerMatcher -(*, func:ย Callable[...,ย Union[bool,ย Awaitable[bool]]], base_logger:ย Optional[logging.Logger]ย =ย None) -
    -
    -
    -
    - -Expand source code - -
    class AsyncBuiltinListenerMatcher(BuiltinListenerMatcher, AsyncListenerMatcher):
    -    async def async_matches(self, req: AsyncBoltRequest, resp: BoltResponse) -> bool:
    -        return await self.func(
    -            **build_async_required_kwargs(
    -                logger=self.logger,
    -                required_arg_names=self.arg_names,
    -                request=req,
    -                response=resp,
    -                this_func=self.func,
    -            )
    -        )
    -
    -

    Ancestors

    - -

    Inherited members

    - -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/listener_matcher/builtins.html b/docs/api-docs/slack_bolt/listener_matcher/builtins.html deleted file mode 100644 index 40ff38194..000000000 --- a/docs/api-docs/slack_bolt/listener_matcher/builtins.html +++ /dev/null @@ -1,1200 +0,0 @@ - - - - - - -slack_bolt.listener_matcher.builtins API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.listener_matcher.builtins

    -
    -
    -
    - -Expand source code - -
    # pytype: skip-file
    -import re
    -import sys
    -from logging import Logger
    -
    -from slack_bolt.error import BoltError
    -from slack_bolt.request.payload_utils import (
    -    is_block_actions,
    -    is_global_shortcut,
    -    is_message_shortcut,
    -    is_attachment_action,
    -    is_dialog_submission,
    -    is_dialog_cancellation,
    -    is_workflow_step_edit,
    -    is_slash_command,
    -    is_event,
    -    is_view_submission,
    -    is_view_closed,
    -    is_block_suggestion,
    -    is_dialog_suggestion,
    -    is_shortcut,
    -    to_action,
    -    is_workflow_step_save,
    -)
    -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
    -else:
    -    from re import Pattern
    -from typing import Callable, Awaitable, Any, Sequence, Optional, Union, Dict
    -
    -from slack_bolt.kwargs_injection import build_required_kwargs
    -from slack_bolt.request import BoltRequest
    -from slack_bolt.response import BoltResponse
    -from .listener_matcher import ListenerMatcher
    -from slack_bolt.logger import get_bolt_logger
    -
    -
    -# a.k.a Union[ListenerMatcher, "AsyncListenerMatcher"]
    -class BuiltinListenerMatcher(ListenerMatcher):
    -    def __init__(
    -        self,
    -        *,
    -        func: Callable[..., Union[bool, Awaitable[bool]]],
    -        base_logger: Optional[Logger] = None,
    -    ):
    -        self.func = func
    -        self.arg_names = get_arg_names_of_callable(func)
    -        self.logger = get_bolt_logger(self.func, base_logger)
    -
    -    def matches(self, req: BoltRequest, resp: BoltResponse) -> bool:
    -        return self.func(
    -            **build_required_kwargs(
    -                logger=self.logger,
    -                required_arg_names=self.arg_names,
    -                request=req,
    -                response=resp,
    -                this_func=self.func,
    -            )
    -        )
    -
    -
    -def build_listener_matcher(
    -    func: Callable[..., bool],
    -    asyncio: bool,
    -    base_logger: Optional[Logger] = None,
    -) -> Union[ListenerMatcher, "AsyncListenerMatcher"]:
    -    if asyncio:
    -        from .async_builtins import AsyncBuiltinListenerMatcher
    -
    -        async def async_fun(body: Dict[str, Any]) -> bool:
    -            return func(body)
    -
    -        return AsyncBuiltinListenerMatcher(func=async_fun, base_logger=base_logger)
    -    else:
    -        return BuiltinListenerMatcher(func=func, base_logger=base_logger)
    -
    -
    -# -------------
    -# events
    -
    -
    -def event(
    -    constraints: Union[
    -        str,
    -        Pattern,
    -        Dict[str, Optional[Union[str, Sequence[Optional[Union[str, Pattern]]]]]],
    -    ],
    -    asyncio: bool = False,
    -    base_logger: Optional[Logger] = None,
    -) -> Union[ListenerMatcher, "AsyncListenerMatcher"]:
    -    if isinstance(constraints, (str, Pattern)):
    -        event_type: Union[str, Pattern] = constraints
    -        _verify_message_event_type(event_type)
    -
    -        def func(body: Dict[str, Any]) -> bool:
    -            return is_event(body) and _matches(event_type, body["event"]["type"])
    -
    -        return build_listener_matcher(func, asyncio, base_logger)
    -
    -    elif "type" in constraints:
    -        _verify_message_event_type(constraints["type"])
    -
    -        def func(body: Dict[str, Any]) -> bool:
    -            if is_event(body):
    -                return _check_event_subtype(
    -                    event_payload=body["event"],
    -                    constraints=constraints,
    -                )
    -            return False
    -
    -        return build_listener_matcher(func, asyncio, base_logger)
    -
    -    raise BoltError(f"event ({constraints}: {type(constraints)}) must be any of str, Pattern, and dict")
    -
    -
    -def message_event(
    -    constraints: Dict[str, Optional[Union[str, Sequence[Optional[Union[str, Pattern]]]]]],
    -    keyword: Union[str, Pattern],
    -    asyncio: bool = False,
    -    base_logger: Optional[Logger] = None,
    -) -> Union[ListenerMatcher, "AsyncListenerMatcher"]:
    -    if "type" in constraints and keyword is not None:
    -        _verify_message_event_type(constraints["type"])
    -
    -        def func(body: Dict[str, Any]) -> bool:
    -            if is_event(body):
    -                is_valid_subtype = _check_event_subtype(
    -                    event_payload=body["event"],
    -                    constraints=constraints,
    -                )
    -                if is_valid_subtype is True:
    -                    # Check keyword matching
    -                    text = body.get("event", {}).get("text", "")
    -                    match_result = re.findall(keyword, text)
    -                    if match_result is not None and match_result != []:
    -                        return True
    -            return False
    -
    -        return build_listener_matcher(func, asyncio, base_logger)
    -
    -    raise BoltError(f"event ({constraints}: {type(constraints)}) must be dict")
    -
    -
    -def _check_event_subtype(event_payload: dict, constraints: dict) -> bool:
    -    if not _matches(constraints["type"], event_payload["type"]):
    -        return False
    -    if "subtype" in constraints:
    -        expected_subtype: Optional[Union[str, Sequence[Optional[Union[str, Pattern]]]]] = constraints["subtype"]
    -        if expected_subtype is None:
    -            # "subtype" in constraints is intentionally None for this pattern
    -            return "subtype" not in event_payload
    -        elif isinstance(expected_subtype, (str, Pattern)):
    -            return "subtype" in event_payload and _matches(expected_subtype, event_payload["subtype"])
    -        elif isinstance(expected_subtype, Sequence):
    -            subtypes: Sequence[Optional[Union[str, Pattern]]] = expected_subtype
    -            for expected in subtypes:
    -                actual: Optional[str] = event_payload.get("subtype")
    -                if expected is None:
    -                    if actual is None:
    -                        return True
    -                elif actual is not None and _matches(expected, actual):
    -                    return True
    -            return False
    -        else:
    -            return "subtype" in event_payload and _matches(expected_subtype, event_payload["subtype"])
    -    return True
    -
    -
    -def _verify_message_event_type(event_type: str) -> 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:
    -        raise ValueError(error_message_event_type(event_type))
    -
    -
    -def workflow_step_execute(
    -    callback_id: Union[str, Pattern],
    -    asyncio: bool = False,
    -    base_logger: Optional[Logger] = None,
    -) -> Union[ListenerMatcher, "AsyncListenerMatcher"]:
    -    def func(body: Dict[str, Any]) -> bool:
    -        return (
    -            is_event(body)
    -            and _matches("workflow_step_execute", body["event"]["type"])
    -            and "workflow_step" in body["event"]
    -            and _matches(callback_id, body["event"]["callback_id"])
    -        )
    -
    -    return build_listener_matcher(func, asyncio, base_logger)
    -
    -
    -# -------------
    -# slash commands
    -
    -
    -def command(
    -    command: Union[str, Pattern],
    -    asyncio: bool = False,
    -    base_logger: Optional[Logger] = None,
    -) -> Union[ListenerMatcher, "AsyncListenerMatcher"]:
    -    def func(body: Dict[str, Any]) -> bool:
    -        return is_slash_command(body) and _matches(command, body["command"])
    -
    -    return build_listener_matcher(func, asyncio, base_logger)
    -
    -
    -# -------------
    -# shortcuts
    -
    -
    -def shortcut(
    -    constraints: Union[str, Pattern, Dict[str, Union[str, Pattern]]],
    -    asyncio: bool = False,
    -    base_logger: Optional[Logger] = None,
    -) -> Union[ListenerMatcher, "AsyncListenerMatcher"]:
    -    if isinstance(constraints, (str, Pattern)):
    -        callback_id: Union[str, Pattern] = constraints
    -
    -        def func(body: Dict[str, Any]) -> bool:
    -            return is_shortcut(body) and _matches(callback_id, body["callback_id"])
    -
    -        return build_listener_matcher(func, asyncio, base_logger)
    -
    -    elif "type" in constraints and "callback_id" in constraints:
    -        if constraints["type"] == "shortcut":
    -            return global_shortcut(constraints["callback_id"], asyncio)
    -        if constraints["type"] == "message_action":
    -            return message_shortcut(constraints["callback_id"], asyncio)
    -
    -    raise BoltError(f"shortcut ({constraints}: {type(constraints)}) must be any of str, Pattern, and dict")
    -
    -
    -def global_shortcut(
    -    callback_id: Union[str, Pattern],
    -    asyncio: bool = False,
    -    base_logger: Optional[Logger] = None,
    -) -> Union[ListenerMatcher, "AsyncListenerMatcher"]:
    -    def func(body: Dict[str, Any]) -> bool:
    -        return is_global_shortcut(body) and _matches(callback_id, body["callback_id"])
    -
    -    return build_listener_matcher(func, asyncio, base_logger)
    -
    -
    -def message_shortcut(
    -    callback_id: Union[str, Pattern],
    -    asyncio: bool = False,
    -    base_logger: Optional[Logger] = None,
    -) -> Union[ListenerMatcher, "AsyncListenerMatcher"]:
    -    def func(body: Dict[str, Any]) -> bool:
    -        return is_message_shortcut(body) and _matches(callback_id, body["callback_id"])
    -
    -    return build_listener_matcher(func, asyncio, base_logger)
    -
    -
    -# -------------
    -# action
    -
    -
    -def action(
    -    constraints: Union[str, Pattern, Dict[str, Union[str, Pattern]]],
    -    asyncio: bool = False,
    -    base_logger: Optional[Logger] = None,
    -) -> Union[ListenerMatcher, "AsyncListenerMatcher"]:
    -    if isinstance(constraints, (str, Pattern)):
    -
    -        def func(body: Dict[str, Any]) -> bool:
    -            return (
    -                _block_action(constraints, body)
    -                or _attachment_action(constraints, body)
    -                or _dialog_submission(constraints, body)
    -                or _dialog_cancellation(constraints, body)
    -                or _workflow_step_edit(constraints, body)
    -            )
    -
    -        return build_listener_matcher(func, asyncio, base_logger)
    -
    -    elif "type" in constraints:
    -        action_type = constraints["type"]
    -        if action_type == "block_actions":
    -            return block_action(constraints, asyncio)
    -        if action_type == "interactive_message":
    -            return attachment_action(constraints["callback_id"], asyncio)
    -        if action_type == "dialog_submission":
    -            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
    -        if action_type == "workflow_step_edit":
    -            return workflow_step_edit(constraints["callback_id"], asyncio)
    -
    -        raise BoltError(f"type: {action_type} is unsupported")
    -    elif "action_id" in constraints:
    -        # The default value is "block_actions"
    -        return block_action(constraints, asyncio)
    -
    -    raise BoltError(f"action ({constraints}: {type(constraints)}) must be any of str, Pattern, and dict")
    -
    -
    -def _block_action(
    -    constraints: Union[str, Pattern, Dict[str, Union[str, Pattern]]],
    -    body: Dict[str, Any],
    -) -> bool:
    -    if is_block_actions(body) is False:
    -        return False
    -
    -    action = to_action(body)
    -    if isinstance(constraints, (str, Pattern)):
    -        action_id = constraints
    -        return _matches(action_id, action["action_id"])
    -    elif isinstance(constraints, dict):
    -        # block_id matching is optional
    -        block_id: Optional[Union[str, Pattern]] = constraints.get("block_id")
    -        block_id_matched = block_id is None or _matches(block_id, action.get("block_id"))
    -        action_id_matched = _matches(constraints["action_id"], action["action_id"])
    -        return block_id_matched and action_id_matched
    -
    -
    -def block_action(
    -    constraints: Union[str, Pattern, Dict[str, Union[str, Pattern]]],
    -    asyncio: bool = False,
    -    base_logger: Optional[Logger] = None,
    -) -> Union[ListenerMatcher, "AsyncListenerMatcher"]:
    -    def func(body: Dict[str, Any]) -> bool:
    -        return _block_action(constraints, body)
    -
    -    return build_listener_matcher(func, asyncio, base_logger)
    -
    -
    -def _attachment_action(
    -    callback_id: Union[str, Pattern],
    -    body: Dict[str, Any],
    -) -> bool:
    -    return is_attachment_action(body) and _matches(callback_id, body["callback_id"])
    -
    -
    -def attachment_action(
    -    callback_id: Union[str, Pattern],
    -    asyncio: bool = False,
    -    base_logger: Optional[Logger] = None,
    -) -> Union[ListenerMatcher, "AsyncListenerMatcher"]:
    -    def func(body: Dict[str, Any]) -> bool:
    -        return _attachment_action(callback_id, body)
    -
    -    return build_listener_matcher(func, asyncio, base_logger)
    -
    -
    -def _dialog_submission(
    -    callback_id: Union[str, Pattern],
    -    body: Dict[str, Any],
    -) -> bool:
    -    return is_dialog_submission(body) and _matches(callback_id, body["callback_id"])
    -
    -
    -def dialog_submission(
    -    callback_id: Union[str, Pattern],
    -    asyncio: bool = False,
    -    base_logger: Optional[Logger] = None,
    -) -> Union[ListenerMatcher, "AsyncListenerMatcher"]:
    -    def func(body: Dict[str, Any]) -> bool:
    -        return _dialog_submission(callback_id, body)
    -
    -    return build_listener_matcher(func, asyncio, base_logger)
    -
    -
    -def _dialog_cancellation(
    -    callback_id: Union[str, Pattern],
    -    body: Dict[str, Any],
    -) -> bool:
    -    return is_dialog_cancellation(body) and _matches(callback_id, body["callback_id"])
    -
    -
    -def dialog_cancellation(
    -    callback_id: Union[str, Pattern],
    -    asyncio: bool = False,
    -    base_logger: Optional[Logger] = None,
    -) -> Union[ListenerMatcher, "AsyncListenerMatcher"]:
    -    def func(body: Dict[str, Any]) -> bool:
    -        return _dialog_cancellation(callback_id, body)
    -
    -    return build_listener_matcher(func, asyncio, base_logger)
    -
    -
    -def _workflow_step_edit(
    -    callback_id: Union[str, Pattern],
    -    body: Dict[str, Any],
    -) -> bool:
    -    return is_workflow_step_edit(body) and _matches(callback_id, body["callback_id"])
    -
    -
    -def workflow_step_edit(
    -    callback_id: Union[str, Pattern],
    -    asyncio: bool = False,
    -    base_logger: Optional[Logger] = None,
    -) -> Union[ListenerMatcher, "AsyncListenerMatcher"]:
    -    def func(body: Dict[str, Any]) -> bool:
    -        return _workflow_step_edit(callback_id, body)
    -
    -    return build_listener_matcher(func, asyncio, base_logger)
    -
    -
    -# -------------------------
    -# view
    -
    -
    -def view(
    -    constraints: Union[str, Pattern, Dict[str, Union[str, Pattern]]],
    -    asyncio: bool = False,
    -    base_logger: Optional[Logger] = None,
    -) -> Union[ListenerMatcher, "AsyncListenerMatcher"]:
    -    if isinstance(constraints, (str, Pattern)):
    -        return view_submission(constraints, asyncio)
    -    elif "type" in constraints:
    -        if constraints["type"] == "view_submission":
    -            return view_submission(constraints["callback_id"], asyncio)
    -        if constraints["type"] == "view_closed":
    -            return view_closed(constraints["callback_id"], asyncio)
    -
    -    raise BoltError(f"view ({constraints}: {type(constraints)}) must be any of str, Pattern, and dict")
    -
    -
    -def view_submission(
    -    callback_id: Union[str, Pattern],
    -    asyncio: bool = False,
    -    base_logger: Optional[Logger] = None,
    -) -> Union[ListenerMatcher, "AsyncListenerMatcher"]:
    -    def func(body: Dict[str, Any]) -> bool:
    -        return is_view_submission(body) and _matches(callback_id, body["view"]["callback_id"])
    -
    -    return build_listener_matcher(func, asyncio, base_logger)
    -
    -
    -def view_closed(
    -    callback_id: Union[str, Pattern],
    -    asyncio: bool = False,
    -    base_logger: Optional[Logger] = None,
    -) -> Union[ListenerMatcher, "AsyncListenerMatcher"]:
    -    def func(body: Dict[str, Any]) -> bool:
    -        return is_view_closed(body) and _matches(callback_id, body["view"]["callback_id"])
    -
    -    return build_listener_matcher(func, asyncio, base_logger)
    -
    -
    -def workflow_step_save(
    -    callback_id: Union[str, Pattern],
    -    asyncio: bool = False,
    -    base_logger: Optional[Logger] = None,
    -) -> Union[ListenerMatcher, "AsyncListenerMatcher"]:
    -    def func(body: Dict[str, Any]) -> bool:
    -        return is_workflow_step_save(body) and _matches(callback_id, body["view"]["callback_id"])
    -
    -    return build_listener_matcher(func, asyncio, base_logger)
    -
    -
    -# -------------
    -# options
    -
    -
    -def options(
    -    constraints: Union[str, Pattern, Dict[str, Union[str, Pattern]]],
    -    asyncio: bool = False,
    -    base_logger: Optional[Logger] = None,
    -) -> Union[ListenerMatcher, "AsyncListenerMatcher"]:
    -    if isinstance(constraints, (str, Pattern)):
    -
    -        def func(body: Dict[str, Any]) -> bool:
    -            return _block_suggestion(constraints, body) or _dialog_suggestion(constraints, body)
    -
    -        return build_listener_matcher(func, asyncio, base_logger)
    -
    -    if "action_id" in constraints:
    -        return block_suggestion(constraints["action_id"], asyncio)
    -    if "callback_id" in constraints:
    -        return dialog_suggestion(constraints["callback_id"], asyncio)
    -    else:
    -        raise BoltError(f"options ({constraints}: {type(constraints)}) must be any of str, Pattern, and dict")
    -
    -
    -def _block_suggestion(
    -    action_id: Union[str, Pattern],
    -    body: Dict[str, Any],
    -) -> bool:
    -    return is_block_suggestion(body) and _matches(action_id, body["action_id"])
    -
    -
    -def block_suggestion(
    -    action_id: Union[str, Pattern],
    -    asyncio: bool = False,
    -    base_logger: Optional[Logger] = None,
    -) -> Union[ListenerMatcher, "AsyncListenerMatcher"]:
    -    def func(body: Dict[str, Any]) -> bool:
    -        return _block_suggestion(action_id, body)
    -
    -    return build_listener_matcher(func, asyncio, base_logger)
    -
    -
    -def _dialog_suggestion(
    -    callback_id: Union[str, Pattern],
    -    body: Dict[str, Any],
    -) -> bool:
    -    return is_dialog_suggestion(body) and _matches(callback_id, body["callback_id"])
    -
    -
    -def dialog_suggestion(
    -    callback_id: Union[str, Pattern],
    -    asyncio: bool = False,
    -    base_logger: Optional[Logger] = None,
    -) -> Union[ListenerMatcher, "AsyncListenerMatcher"]:
    -    def func(body: Dict[str, Any]) -> bool:
    -        return _dialog_suggestion(callback_id, body)
    -
    -    return build_listener_matcher(func, asyncio, base_logger)
    -
    -
    -# -------------------------
    -
    -
    -def _matches(str_or_pattern: Union[str, Pattern], input: Optional[str]) -> bool:
    -    if str_or_pattern is None or input is None:
    -        return False
    -
    -    if isinstance(str_or_pattern, str):
    -        exact_match_str: str = str_or_pattern
    -        return input == exact_match_str
    -    elif isinstance(str_or_pattern, Pattern):
    -        pattern: Pattern = str_or_pattern
    -        return pattern.search(input) is not None
    -    else:
    -        raise BoltError(f"{str_or_pattern} ({type(str_or_pattern)}) must be either str or Pattern")
    -
    -
    -
    -
    -
    -
    -
    -

    Functions

    -
    -
    -def action(constraints:ย Union[str,ย re.Pattern,ย Dict[str,ย Union[str,ย re.Pattern]]], asyncio:ย boolย =ย False, base_logger:ย Optional[logging.Logger]ย =ย None) โ€‘>ย Union[ListenerMatcher,ย AsyncListenerMatcher] -
    -
    -
    -
    - -Expand source code - -
    def action(
    -    constraints: Union[str, Pattern, Dict[str, Union[str, Pattern]]],
    -    asyncio: bool = False,
    -    base_logger: Optional[Logger] = None,
    -) -> Union[ListenerMatcher, "AsyncListenerMatcher"]:
    -    if isinstance(constraints, (str, Pattern)):
    -
    -        def func(body: Dict[str, Any]) -> bool:
    -            return (
    -                _block_action(constraints, body)
    -                or _attachment_action(constraints, body)
    -                or _dialog_submission(constraints, body)
    -                or _dialog_cancellation(constraints, body)
    -                or _workflow_step_edit(constraints, body)
    -            )
    -
    -        return build_listener_matcher(func, asyncio, base_logger)
    -
    -    elif "type" in constraints:
    -        action_type = constraints["type"]
    -        if action_type == "block_actions":
    -            return block_action(constraints, asyncio)
    -        if action_type == "interactive_message":
    -            return attachment_action(constraints["callback_id"], asyncio)
    -        if action_type == "dialog_submission":
    -            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
    -        if action_type == "workflow_step_edit":
    -            return workflow_step_edit(constraints["callback_id"], asyncio)
    -
    -        raise BoltError(f"type: {action_type} is unsupported")
    -    elif "action_id" in constraints:
    -        # The default value is "block_actions"
    -        return block_action(constraints, asyncio)
    -
    -    raise BoltError(f"action ({constraints}: {type(constraints)}) must be any of str, Pattern, and dict")
    -
    -
    -
    -def attachment_action(callback_id:ย Union[str,ย re.Pattern], asyncio:ย boolย =ย False, base_logger:ย Optional[logging.Logger]ย =ย None) โ€‘>ย Union[ListenerMatcher,ย AsyncListenerMatcher] -
    -
    -
    -
    - -Expand source code - -
    def attachment_action(
    -    callback_id: Union[str, Pattern],
    -    asyncio: bool = False,
    -    base_logger: Optional[Logger] = None,
    -) -> Union[ListenerMatcher, "AsyncListenerMatcher"]:
    -    def func(body: Dict[str, Any]) -> bool:
    -        return _attachment_action(callback_id, body)
    -
    -    return build_listener_matcher(func, asyncio, base_logger)
    -
    -
    -
    -def block_action(constraints:ย Union[str,ย re.Pattern,ย Dict[str,ย Union[str,ย re.Pattern]]], asyncio:ย boolย =ย False, base_logger:ย Optional[logging.Logger]ย =ย None) โ€‘>ย Union[ListenerMatcher,ย AsyncListenerMatcher] -
    -
    -
    -
    - -Expand source code - -
    def block_action(
    -    constraints: Union[str, Pattern, Dict[str, Union[str, Pattern]]],
    -    asyncio: bool = False,
    -    base_logger: Optional[Logger] = None,
    -) -> Union[ListenerMatcher, "AsyncListenerMatcher"]:
    -    def func(body: Dict[str, Any]) -> bool:
    -        return _block_action(constraints, body)
    -
    -    return build_listener_matcher(func, asyncio, base_logger)
    -
    -
    -
    -def block_suggestion(action_id:ย Union[str,ย re.Pattern], asyncio:ย boolย =ย False, base_logger:ย Optional[logging.Logger]ย =ย None) โ€‘>ย Union[ListenerMatcher,ย AsyncListenerMatcher] -
    -
    -
    -
    - -Expand source code - -
    def block_suggestion(
    -    action_id: Union[str, Pattern],
    -    asyncio: bool = False,
    -    base_logger: Optional[Logger] = None,
    -) -> Union[ListenerMatcher, "AsyncListenerMatcher"]:
    -    def func(body: Dict[str, Any]) -> bool:
    -        return _block_suggestion(action_id, body)
    -
    -    return build_listener_matcher(func, asyncio, base_logger)
    -
    -
    -
    -def build_listener_matcher(func:ย Callable[...,ย bool], asyncio:ย bool, base_logger:ย Optional[logging.Logger]ย =ย None) โ€‘>ย Union[ListenerMatcher,ย AsyncListenerMatcher] -
    -
    -
    -
    - -Expand source code - -
    def build_listener_matcher(
    -    func: Callable[..., bool],
    -    asyncio: bool,
    -    base_logger: Optional[Logger] = None,
    -) -> Union[ListenerMatcher, "AsyncListenerMatcher"]:
    -    if asyncio:
    -        from .async_builtins import AsyncBuiltinListenerMatcher
    -
    -        async def async_fun(body: Dict[str, Any]) -> bool:
    -            return func(body)
    -
    -        return AsyncBuiltinListenerMatcher(func=async_fun, base_logger=base_logger)
    -    else:
    -        return BuiltinListenerMatcher(func=func, base_logger=base_logger)
    -
    -
    -
    -def command(command:ย Union[str,ย re.Pattern], asyncio:ย boolย =ย False, base_logger:ย Optional[logging.Logger]ย =ย None) โ€‘>ย Union[ListenerMatcher,ย AsyncListenerMatcher] -
    -
    -
    -
    - -Expand source code - -
    def command(
    -    command: Union[str, Pattern],
    -    asyncio: bool = False,
    -    base_logger: Optional[Logger] = None,
    -) -> Union[ListenerMatcher, "AsyncListenerMatcher"]:
    -    def func(body: Dict[str, Any]) -> bool:
    -        return is_slash_command(body) and _matches(command, body["command"])
    -
    -    return build_listener_matcher(func, asyncio, base_logger)
    -
    -
    -
    -def dialog_cancellation(callback_id:ย Union[str,ย re.Pattern], asyncio:ย boolย =ย False, base_logger:ย Optional[logging.Logger]ย =ย None) โ€‘>ย Union[ListenerMatcher,ย AsyncListenerMatcher] -
    -
    -
    -
    - -Expand source code - -
    def dialog_cancellation(
    -    callback_id: Union[str, Pattern],
    -    asyncio: bool = False,
    -    base_logger: Optional[Logger] = None,
    -) -> Union[ListenerMatcher, "AsyncListenerMatcher"]:
    -    def func(body: Dict[str, Any]) -> bool:
    -        return _dialog_cancellation(callback_id, body)
    -
    -    return build_listener_matcher(func, asyncio, base_logger)
    -
    -
    -
    -def dialog_submission(callback_id:ย Union[str,ย re.Pattern], asyncio:ย boolย =ย False, base_logger:ย Optional[logging.Logger]ย =ย None) โ€‘>ย Union[ListenerMatcher,ย AsyncListenerMatcher] -
    -
    -
    -
    - -Expand source code - -
    def dialog_submission(
    -    callback_id: Union[str, Pattern],
    -    asyncio: bool = False,
    -    base_logger: Optional[Logger] = None,
    -) -> Union[ListenerMatcher, "AsyncListenerMatcher"]:
    -    def func(body: Dict[str, Any]) -> bool:
    -        return _dialog_submission(callback_id, body)
    -
    -    return build_listener_matcher(func, asyncio, base_logger)
    -
    -
    -
    -def dialog_suggestion(callback_id:ย Union[str,ย re.Pattern], asyncio:ย boolย =ย False, base_logger:ย Optional[logging.Logger]ย =ย None) โ€‘>ย Union[ListenerMatcher,ย AsyncListenerMatcher] -
    -
    -
    -
    - -Expand source code - -
    def dialog_suggestion(
    -    callback_id: Union[str, Pattern],
    -    asyncio: bool = False,
    -    base_logger: Optional[Logger] = None,
    -) -> Union[ListenerMatcher, "AsyncListenerMatcher"]:
    -    def func(body: Dict[str, Any]) -> bool:
    -        return _dialog_suggestion(callback_id, body)
    -
    -    return build_listener_matcher(func, asyncio, base_logger)
    -
    -
    -
    -def event(constraints:ย Union[str,ย re.Pattern,ย Dict[str,ย Union[str,ย Sequence[Union[str,ย re.Pattern,ย ForwardRef(None)]],ย ForwardRef(None)]]], asyncio:ย boolย =ย False, base_logger:ย Optional[logging.Logger]ย =ย None) โ€‘>ย Union[ListenerMatcher,ย AsyncListenerMatcher] -
    -
    -
    -
    - -Expand source code - -
    def event(
    -    constraints: Union[
    -        str,
    -        Pattern,
    -        Dict[str, Optional[Union[str, Sequence[Optional[Union[str, Pattern]]]]]],
    -    ],
    -    asyncio: bool = False,
    -    base_logger: Optional[Logger] = None,
    -) -> Union[ListenerMatcher, "AsyncListenerMatcher"]:
    -    if isinstance(constraints, (str, Pattern)):
    -        event_type: Union[str, Pattern] = constraints
    -        _verify_message_event_type(event_type)
    -
    -        def func(body: Dict[str, Any]) -> bool:
    -            return is_event(body) and _matches(event_type, body["event"]["type"])
    -
    -        return build_listener_matcher(func, asyncio, base_logger)
    -
    -    elif "type" in constraints:
    -        _verify_message_event_type(constraints["type"])
    -
    -        def func(body: Dict[str, Any]) -> bool:
    -            if is_event(body):
    -                return _check_event_subtype(
    -                    event_payload=body["event"],
    -                    constraints=constraints,
    -                )
    -            return False
    -
    -        return build_listener_matcher(func, asyncio, base_logger)
    -
    -    raise BoltError(f"event ({constraints}: {type(constraints)}) must be any of str, Pattern, and dict")
    -
    -
    -
    -def global_shortcut(callback_id:ย Union[str,ย re.Pattern], asyncio:ย boolย =ย False, base_logger:ย Optional[logging.Logger]ย =ย None) โ€‘>ย Union[ListenerMatcher,ย AsyncListenerMatcher] -
    -
    -
    -
    - -Expand source code - -
    def global_shortcut(
    -    callback_id: Union[str, Pattern],
    -    asyncio: bool = False,
    -    base_logger: Optional[Logger] = None,
    -) -> Union[ListenerMatcher, "AsyncListenerMatcher"]:
    -    def func(body: Dict[str, Any]) -> bool:
    -        return is_global_shortcut(body) and _matches(callback_id, body["callback_id"])
    -
    -    return build_listener_matcher(func, asyncio, base_logger)
    -
    -
    -
    -def message_event(constraints:ย Dict[str,ย Union[str,ย Sequence[Union[str,ย re.Pattern,ย ForwardRef(None)]],ย ForwardRef(None)]], keyword:ย Union[str,ย re.Pattern], asyncio:ย boolย =ย False, base_logger:ย Optional[logging.Logger]ย =ย None) โ€‘>ย Union[ListenerMatcher,ย AsyncListenerMatcher] -
    -
    -
    -
    - -Expand source code - -
    def message_event(
    -    constraints: Dict[str, Optional[Union[str, Sequence[Optional[Union[str, Pattern]]]]]],
    -    keyword: Union[str, Pattern],
    -    asyncio: bool = False,
    -    base_logger: Optional[Logger] = None,
    -) -> Union[ListenerMatcher, "AsyncListenerMatcher"]:
    -    if "type" in constraints and keyword is not None:
    -        _verify_message_event_type(constraints["type"])
    -
    -        def func(body: Dict[str, Any]) -> bool:
    -            if is_event(body):
    -                is_valid_subtype = _check_event_subtype(
    -                    event_payload=body["event"],
    -                    constraints=constraints,
    -                )
    -                if is_valid_subtype is True:
    -                    # Check keyword matching
    -                    text = body.get("event", {}).get("text", "")
    -                    match_result = re.findall(keyword, text)
    -                    if match_result is not None and match_result != []:
    -                        return True
    -            return False
    -
    -        return build_listener_matcher(func, asyncio, base_logger)
    -
    -    raise BoltError(f"event ({constraints}: {type(constraints)}) must be dict")
    -
    -
    -
    -def message_shortcut(callback_id:ย Union[str,ย re.Pattern], asyncio:ย boolย =ย False, base_logger:ย Optional[logging.Logger]ย =ย None) โ€‘>ย Union[ListenerMatcher,ย AsyncListenerMatcher] -
    -
    -
    -
    - -Expand source code - -
    def message_shortcut(
    -    callback_id: Union[str, Pattern],
    -    asyncio: bool = False,
    -    base_logger: Optional[Logger] = None,
    -) -> Union[ListenerMatcher, "AsyncListenerMatcher"]:
    -    def func(body: Dict[str, Any]) -> bool:
    -        return is_message_shortcut(body) and _matches(callback_id, body["callback_id"])
    -
    -    return build_listener_matcher(func, asyncio, base_logger)
    -
    -
    -
    -def options(constraints:ย Union[str,ย re.Pattern,ย Dict[str,ย Union[str,ย re.Pattern]]], asyncio:ย boolย =ย False, base_logger:ย Optional[logging.Logger]ย =ย None) โ€‘>ย Union[ListenerMatcher,ย AsyncListenerMatcher] -
    -
    -
    -
    - -Expand source code - -
    def options(
    -    constraints: Union[str, Pattern, Dict[str, Union[str, Pattern]]],
    -    asyncio: bool = False,
    -    base_logger: Optional[Logger] = None,
    -) -> Union[ListenerMatcher, "AsyncListenerMatcher"]:
    -    if isinstance(constraints, (str, Pattern)):
    -
    -        def func(body: Dict[str, Any]) -> bool:
    -            return _block_suggestion(constraints, body) or _dialog_suggestion(constraints, body)
    -
    -        return build_listener_matcher(func, asyncio, base_logger)
    -
    -    if "action_id" in constraints:
    -        return block_suggestion(constraints["action_id"], asyncio)
    -    if "callback_id" in constraints:
    -        return dialog_suggestion(constraints["callback_id"], asyncio)
    -    else:
    -        raise BoltError(f"options ({constraints}: {type(constraints)}) must be any of str, Pattern, and dict")
    -
    -
    -
    -def shortcut(constraints:ย Union[str,ย re.Pattern,ย Dict[str,ย Union[str,ย re.Pattern]]], asyncio:ย boolย =ย False, base_logger:ย Optional[logging.Logger]ย =ย None) โ€‘>ย Union[ListenerMatcher,ย AsyncListenerMatcher] -
    -
    -
    -
    - -Expand source code - -
    def shortcut(
    -    constraints: Union[str, Pattern, Dict[str, Union[str, Pattern]]],
    -    asyncio: bool = False,
    -    base_logger: Optional[Logger] = None,
    -) -> Union[ListenerMatcher, "AsyncListenerMatcher"]:
    -    if isinstance(constraints, (str, Pattern)):
    -        callback_id: Union[str, Pattern] = constraints
    -
    -        def func(body: Dict[str, Any]) -> bool:
    -            return is_shortcut(body) and _matches(callback_id, body["callback_id"])
    -
    -        return build_listener_matcher(func, asyncio, base_logger)
    -
    -    elif "type" in constraints and "callback_id" in constraints:
    -        if constraints["type"] == "shortcut":
    -            return global_shortcut(constraints["callback_id"], asyncio)
    -        if constraints["type"] == "message_action":
    -            return message_shortcut(constraints["callback_id"], asyncio)
    -
    -    raise BoltError(f"shortcut ({constraints}: {type(constraints)}) must be any of str, Pattern, and dict")
    -
    -
    -
    -def view(constraints:ย Union[str,ย re.Pattern,ย Dict[str,ย Union[str,ย re.Pattern]]], asyncio:ย boolย =ย False, base_logger:ย Optional[logging.Logger]ย =ย None) โ€‘>ย Union[ListenerMatcher,ย AsyncListenerMatcher] -
    -
    -
    -
    - -Expand source code - -
    def view(
    -    constraints: Union[str, Pattern, Dict[str, Union[str, Pattern]]],
    -    asyncio: bool = False,
    -    base_logger: Optional[Logger] = None,
    -) -> Union[ListenerMatcher, "AsyncListenerMatcher"]:
    -    if isinstance(constraints, (str, Pattern)):
    -        return view_submission(constraints, asyncio)
    -    elif "type" in constraints:
    -        if constraints["type"] == "view_submission":
    -            return view_submission(constraints["callback_id"], asyncio)
    -        if constraints["type"] == "view_closed":
    -            return view_closed(constraints["callback_id"], asyncio)
    -
    -    raise BoltError(f"view ({constraints}: {type(constraints)}) must be any of str, Pattern, and dict")
    -
    -
    -
    -def view_closed(callback_id:ย Union[str,ย re.Pattern], asyncio:ย boolย =ย False, base_logger:ย Optional[logging.Logger]ย =ย None) โ€‘>ย Union[ListenerMatcher,ย AsyncListenerMatcher] -
    -
    -
    -
    - -Expand source code - -
    def view_closed(
    -    callback_id: Union[str, Pattern],
    -    asyncio: bool = False,
    -    base_logger: Optional[Logger] = None,
    -) -> Union[ListenerMatcher, "AsyncListenerMatcher"]:
    -    def func(body: Dict[str, Any]) -> bool:
    -        return is_view_closed(body) and _matches(callback_id, body["view"]["callback_id"])
    -
    -    return build_listener_matcher(func, asyncio, base_logger)
    -
    -
    -
    -def view_submission(callback_id:ย Union[str,ย re.Pattern], asyncio:ย boolย =ย False, base_logger:ย Optional[logging.Logger]ย =ย None) โ€‘>ย Union[ListenerMatcher,ย AsyncListenerMatcher] -
    -
    -
    -
    - -Expand source code - -
    def view_submission(
    -    callback_id: Union[str, Pattern],
    -    asyncio: bool = False,
    -    base_logger: Optional[Logger] = None,
    -) -> Union[ListenerMatcher, "AsyncListenerMatcher"]:
    -    def func(body: Dict[str, Any]) -> bool:
    -        return is_view_submission(body) and _matches(callback_id, body["view"]["callback_id"])
    -
    -    return build_listener_matcher(func, asyncio, base_logger)
    -
    -
    -
    -def workflow_step_edit(callback_id:ย Union[str,ย re.Pattern], asyncio:ย boolย =ย False, base_logger:ย Optional[logging.Logger]ย =ย None) โ€‘>ย Union[ListenerMatcher,ย AsyncListenerMatcher] -
    -
    -
    -
    - -Expand source code - -
    def workflow_step_edit(
    -    callback_id: Union[str, Pattern],
    -    asyncio: bool = False,
    -    base_logger: Optional[Logger] = None,
    -) -> Union[ListenerMatcher, "AsyncListenerMatcher"]:
    -    def func(body: Dict[str, Any]) -> bool:
    -        return _workflow_step_edit(callback_id, body)
    -
    -    return build_listener_matcher(func, asyncio, base_logger)
    -
    -
    -
    -def workflow_step_execute(callback_id:ย Union[str,ย re.Pattern], asyncio:ย boolย =ย False, base_logger:ย Optional[logging.Logger]ย =ย None) โ€‘>ย Union[ListenerMatcher,ย AsyncListenerMatcher] -
    -
    -
    -
    - -Expand source code - -
    def workflow_step_execute(
    -    callback_id: Union[str, Pattern],
    -    asyncio: bool = False,
    -    base_logger: Optional[Logger] = None,
    -) -> Union[ListenerMatcher, "AsyncListenerMatcher"]:
    -    def func(body: Dict[str, Any]) -> bool:
    -        return (
    -            is_event(body)
    -            and _matches("workflow_step_execute", body["event"]["type"])
    -            and "workflow_step" in body["event"]
    -            and _matches(callback_id, body["event"]["callback_id"])
    -        )
    -
    -    return build_listener_matcher(func, asyncio, base_logger)
    -
    -
    -
    -def workflow_step_save(callback_id:ย Union[str,ย re.Pattern], asyncio:ย boolย =ย False, base_logger:ย Optional[logging.Logger]ย =ย None) โ€‘>ย Union[ListenerMatcher,ย AsyncListenerMatcher] -
    -
    -
    -
    - -Expand source code - -
    def workflow_step_save(
    -    callback_id: Union[str, Pattern],
    -    asyncio: bool = False,
    -    base_logger: Optional[Logger] = None,
    -) -> Union[ListenerMatcher, "AsyncListenerMatcher"]:
    -    def func(body: Dict[str, Any]) -> bool:
    -        return is_workflow_step_save(body) and _matches(callback_id, body["view"]["callback_id"])
    -
    -    return build_listener_matcher(func, asyncio, base_logger)
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class BuiltinListenerMatcher -(*, func:ย Callable[...,ย Union[bool,ย Awaitable[bool]]], base_logger:ย Optional[logging.Logger]ย =ย None) -
    -
    -
    -
    - -Expand source code - -
    class BuiltinListenerMatcher(ListenerMatcher):
    -    def __init__(
    -        self,
    -        *,
    -        func: Callable[..., Union[bool, Awaitable[bool]]],
    -        base_logger: Optional[Logger] = None,
    -    ):
    -        self.func = func
    -        self.arg_names = get_arg_names_of_callable(func)
    -        self.logger = get_bolt_logger(self.func, base_logger)
    -
    -    def matches(self, req: BoltRequest, resp: BoltResponse) -> bool:
    -        return self.func(
    -            **build_required_kwargs(
    -                logger=self.logger,
    -                required_arg_names=self.arg_names,
    -                request=req,
    -                response=resp,
    -                this_func=self.func,
    -            )
    -        )
    -
    -

    Ancestors

    - -

    Subclasses

    - -

    Inherited members

    - -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/listener_matcher/custom_listener_matcher.html b/docs/api-docs/slack_bolt/listener_matcher/custom_listener_matcher.html deleted file mode 100644 index 4e286a6d5..000000000 --- a/docs/api-docs/slack_bolt/listener_matcher/custom_listener_matcher.html +++ /dev/null @@ -1,172 +0,0 @@ - - - - - - -slack_bolt.listener_matcher.custom_listener_matcher API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.listener_matcher.custom_listener_matcher

    -
    -
    -
    - -Expand source code - -
    from logging import Logger
    -from typing import Callable, Sequence, Optional
    -
    -from slack_bolt.kwargs_injection import build_required_kwargs
    -from slack_bolt.logger import get_bolt_app_logger
    -from slack_bolt.request import BoltRequest
    -from slack_bolt.response import BoltResponse
    -from .listener_matcher import ListenerMatcher
    -from ..util.utils import get_arg_names_of_callable
    -
    -
    -class CustomListenerMatcher(ListenerMatcher):
    -    app_name: str
    -    func: Callable[..., bool]
    -    arg_names: Sequence[str]
    -    logger: Logger
    -
    -    def __init__(self, *, app_name: str, func: Callable[..., bool], base_logger: Optional[Logger] = None):
    -        self.app_name = app_name
    -        self.func = func
    -        self.arg_names = get_arg_names_of_callable(func)
    -        self.logger = get_bolt_app_logger(self.app_name, self.func, base_logger)
    -
    -    def matches(self, req: BoltRequest, resp: BoltResponse) -> bool:
    -        return self.func(
    -            **build_required_kwargs(
    -                logger=self.logger,
    -                required_arg_names=self.arg_names,
    -                request=req,
    -                response=resp,
    -                this_func=self.func,
    -            )
    -        )
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class CustomListenerMatcher -(*, app_name:ย str, func:ย Callable[...,ย bool], base_logger:ย Optional[logging.Logger]ย =ย None) -
    -
    -
    -
    - -Expand source code - -
    class CustomListenerMatcher(ListenerMatcher):
    -    app_name: str
    -    func: Callable[..., bool]
    -    arg_names: Sequence[str]
    -    logger: Logger
    -
    -    def __init__(self, *, app_name: str, func: Callable[..., bool], base_logger: Optional[Logger] = None):
    -        self.app_name = app_name
    -        self.func = func
    -        self.arg_names = get_arg_names_of_callable(func)
    -        self.logger = get_bolt_app_logger(self.app_name, self.func, base_logger)
    -
    -    def matches(self, req: BoltRequest, resp: BoltResponse) -> bool:
    -        return self.func(
    -            **build_required_kwargs(
    -                logger=self.logger,
    -                required_arg_names=self.arg_names,
    -                request=req,
    -                response=resp,
    -                this_func=self.func,
    -            )
    -        )
    -
    -

    Ancestors

    - -

    Class variables

    -
    -
    var app_name :ย str
    -
    -
    -
    -
    var arg_names :ย Sequence[str]
    -
    -
    -
    -
    var func :ย Callable[...,ย bool]
    -
    -
    -
    -
    var logger :ย logging.Logger
    -
    -
    -
    -
    -

    Inherited members

    - -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/listener_matcher/listener_matcher.html b/docs/api-docs/slack_bolt/listener_matcher/listener_matcher.html deleted file mode 100644 index c9ed6ee8b..000000000 --- a/docs/api-docs/slack_bolt/listener_matcher/listener_matcher.html +++ /dev/null @@ -1,154 +0,0 @@ - - - - - - -slack_bolt.listener_matcher.listener_matcher API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.listener_matcher.listener_matcher

    -
    -
    -
    - -Expand source code - -
    from abc import abstractmethod, ABCMeta
    -
    -from slack_bolt.request import BoltRequest
    -from slack_bolt.response import BoltResponse
    -
    -
    -class ListenerMatcher(metaclass=ABCMeta):
    -    @abstractmethod
    -    def matches(self, req: BoltRequest, resp: BoltResponse) -> bool:
    -        """Matches against the request and returns True if matched.
    -
    -        Args:
    -            req: The request
    -            resp: The response
    -
    -        Returns:
    -            True if matched.
    -        """
    -        raise NotImplementedError()
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class ListenerMatcher -
    -
    -
    -
    - -Expand source code - -
    class ListenerMatcher(metaclass=ABCMeta):
    -    @abstractmethod
    -    def matches(self, req: BoltRequest, resp: BoltResponse) -> bool:
    -        """Matches against the request and returns True if matched.
    -
    -        Args:
    -            req: The request
    -            resp: The response
    -
    -        Returns:
    -            True if matched.
    -        """
    -        raise NotImplementedError()
    -
    -

    Subclasses

    - -

    Methods

    -
    -
    -def matches(self, req:ย BoltRequest, resp:ย BoltResponse) โ€‘>ย bool -
    -
    -

    Matches against the request and returns True if matched.

    -

    Args

    -
    -
    req
    -
    The request
    -
    resp
    -
    The response
    -
    -

    Returns

    -

    True if matched.

    -
    - -Expand source code - -
    @abstractmethod
    -def matches(self, req: BoltRequest, resp: BoltResponse) -> bool:
    -    """Matches against the request and returns True if matched.
    -
    -    Args:
    -        req: The request
    -        resp: The response
    -
    -    Returns:
    -        True if matched.
    -    """
    -    raise NotImplementedError()
    -
    -
    -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/logger/index.html b/docs/api-docs/slack_bolt/logger/index.html deleted file mode 100644 index 1a829be20..000000000 --- a/docs/api-docs/slack_bolt/logger/index.html +++ /dev/null @@ -1,167 +0,0 @@ - - - - - - -slack_bolt.logger API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.logger

    -
    -
    -

    Bolt for Python relies on the standard logging module.

    -
    - -Expand source code - -
    """Bolt for Python relies on the standard `logging` module."""
    -
    -import logging
    -from logging import Logger
    -from typing import Any, Optional
    -
    -
    -def get_bolt_logger(cls: Any, base_logger: Optional[Logger] = None) -> Logger:
    -    logger = logging.getLogger(f"slack_bolt.{cls.__name__}")
    -    if base_logger is not None:
    -        _configure_from_base_logger(logger, base_logger)
    -    else:
    -        _configure_from_root(logger)
    -    return logger
    -
    -
    -def get_bolt_app_logger(app_name: str, cls: object = None, base_logger: Optional[Logger] = None) -> Logger:
    -    logger: Logger = (
    -        logging.getLogger(f"{app_name}:{cls.__name__}") if cls and hasattr(cls, "__name__") else logging.getLogger(app_name)
    -    )
    -
    -    if base_logger is not None:
    -        _configure_from_base_logger(logger, base_logger)
    -    else:
    -        _configure_from_root(logger)
    -    return logger
    -
    -
    -def _configure_from_base_logger(new_logger: Logger, base_logger: Logger):
    -    new_logger.disabled = base_logger.disabled
    -    new_logger.level = base_logger.level
    -    if len(new_logger.handlers) == 0:
    -        for h in base_logger.handlers:
    -            new_logger.addHandler(h)
    -    if len(new_logger.filters) == 0:
    -        for f in base_logger.filters:
    -            new_logger.addFilter(f)
    -
    -
    -def _configure_from_root(new_logger: Logger):
    -    new_logger.disabled = logging.root.disabled
    -    new_logger.level = logging.root.level
    -
    -
    -__all__ = [
    -    "get_bolt_logger",
    -    "get_bolt_app_logger",
    -]
    -
    -
    -
    -

    Sub-modules

    -
    -
    slack_bolt.logger.messages
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Functions

    -
    -
    -def get_bolt_app_logger(app_name:ย str, cls:ย objectย =ย None, base_logger:ย Optional[logging.Logger]ย =ย None) โ€‘>ย logging.Logger -
    -
    -
    -
    - -Expand source code - -
    def get_bolt_app_logger(app_name: str, cls: object = None, base_logger: Optional[Logger] = None) -> Logger:
    -    logger: Logger = (
    -        logging.getLogger(f"{app_name}:{cls.__name__}") if cls and hasattr(cls, "__name__") else logging.getLogger(app_name)
    -    )
    -
    -    if base_logger is not None:
    -        _configure_from_base_logger(logger, base_logger)
    -    else:
    -        _configure_from_root(logger)
    -    return logger
    -
    -
    -
    -def get_bolt_logger(cls:ย Any, base_logger:ย Optional[logging.Logger]ย =ย None) โ€‘>ย logging.Logger -
    -
    -
    -
    - -Expand source code - -
    def get_bolt_logger(cls: Any, base_logger: Optional[Logger] = None) -> Logger:
    -    logger = logging.getLogger(f"slack_bolt.{cls.__name__}")
    -    if base_logger is not None:
    -        _configure_from_base_logger(logger, base_logger)
    -    else:
    -        _configure_from_root(logger)
    -    return logger
    -
    -
    -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/middleware/async_custom_middleware.html b/docs/api-docs/slack_bolt/middleware/async_custom_middleware.html deleted file mode 100644 index f3909b5db..000000000 --- a/docs/api-docs/slack_bolt/middleware/async_custom_middleware.html +++ /dev/null @@ -1,222 +0,0 @@ - - - - - - -slack_bolt.middleware.async_custom_middleware API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.middleware.async_custom_middleware

    -
    -
    -
    - -Expand source code - -
    import inspect
    -from logging import Logger
    -from typing import Callable, Awaitable, Any, Sequence, Optional
    -
    -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
    -from .async_middleware import AsyncMiddleware
    -from slack_bolt.util.utils import get_name_for_callable, get_arg_names_of_callable
    -
    -
    -class AsyncCustomMiddleware(AsyncMiddleware):
    -    app_name: str
    -    func: Callable[..., Awaitable[Any]]
    -    arg_names: Sequence[str]
    -    logger: Logger
    -
    -    def __init__(
    -        self,
    -        *,
    -        app_name: str,
    -        func: Callable[..., Awaitable[Any]],
    -        base_logger: Optional[Logger] = None,
    -    ):
    -        self.app_name = app_name
    -        if inspect.iscoroutinefunction(func):
    -            self.func = func
    -        else:
    -            raise ValueError("Async middleware function must be an async function")
    -
    -        self.arg_names = get_arg_names_of_callable(func)
    -        self.logger = get_bolt_app_logger(self.app_name, self.func, base_logger)
    -
    -    async def async_process(
    -        self,
    -        *,
    -        req: AsyncBoltRequest,
    -        resp: BoltResponse,
    -        # 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
    -        next: Callable[[], Awaitable[BoltResponse]],
    -    ) -> BoltResponse:
    -        return await self.func(
    -            **build_async_required_kwargs(
    -                logger=self.logger,
    -                required_arg_names=self.arg_names,
    -                request=req,
    -                response=resp,
    -                next_func=next,
    -                this_func=self.func,
    -            )
    -        )
    -
    -    @property
    -    def name(self) -> str:
    -        return f"AsyncCustomMiddleware(func={get_name_for_callable(self.func)})"
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class AsyncCustomMiddleware -(*, app_name:ย str, func:ย Callable[...,ย Awaitable[Any]], base_logger:ย Optional[logging.Logger]ย =ย None) -
    -
    -

    A middleware can process request data before other middleware and listener functions.

    -
    - -Expand source code - -
    class AsyncCustomMiddleware(AsyncMiddleware):
    -    app_name: str
    -    func: Callable[..., Awaitable[Any]]
    -    arg_names: Sequence[str]
    -    logger: Logger
    -
    -    def __init__(
    -        self,
    -        *,
    -        app_name: str,
    -        func: Callable[..., Awaitable[Any]],
    -        base_logger: Optional[Logger] = None,
    -    ):
    -        self.app_name = app_name
    -        if inspect.iscoroutinefunction(func):
    -            self.func = func
    -        else:
    -            raise ValueError("Async middleware function must be an async function")
    -
    -        self.arg_names = get_arg_names_of_callable(func)
    -        self.logger = get_bolt_app_logger(self.app_name, self.func, base_logger)
    -
    -    async def async_process(
    -        self,
    -        *,
    -        req: AsyncBoltRequest,
    -        resp: BoltResponse,
    -        # 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
    -        next: Callable[[], Awaitable[BoltResponse]],
    -    ) -> BoltResponse:
    -        return await self.func(
    -            **build_async_required_kwargs(
    -                logger=self.logger,
    -                required_arg_names=self.arg_names,
    -                request=req,
    -                response=resp,
    -                next_func=next,
    -                this_func=self.func,
    -            )
    -        )
    -
    -    @property
    -    def name(self) -> str:
    -        return f"AsyncCustomMiddleware(func={get_name_for_callable(self.func)})"
    -
    -

    Ancestors

    - -

    Class variables

    -
    -
    var app_name :ย str
    -
    -
    -
    -
    var arg_names :ย Sequence[str]
    -
    -
    -
    -
    var func :ย Callable[...,ย Awaitable[Any]]
    -
    -
    -
    -
    var logger :ย logging.Logger
    -
    -
    -
    -
    -

    Inherited members

    - -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/middleware/authorization/async_authorization.html b/docs/api-docs/slack_bolt/middleware/authorization/async_authorization.html deleted file mode 100644 index 40ca48ece..000000000 --- a/docs/api-docs/slack_bolt/middleware/authorization/async_authorization.html +++ /dev/null @@ -1,107 +0,0 @@ - - - - - - -slack_bolt.middleware.authorization.async_authorization API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.middleware.authorization.async_authorization

    -
    -
    -
    - -Expand source code - -
    from abc import ABC
    -
    -from slack_bolt.middleware.async_middleware import AsyncMiddleware
    -
    -
    -class AsyncAuthorization(AsyncMiddleware, ABC):
    -    pass
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class AsyncAuthorization -
    -
    -

    A middleware can process request data before other middleware and listener functions.

    -
    - -Expand source code - -
    class AsyncAuthorization(AsyncMiddleware, ABC):
    -    pass
    -
    -

    Ancestors

    - -

    Subclasses

    - -

    Inherited members

    - -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/middleware/authorization/async_internals.html b/docs/api-docs/slack_bolt/middleware/authorization/async_internals.html deleted file mode 100644 index f7a7a6da5..000000000 --- a/docs/api-docs/slack_bolt/middleware/authorization/async_internals.html +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - -slack_bolt.middleware.authorization.async_internals API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.middleware.authorization.async_internals

    -
    -
    -
    - -Expand source code - -
    from slack_bolt.middleware.authorization.internals import _build_error_text
    -from slack_bolt.request.async_request import AsyncBoltRequest
    -from slack_bolt.response import BoltResponse
    -
    -
    -def _is_url_verification(req: AsyncBoltRequest) -> bool:
    -    return req is not None and req.body is not None and req.body.get("type") == "url_verification"
    -
    -
    -def _is_ssl_check(req: AsyncBoltRequest) -> bool:
    -    return req is not None and req.body is not None and req.body.get("type") == "ssl_check"
    -
    -
    -def _is_no_auth_required(req: AsyncBoltRequest) -> bool:
    -    return _is_url_verification(req) or _is_ssl_check(req)
    -
    -
    -def _build_error_response() -> BoltResponse:
    -    # show an ephemeral message to the end-user
    -    return BoltResponse(
    -        status=200,
    -        body=_build_error_text(),
    -    )
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/middleware/authorization/async_single_team_authorization.html b/docs/api-docs/slack_bolt/middleware/authorization/async_single_team_authorization.html deleted file mode 100644 index 8f3bcff67..000000000 --- a/docs/api-docs/slack_bolt/middleware/authorization/async_single_team_authorization.html +++ /dev/null @@ -1,214 +0,0 @@ - - - - - - -slack_bolt.middleware.authorization.async_single_team_authorization API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.middleware.authorization.async_single_team_authorization

    -
    -
    -
    - -Expand source code - -
    from logging import Logger
    -from typing import Callable, Awaitable, Optional
    -
    -from slack_bolt.logger import get_bolt_logger
    -from slack_bolt.middleware.authorization.async_authorization import AsyncAuthorization
    -from slack_bolt.request.async_request import AsyncBoltRequest
    -from slack_bolt.response import BoltResponse
    -from slack_sdk.web.async_slack_response import AsyncSlackResponse
    -from slack_sdk.errors import SlackApiError
    -from .async_internals import _build_error_response, _is_no_auth_required
    -from .internals import _to_authorize_result, _is_no_auth_test_call_required, _build_error_text
    -from ...authorization import AuthorizeResult
    -
    -
    -class AsyncSingleTeamAuthorization(AsyncAuthorization):
    -    def __init__(self, base_logger: Optional[Logger] = None):
    -        """Single-workspace authorization."""
    -        self.auth_test_result: Optional[AsyncSlackResponse] = None
    -        self.logger = get_bolt_logger(AsyncSingleTeamAuthorization, base_logger=base_logger)
    -
    -    async def async_process(
    -        self,
    -        *,
    -        req: AsyncBoltRequest,
    -        resp: BoltResponse,
    -        # 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
    -        next: Callable[[], Awaitable[BoltResponse]],
    -    ) -> BoltResponse:
    -        if _is_no_auth_required(req):
    -            return await next()
    -
    -        if _is_no_auth_test_call_required(req):
    -            req.context.set_authorize_result(
    -                AuthorizeResult(
    -                    enterprise_id=req.context.enterprise_id,
    -                    team_id=req.context.team_id,
    -                    user_id=req.context.user_id,
    -                )
    -            )
    -            return await next()
    -
    -        try:
    -            if self.auth_test_result is None:
    -                self.auth_test_result = await req.context.client.auth_test()
    -
    -            if self.auth_test_result:
    -                req.context.set_authorize_result(
    -                    _to_authorize_result(
    -                        auth_test_result=self.auth_test_result,
    -                        token=req.context.client.token,
    -                        request_user_id=req.context.user_id,
    -                    )
    -                )
    -                return await next()
    -            else:
    -                # Just in case
    -                self.logger.error("auth.test API call result is unexpectedly None")
    -                if req.context.response_url is not None:
    -                    await req.context.respond(_build_error_text())
    -                    return BoltResponse(status=200, body="")
    -                return _build_error_response()
    -        except SlackApiError as e:
    -            self.logger.error(f"Failed to authorize with the given token ({e})")
    -            return _build_error_response()
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class AsyncSingleTeamAuthorization -(base_logger:ย Optional[logging.Logger]ย =ย None) -
    -
    -

    A middleware can process request data before other middleware and listener functions.

    -

    Single-workspace authorization.

    -
    - -Expand source code - -
    class AsyncSingleTeamAuthorization(AsyncAuthorization):
    -    def __init__(self, base_logger: Optional[Logger] = None):
    -        """Single-workspace authorization."""
    -        self.auth_test_result: Optional[AsyncSlackResponse] = None
    -        self.logger = get_bolt_logger(AsyncSingleTeamAuthorization, base_logger=base_logger)
    -
    -    async def async_process(
    -        self,
    -        *,
    -        req: AsyncBoltRequest,
    -        resp: BoltResponse,
    -        # 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
    -        next: Callable[[], Awaitable[BoltResponse]],
    -    ) -> BoltResponse:
    -        if _is_no_auth_required(req):
    -            return await next()
    -
    -        if _is_no_auth_test_call_required(req):
    -            req.context.set_authorize_result(
    -                AuthorizeResult(
    -                    enterprise_id=req.context.enterprise_id,
    -                    team_id=req.context.team_id,
    -                    user_id=req.context.user_id,
    -                )
    -            )
    -            return await next()
    -
    -        try:
    -            if self.auth_test_result is None:
    -                self.auth_test_result = await req.context.client.auth_test()
    -
    -            if self.auth_test_result:
    -                req.context.set_authorize_result(
    -                    _to_authorize_result(
    -                        auth_test_result=self.auth_test_result,
    -                        token=req.context.client.token,
    -                        request_user_id=req.context.user_id,
    -                    )
    -                )
    -                return await next()
    -            else:
    -                # Just in case
    -                self.logger.error("auth.test API call result is unexpectedly None")
    -                if req.context.response_url is not None:
    -                    await req.context.respond(_build_error_text())
    -                    return BoltResponse(status=200, body="")
    -                return _build_error_response()
    -        except SlackApiError as e:
    -            self.logger.error(f"Failed to authorize with the given token ({e})")
    -            return _build_error_response()
    -
    -

    Ancestors

    - -

    Inherited members

    - -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/middleware/authorization/authorization.html b/docs/api-docs/slack_bolt/middleware/authorization/authorization.html deleted file mode 100644 index 91cf574df..000000000 --- a/docs/api-docs/slack_bolt/middleware/authorization/authorization.html +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - -slack_bolt.middleware.authorization.authorization API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.middleware.authorization.authorization

    -
    -
    -
    - -Expand source code - -
    from ..middleware import Middleware
    -
    -
    -class Authorization(Middleware):
    -    pass
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class Authorization -
    -
    -

    A middleware can process request data before other middleware and listener functions.

    -
    - -Expand source code - -
    class Authorization(Middleware):
    -    pass
    -
    -

    Ancestors

    - -

    Subclasses

    - -

    Inherited members

    - -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/middleware/authorization/internals.html b/docs/api-docs/slack_bolt/middleware/authorization/internals.html deleted file mode 100644 index 4cee299f3..000000000 --- a/docs/api-docs/slack_bolt/middleware/authorization/internals.html +++ /dev/null @@ -1,137 +0,0 @@ - - - - - - -slack_bolt.middleware.authorization.internals API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.middleware.authorization.internals

    -
    -
    -
    - -Expand source code - -
    from typing import Optional, Union
    -
    -from slack_sdk.web import SlackResponse
    -
    -from slack_bolt.authorization import AuthorizeResult
    -from slack_bolt.request.request import BoltRequest
    -from slack_bolt.response import BoltResponse
    -
    -#
    -# NOTE: this source file intentionally avoids having a reference to
    -# AsyncBoltRequest, AsyncSlackResponse, and whatever Async-prefixed.
    -#
    -# The reason why we do so is to enable developers use sync version of Bolt
    -# without installing aiohttp library (or any others we may use for async things)
    -#
    -
    -
    -def _is_url_verification(req: Union[BoltRequest, "AsyncBoltRequest"]) -> bool:  # type: ignore
    -    return req is not None and req.body is not None and req.body.get("type") == "url_verification"
    -
    -
    -def _is_ssl_check(req: Union[BoltRequest, "AsyncBoltRequest"]) -> bool:  # type: ignore
    -    return req is not None and req.body is not None and req.body.get("type") == "ssl_check"
    -
    -
    -no_auth_test_events = ["app_uninstalled", "tokens_revoked", "team_access_revoked"]
    -
    -
    -def _is_no_auth_test_events(req: Union[BoltRequest, "AsyncBoltRequest"]) -> bool:  # type: ignore
    -    return (
    -        req is not None
    -        and req.body is not None
    -        and req.body.get("type") == "event_callback"
    -        and req.body.get("event", {}).get("type") in no_auth_test_events
    -    )
    -
    -
    -def _is_no_auth_required(req: Union[BoltRequest, "AsyncBoltRequest"]) -> bool:  # type: ignore
    -    return _is_url_verification(req) or _is_ssl_check(req)
    -
    -
    -def _is_no_auth_test_call_required(req: Union[BoltRequest, "AsyncBoltRequest"]) -> bool:  # type: ignore
    -    return _is_no_auth_test_events(req)
    -
    -
    -def _build_error_text() -> str:
    -    return (
    -        ":warning: We apologize, but for some unknown reason, your installation with this app is no longer available. "
    -        "Please reinstall this app into your workspace :bow:"
    -    )
    -
    -
    -def _build_error_response() -> BoltResponse:
    -    # show an ephemeral message to the end-user
    -    return BoltResponse(
    -        status=200,
    -        body=_build_error_text(),
    -    )
    -
    -
    -def _is_bot_token(token: Optional[str]) -> bool:
    -    return token is not None and token.startswith("xoxb-")
    -
    -
    -def _to_authorize_result(  # type: ignore
    -    auth_test_result: Union[SlackResponse, "AsyncSlackResponse"],
    -    token: Optional[str],
    -    request_user_id: Optional[str],
    -) -> AuthorizeResult:
    -    user_id = auth_test_result.get("user_id")
    -    return AuthorizeResult(
    -        enterprise_id=auth_test_result.get("enterprise_id"),
    -        team_id=auth_test_result.get("team_id"),
    -        bot_id=auth_test_result.get("bot_id"),
    -        bot_user_id=user_id if _is_bot_token(token) else None,
    -        bot_token=token if _is_bot_token(token) else None,
    -        user_id=request_user_id or (user_id if not _is_bot_token(token) else None),
    -        user_token=token if not _is_bot_token(token) else None,
    -    )
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/middleware/authorization/multi_teams_authorization.html b/docs/api-docs/slack_bolt/middleware/authorization/multi_teams_authorization.html deleted file mode 100644 index 8b5bda9e9..000000000 --- a/docs/api-docs/slack_bolt/middleware/authorization/multi_teams_authorization.html +++ /dev/null @@ -1,308 +0,0 @@ - - - - - - -slack_bolt.middleware.authorization.multi_teams_authorization API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.middleware.authorization.multi_teams_authorization

    -
    -
    -
    - -Expand source code - -
    from logging import Logger
    -from typing import Callable, Optional
    -
    -from slack_sdk.errors import SlackApiError
    -
    -from slack_bolt.logger import get_bolt_logger
    -from slack_bolt.request import BoltRequest
    -from slack_bolt.response import BoltResponse
    -from .authorization import Authorization
    -from .internals import (
    -    _build_error_response,
    -    _is_no_auth_required,
    -    _is_no_auth_test_call_required,
    -    _build_error_text,
    -)
    -from ...authorization import AuthorizeResult
    -from ...authorization.authorize import Authorize
    -
    -
    -class MultiTeamsAuthorization(Authorization):
    -    authorize: Authorize
    -    user_token_resolution: str
    -
    -    def __init__(
    -        self,
    -        *,
    -        authorize: Authorize,
    -        base_logger: Optional[Logger] = None,
    -        user_token_resolution: str = "authed_user",
    -    ):
    -        """Multi-workspace authorization.
    -
    -        Args:
    -            authorize: The function to authorize incoming requests from Slack.
    -            base_logger: The base logger
    -            user_token_resolution: "authed_user" or "actor"
    -        """
    -        self.authorize = authorize
    -        self.logger = get_bolt_logger(MultiTeamsAuthorization, base_logger=base_logger)
    -        self.user_token_resolution = user_token_resolution
    -
    -    def process(
    -        self,
    -        *,
    -        req: BoltRequest,
    -        resp: BoltResponse,
    -        next: Callable[[], BoltResponse],
    -    ) -> BoltResponse:
    -        if _is_no_auth_required(req):
    -            return next()
    -
    -        if _is_no_auth_test_call_required(req):
    -            req.context.set_authorize_result(
    -                AuthorizeResult(
    -                    enterprise_id=req.context.enterprise_id,
    -                    team_id=req.context.team_id,
    -                    user_id=req.context.user_id,
    -                )
    -            )
    -            return next()
    -
    -        try:
    -            auth_result: Optional[AuthorizeResult] = None
    -            if self.user_token_resolution == "actor":
    -                auth_result = self.authorize(
    -                    context=req.context,
    -                    enterprise_id=req.context.enterprise_id,
    -                    team_id=req.context.team_id,
    -                    user_id=req.context.user_id,
    -                    actor_enterprise_id=req.context.actor_enterprise_id,
    -                    actor_team_id=req.context.actor_team_id,
    -                    actor_user_id=req.context.actor_user_id,
    -                )
    -            else:
    -                auth_result = self.authorize(
    -                    context=req.context,
    -                    enterprise_id=req.context.enterprise_id,
    -                    team_id=req.context.team_id,
    -                    user_id=req.context.user_id,
    -                )
    -            if auth_result is not None:
    -                req.context.set_authorize_result(auth_result)
    -                token = auth_result.bot_token or auth_result.user_token
    -                req.context["token"] = token
    -                # As App#_init_context() generates a new WebClient for this request,
    -                # it's safe to modify this instance.
    -                req.context.client.token = token
    -                return next()
    -            else:
    -                # This situation can arise if:
    -                # * A developer installed the app from the "Install to Workspace" button in Slack app config page
    -                # * The InstallationStore failed to save or deleted the installation for this workspace
    -                self.logger.error(
    -                    "Although the app should be installed into this workspace, "
    -                    "the AuthorizeResult (returned value from authorize) for it was not found."
    -                )
    -                if req.context.response_url is not None:
    -                    req.context.respond(_build_error_text())
    -                    return BoltResponse(status=200, body="")
    -                return _build_error_response()
    -
    -        except SlackApiError as e:
    -            self.logger.error(f"Failed to authorize with the given token ({e})")
    -            return _build_error_response()
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class MultiTeamsAuthorization -(*, authorize:ย Authorize, base_logger:ย Optional[logging.Logger]ย =ย None, user_token_resolution:ย strย =ย 'authed_user') -
    -
    -

    A middleware can process request data before other middleware and listener functions.

    -

    Multi-workspace authorization.

    -

    Args

    -
    -
    authorize
    -
    The function to authorize incoming requests from Slack.
    -
    base_logger
    -
    The base logger
    -
    user_token_resolution
    -
    "authed_user" or "actor"
    -
    -
    - -Expand source code - -
    class MultiTeamsAuthorization(Authorization):
    -    authorize: Authorize
    -    user_token_resolution: str
    -
    -    def __init__(
    -        self,
    -        *,
    -        authorize: Authorize,
    -        base_logger: Optional[Logger] = None,
    -        user_token_resolution: str = "authed_user",
    -    ):
    -        """Multi-workspace authorization.
    -
    -        Args:
    -            authorize: The function to authorize incoming requests from Slack.
    -            base_logger: The base logger
    -            user_token_resolution: "authed_user" or "actor"
    -        """
    -        self.authorize = authorize
    -        self.logger = get_bolt_logger(MultiTeamsAuthorization, base_logger=base_logger)
    -        self.user_token_resolution = user_token_resolution
    -
    -    def process(
    -        self,
    -        *,
    -        req: BoltRequest,
    -        resp: BoltResponse,
    -        next: Callable[[], BoltResponse],
    -    ) -> BoltResponse:
    -        if _is_no_auth_required(req):
    -            return next()
    -
    -        if _is_no_auth_test_call_required(req):
    -            req.context.set_authorize_result(
    -                AuthorizeResult(
    -                    enterprise_id=req.context.enterprise_id,
    -                    team_id=req.context.team_id,
    -                    user_id=req.context.user_id,
    -                )
    -            )
    -            return next()
    -
    -        try:
    -            auth_result: Optional[AuthorizeResult] = None
    -            if self.user_token_resolution == "actor":
    -                auth_result = self.authorize(
    -                    context=req.context,
    -                    enterprise_id=req.context.enterprise_id,
    -                    team_id=req.context.team_id,
    -                    user_id=req.context.user_id,
    -                    actor_enterprise_id=req.context.actor_enterprise_id,
    -                    actor_team_id=req.context.actor_team_id,
    -                    actor_user_id=req.context.actor_user_id,
    -                )
    -            else:
    -                auth_result = self.authorize(
    -                    context=req.context,
    -                    enterprise_id=req.context.enterprise_id,
    -                    team_id=req.context.team_id,
    -                    user_id=req.context.user_id,
    -                )
    -            if auth_result is not None:
    -                req.context.set_authorize_result(auth_result)
    -                token = auth_result.bot_token or auth_result.user_token
    -                req.context["token"] = token
    -                # As App#_init_context() generates a new WebClient for this request,
    -                # it's safe to modify this instance.
    -                req.context.client.token = token
    -                return next()
    -            else:
    -                # This situation can arise if:
    -                # * A developer installed the app from the "Install to Workspace" button in Slack app config page
    -                # * The InstallationStore failed to save or deleted the installation for this workspace
    -                self.logger.error(
    -                    "Although the app should be installed into this workspace, "
    -                    "the AuthorizeResult (returned value from authorize) for it was not found."
    -                )
    -                if req.context.response_url is not None:
    -                    req.context.respond(_build_error_text())
    -                    return BoltResponse(status=200, body="")
    -                return _build_error_response()
    -
    -        except SlackApiError as e:
    -            self.logger.error(f"Failed to authorize with the given token ({e})")
    -            return _build_error_response()
    -
    -

    Ancestors

    - -

    Class variables

    -
    -
    var authorize :ย Authorize
    -
    -
    -
    -
    var user_token_resolution :ย str
    -
    -
    -
    -
    -

    Inherited members

    - -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/middleware/authorization/single_team_authorization.html b/docs/api-docs/slack_bolt/middleware/authorization/single_team_authorization.html deleted file mode 100644 index 9d023b2d6..000000000 --- a/docs/api-docs/slack_bolt/middleware/authorization/single_team_authorization.html +++ /dev/null @@ -1,245 +0,0 @@ - - - - - - -slack_bolt.middleware.authorization.single_team_authorization API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.middleware.authorization.single_team_authorization

    -
    -
    -
    - -Expand source code - -
    from logging import Logger
    -from typing import Callable, Optional
    -
    -from slack_bolt.logger import get_bolt_logger
    -from .authorization import Authorization
    -from slack_bolt.request import BoltRequest
    -from slack_bolt.response import BoltResponse
    -from slack_sdk.errors import SlackApiError
    -from slack_sdk.web import SlackResponse
    -from .internals import (
    -    _build_error_response,
    -    _is_no_auth_required,
    -    _to_authorize_result,
    -    _is_no_auth_test_call_required,
    -    _build_error_text,
    -)
    -from ...authorization import AuthorizeResult
    -
    -
    -class SingleTeamAuthorization(Authorization):
    -    def __init__(
    -        self,
    -        *,
    -        auth_test_result: Optional[SlackResponse] = None,
    -        base_logger: Optional[Logger] = None,
    -    ):
    -        """Single-workspace authorization.
    -
    -        Args:
    -            auth_test_result: The initial `auth.test` API call result.
    -            base_logger: The base logger
    -        """
    -        self.auth_test_result = auth_test_result
    -        self.logger = get_bolt_logger(SingleTeamAuthorization, base_logger=base_logger)
    -
    -    def process(
    -        self,
    -        *,
    -        req: BoltRequest,
    -        resp: BoltResponse,
    -        # 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
    -        next: Callable[[], BoltResponse],
    -    ) -> BoltResponse:
    -        if _is_no_auth_required(req):
    -            return next()
    -
    -        if _is_no_auth_test_call_required(req):
    -            req.context.set_authorize_result(
    -                AuthorizeResult(
    -                    enterprise_id=req.context.enterprise_id,
    -                    team_id=req.context.team_id,
    -                    user_id=req.context.user_id,
    -                )
    -            )
    -            return next()
    -
    -        try:
    -            if not self.auth_test_result:
    -                self.auth_test_result = req.context.client.auth_test()
    -
    -            if self.auth_test_result:
    -                req.context.set_authorize_result(
    -                    _to_authorize_result(
    -                        auth_test_result=self.auth_test_result,
    -                        token=req.context.client.token,
    -                        request_user_id=req.context.user_id,
    -                    )
    -                )
    -                return next()
    -            else:
    -                # Just in case
    -                self.logger.error("auth.test API call result is unexpectedly None")
    -                if req.context.response_url is not None:
    -                    req.context.respond(_build_error_text())
    -                    return BoltResponse(status=200, body="")
    -                return _build_error_response()
    -        except SlackApiError as e:
    -            self.logger.error(f"Failed to authorize with the given token ({e})")
    -            return _build_error_response()
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class SingleTeamAuthorization -(*, auth_test_result:ย Optional[slack_sdk.web.slack_response.SlackResponse]ย =ย None, base_logger:ย Optional[logging.Logger]ย =ย None) -
    -
    -

    A middleware can process request data before other middleware and listener functions.

    -

    Single-workspace authorization.

    -

    Args

    -
    -
    auth_test_result
    -
    The initial auth.test API call result.
    -
    base_logger
    -
    The base logger
    -
    -
    - -Expand source code - -
    class SingleTeamAuthorization(Authorization):
    -    def __init__(
    -        self,
    -        *,
    -        auth_test_result: Optional[SlackResponse] = None,
    -        base_logger: Optional[Logger] = None,
    -    ):
    -        """Single-workspace authorization.
    -
    -        Args:
    -            auth_test_result: The initial `auth.test` API call result.
    -            base_logger: The base logger
    -        """
    -        self.auth_test_result = auth_test_result
    -        self.logger = get_bolt_logger(SingleTeamAuthorization, base_logger=base_logger)
    -
    -    def process(
    -        self,
    -        *,
    -        req: BoltRequest,
    -        resp: BoltResponse,
    -        # 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
    -        next: Callable[[], BoltResponse],
    -    ) -> BoltResponse:
    -        if _is_no_auth_required(req):
    -            return next()
    -
    -        if _is_no_auth_test_call_required(req):
    -            req.context.set_authorize_result(
    -                AuthorizeResult(
    -                    enterprise_id=req.context.enterprise_id,
    -                    team_id=req.context.team_id,
    -                    user_id=req.context.user_id,
    -                )
    -            )
    -            return next()
    -
    -        try:
    -            if not self.auth_test_result:
    -                self.auth_test_result = req.context.client.auth_test()
    -
    -            if self.auth_test_result:
    -                req.context.set_authorize_result(
    -                    _to_authorize_result(
    -                        auth_test_result=self.auth_test_result,
    -                        token=req.context.client.token,
    -                        request_user_id=req.context.user_id,
    -                    )
    -                )
    -                return next()
    -            else:
    -                # Just in case
    -                self.logger.error("auth.test API call result is unexpectedly None")
    -                if req.context.response_url is not None:
    -                    req.context.respond(_build_error_text())
    -                    return BoltResponse(status=200, body="")
    -                return _build_error_response()
    -        except SlackApiError as e:
    -            self.logger.error(f"Failed to authorize with the given token ({e})")
    -            return _build_error_response()
    -
    -

    Ancestors

    - -

    Inherited members

    - -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/middleware/custom_middleware.html b/docs/api-docs/slack_bolt/middleware/custom_middleware.html deleted file mode 100644 index 17abf683f..000000000 --- a/docs/api-docs/slack_bolt/middleware/custom_middleware.html +++ /dev/null @@ -1,201 +0,0 @@ - - - - - - -slack_bolt.middleware.custom_middleware API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.middleware.custom_middleware

    -
    -
    -
    - -Expand source code - -
    from logging import Logger
    -from typing import Callable, Any, Sequence, Optional
    -
    -from slack_bolt.kwargs_injection import build_required_kwargs
    -from slack_bolt.logger import get_bolt_app_logger
    -from slack_bolt.request import BoltRequest
    -from slack_bolt.response import BoltResponse
    -from .middleware import Middleware
    -from slack_bolt.util.utils import get_name_for_callable, get_arg_names_of_callable
    -
    -
    -class CustomMiddleware(Middleware):
    -    app_name: str
    -    func: Callable[..., Any]
    -    arg_names: Sequence[str]
    -    logger: Logger
    -
    -    def __init__(self, *, app_name: str, func: Callable, base_logger: Optional[Logger] = None):
    -        self.app_name = app_name
    -        self.func = func
    -        self.arg_names = get_arg_names_of_callable(func)
    -        self.logger = get_bolt_app_logger(self.app_name, self.func, base_logger)
    -
    -    def process(
    -        self,
    -        *,
    -        req: BoltRequest,
    -        resp: BoltResponse,
    -        # 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
    -        next: Callable[[], BoltResponse],
    -    ) -> BoltResponse:
    -        return self.func(
    -            **build_required_kwargs(
    -                logger=self.logger,
    -                required_arg_names=self.arg_names,
    -                request=req,
    -                response=resp,
    -                next_func=next,
    -                this_func=self.func,
    -            )
    -        )
    -
    -    @property
    -    def name(self) -> str:
    -        return f"CustomMiddleware(func={get_name_for_callable(self.func)})"
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class CustomMiddleware -(*, app_name:ย str, func:ย Callable, base_logger:ย Optional[logging.Logger]ย =ย None) -
    -
    -

    A middleware can process request data before other middleware and listener functions.

    -
    - -Expand source code - -
    class CustomMiddleware(Middleware):
    -    app_name: str
    -    func: Callable[..., Any]
    -    arg_names: Sequence[str]
    -    logger: Logger
    -
    -    def __init__(self, *, app_name: str, func: Callable, base_logger: Optional[Logger] = None):
    -        self.app_name = app_name
    -        self.func = func
    -        self.arg_names = get_arg_names_of_callable(func)
    -        self.logger = get_bolt_app_logger(self.app_name, self.func, base_logger)
    -
    -    def process(
    -        self,
    -        *,
    -        req: BoltRequest,
    -        resp: BoltResponse,
    -        # 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
    -        next: Callable[[], BoltResponse],
    -    ) -> BoltResponse:
    -        return self.func(
    -            **build_required_kwargs(
    -                logger=self.logger,
    -                required_arg_names=self.arg_names,
    -                request=req,
    -                response=resp,
    -                next_func=next,
    -                this_func=self.func,
    -            )
    -        )
    -
    -    @property
    -    def name(self) -> str:
    -        return f"CustomMiddleware(func={get_name_for_callable(self.func)})"
    -
    -

    Ancestors

    - -

    Class variables

    -
    -
    var app_name :ย str
    -
    -
    -
    -
    var arg_names :ย Sequence[str]
    -
    -
    -
    -
    var func :ย Callable[...,ย Any]
    -
    -
    -
    -
    var logger :ย logging.Logger
    -
    -
    -
    -
    -

    Inherited members

    - -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/middleware/ignoring_self_events/async_ignoring_self_events.html b/docs/api-docs/slack_bolt/middleware/ignoring_self_events/async_ignoring_self_events.html deleted file mode 100644 index 752277bad..000000000 --- a/docs/api-docs/slack_bolt/middleware/ignoring_self_events/async_ignoring_self_events.html +++ /dev/null @@ -1,141 +0,0 @@ - - - - - - -slack_bolt.middleware.ignoring_self_events.async_ignoring_self_events API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.middleware.ignoring_self_events.async_ignoring_self_events

    -
    -
    -
    - -Expand source code - -
    from typing import Callable, Awaitable
    -
    -from slack_bolt.request.async_request import AsyncBoltRequest
    -from slack_bolt.response import BoltResponse
    -from .ignoring_self_events import IgnoringSelfEvents
    -from slack_bolt.middleware.async_middleware import AsyncMiddleware
    -
    -
    -class AsyncIgnoringSelfEvents(IgnoringSelfEvents, AsyncMiddleware):
    -    async def async_process(
    -        self,
    -        *,
    -        req: AsyncBoltRequest,
    -        resp: BoltResponse,
    -        next: Callable[[], Awaitable[BoltResponse]],
    -    ) -> BoltResponse:
    -        auth_result = req.context.authorize_result
    -        # message events can have $.event.bot_id while it does not have its user_id
    -        bot_id = req.body.get("event", {}).get("bot_id")
    -        if self._is_self_event(auth_result, req.context.user_id, bot_id, req.body):
    -            self._debug_log(req.body)
    -            return await req.context.ack()
    -        else:
    -            return await next()
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class AsyncIgnoringSelfEvents -(base_logger:ย Optional[logging.Logger]ย =ย None) -
    -
    -

    A middleware can process request data before other middleware and listener functions.

    -

    Ignores the events generated by this bot user itself.

    -
    - -Expand source code - -
    class AsyncIgnoringSelfEvents(IgnoringSelfEvents, AsyncMiddleware):
    -    async def async_process(
    -        self,
    -        *,
    -        req: AsyncBoltRequest,
    -        resp: BoltResponse,
    -        next: Callable[[], Awaitable[BoltResponse]],
    -    ) -> BoltResponse:
    -        auth_result = req.context.authorize_result
    -        # message events can have $.event.bot_id while it does not have its user_id
    -        bot_id = req.body.get("event", {}).get("bot_id")
    -        if self._is_self_event(auth_result, req.context.user_id, bot_id, req.body):
    -            self._debug_log(req.body)
    -            return await req.context.ack()
    -        else:
    -            return await next()
    -
    -

    Ancestors

    - -

    Inherited members

    - -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/middleware/ignoring_self_events/ignoring_self_events.html b/docs/api-docs/slack_bolt/middleware/ignoring_self_events/ignoring_self_events.html deleted file mode 100644 index 7cc174e91..000000000 --- a/docs/api-docs/slack_bolt/middleware/ignoring_self_events/ignoring_self_events.html +++ /dev/null @@ -1,218 +0,0 @@ - - - - - - -slack_bolt.middleware.ignoring_self_events.ignoring_self_events API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.middleware.ignoring_self_events.ignoring_self_events

    -
    -
    -
    - -Expand source code - -
    import logging
    -from typing import Callable, Dict, Any, Optional
    -
    -from slack_bolt.authorization import AuthorizeResult
    -from slack_bolt.logger import get_bolt_logger
    -from slack_bolt.request import BoltRequest
    -from slack_bolt.response import BoltResponse
    -from slack_bolt.middleware.middleware import Middleware
    -
    -
    -class IgnoringSelfEvents(Middleware):
    -    def __init__(self, base_logger: Optional[logging.Logger] = None):
    -        """Ignores the events generated by this bot user itself."""
    -        self.logger = get_bolt_logger(IgnoringSelfEvents, base_logger=base_logger)
    -
    -    def process(
    -        self,
    -        *,
    -        req: BoltRequest,
    -        resp: BoltResponse,
    -        next: Callable[[], BoltResponse],
    -    ) -> BoltResponse:
    -        auth_result = req.context.authorize_result
    -        # message events can have $.event.bot_id while it does not have its user_id
    -        bot_id = req.body.get("event", {}).get("bot_id")
    -        if self._is_self_event(auth_result, req.context.user_id, bot_id, req.body):
    -            self._debug_log(req.body)
    -            return req.context.ack()
    -        else:
    -            return next()
    -
    -    # -----------------------------------------
    -
    -    # It's an Events API event that isn't of type message,
    -    # but the user ID might match our own app. Filter these out.
    -    # However, some events still must be fired, because they can make sense.
    -    events_that_should_be_kept = ["member_joined_channel", "member_left_channel"]
    -
    -    @classmethod
    -    def _is_self_event(
    -        cls,
    -        auth_result: AuthorizeResult,
    -        user_id: Optional[str],
    -        bot_id: Optional[str],
    -        body: Dict[str, Any],
    -    ):
    -        return (
    -            auth_result is not None
    -            and (
    -                (user_id is not None and user_id == auth_result.bot_user_id)
    -                or (bot_id is not None and bot_id == auth_result.bot_id)  # for bot_message events
    -            )
    -            and body.get("event") is not None
    -            and body.get("event", {}).get("type") not in cls.events_that_should_be_kept
    -        )
    -
    -    def _debug_log(self, body: dict):
    -        if self.logger.level <= logging.DEBUG:
    -            event = body.get("event")
    -            self.logger.debug(f"Skipped self event: {event}")
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class IgnoringSelfEvents -(base_logger:ย Optional[logging.Logger]ย =ย None) -
    -
    -

    A middleware can process request data before other middleware and listener functions.

    -

    Ignores the events generated by this bot user itself.

    -
    - -Expand source code - -
    class IgnoringSelfEvents(Middleware):
    -    def __init__(self, base_logger: Optional[logging.Logger] = None):
    -        """Ignores the events generated by this bot user itself."""
    -        self.logger = get_bolt_logger(IgnoringSelfEvents, base_logger=base_logger)
    -
    -    def process(
    -        self,
    -        *,
    -        req: BoltRequest,
    -        resp: BoltResponse,
    -        next: Callable[[], BoltResponse],
    -    ) -> BoltResponse:
    -        auth_result = req.context.authorize_result
    -        # message events can have $.event.bot_id while it does not have its user_id
    -        bot_id = req.body.get("event", {}).get("bot_id")
    -        if self._is_self_event(auth_result, req.context.user_id, bot_id, req.body):
    -            self._debug_log(req.body)
    -            return req.context.ack()
    -        else:
    -            return next()
    -
    -    # -----------------------------------------
    -
    -    # It's an Events API event that isn't of type message,
    -    # but the user ID might match our own app. Filter these out.
    -    # However, some events still must be fired, because they can make sense.
    -    events_that_should_be_kept = ["member_joined_channel", "member_left_channel"]
    -
    -    @classmethod
    -    def _is_self_event(
    -        cls,
    -        auth_result: AuthorizeResult,
    -        user_id: Optional[str],
    -        bot_id: Optional[str],
    -        body: Dict[str, Any],
    -    ):
    -        return (
    -            auth_result is not None
    -            and (
    -                (user_id is not None and user_id == auth_result.bot_user_id)
    -                or (bot_id is not None and bot_id == auth_result.bot_id)  # for bot_message events
    -            )
    -            and body.get("event") is not None
    -            and body.get("event", {}).get("type") not in cls.events_that_should_be_kept
    -        )
    -
    -    def _debug_log(self, body: dict):
    -        if self.logger.level <= logging.DEBUG:
    -            event = body.get("event")
    -            self.logger.debug(f"Skipped self event: {event}")
    -
    -

    Ancestors

    - -

    Subclasses

    - -

    Class variables

    -
    -
    var events_that_should_be_kept
    -
    -
    -
    -
    -

    Inherited members

    - -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/middleware/message_listener_matches/async_message_listener_matches.html b/docs/api-docs/slack_bolt/middleware/message_listener_matches/async_message_listener_matches.html deleted file mode 100644 index aa9a5cd93..000000000 --- a/docs/api-docs/slack_bolt/middleware/message_listener_matches/async_message_listener_matches.html +++ /dev/null @@ -1,158 +0,0 @@ - - - - - - -slack_bolt.middleware.message_listener_matches.async_message_listener_matches API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.middleware.message_listener_matches.async_message_listener_matches

    -
    -
    -
    - -Expand source code - -
    import re
    -from typing import Callable, Awaitable, Union, Pattern
    -
    -from slack_bolt.request.async_request import AsyncBoltRequest
    -from slack_bolt.response import BoltResponse
    -from slack_bolt.middleware.async_middleware import AsyncMiddleware
    -
    -
    -class AsyncMessageListenerMatches(AsyncMiddleware):
    -    def __init__(self, keyword: Union[str, Pattern]):
    -        """Captures matched keywords and saves the values in context."""
    -        self.keyword = keyword
    -
    -    async def async_process(
    -        self,
    -        *,
    -        req: AsyncBoltRequest,
    -        resp: BoltResponse,
    -        # 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
    -        next: Callable[[], Awaitable[BoltResponse]],
    -    ) -> BoltResponse:
    -        text = req.body.get("event", {}).get("text", "")
    -        if text:
    -            m = re.findall(self.keyword, text)
    -            if m is not None and m != []:
    -                if type(m[0]) is not tuple:
    -                    m = tuple(m)
    -                else:
    -                    m = m[0]
    -                req.context["matches"] = m  # tuple or list
    -                return await next()
    -
    -        # As the text doesn't match, skip running the listener
    -        return resp
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class AsyncMessageListenerMatches -(keyword:ย Union[str,ย Pattern]) -
    -
    -

    A middleware can process request data before other middleware and listener functions.

    -

    Captures matched keywords and saves the values in context.

    -
    - -Expand source code - -
    class AsyncMessageListenerMatches(AsyncMiddleware):
    -    def __init__(self, keyword: Union[str, Pattern]):
    -        """Captures matched keywords and saves the values in context."""
    -        self.keyword = keyword
    -
    -    async def async_process(
    -        self,
    -        *,
    -        req: AsyncBoltRequest,
    -        resp: BoltResponse,
    -        # 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
    -        next: Callable[[], Awaitable[BoltResponse]],
    -    ) -> BoltResponse:
    -        text = req.body.get("event", {}).get("text", "")
    -        if text:
    -            m = re.findall(self.keyword, text)
    -            if m is not None and m != []:
    -                if type(m[0]) is not tuple:
    -                    m = tuple(m)
    -                else:
    -                    m = m[0]
    -                req.context["matches"] = m  # tuple or list
    -                return await next()
    -
    -        # As the text doesn't match, skip running the listener
    -        return resp
    -
    -

    Ancestors

    - -

    Inherited members

    - -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/middleware/message_listener_matches/message_listener_matches.html b/docs/api-docs/slack_bolt/middleware/message_listener_matches/message_listener_matches.html deleted file mode 100644 index 9f294802d..000000000 --- a/docs/api-docs/slack_bolt/middleware/message_listener_matches/message_listener_matches.html +++ /dev/null @@ -1,158 +0,0 @@ - - - - - - -slack_bolt.middleware.message_listener_matches.message_listener_matches API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.middleware.message_listener_matches.message_listener_matches

    -
    -
    -
    - -Expand source code - -
    import re
    -from typing import Callable, Pattern, Union
    -
    -from slack_bolt.request import BoltRequest
    -from slack_bolt.response import BoltResponse
    -from slack_bolt.middleware.middleware import Middleware
    -
    -
    -class MessageListenerMatches(Middleware):  # type: ignore
    -    def __init__(self, keyword: Union[str, Pattern]):
    -        """Captures matched keywords and saves the values in context."""
    -        self.keyword = keyword
    -
    -    def process(
    -        self,
    -        *,
    -        req: BoltRequest,
    -        resp: BoltResponse,
    -        # 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
    -        next: Callable[[], BoltResponse],
    -    ) -> BoltResponse:
    -        text = req.body.get("event", {}).get("text", "")
    -        if text:
    -            m = re.findall(self.keyword, text)
    -            if m is not None and m != []:
    -                if type(m[0]) is not tuple:
    -                    m = tuple(m)
    -                else:
    -                    m = m[0]
    -                req.context["matches"] = m  # tuple or list
    -                return next()
    -
    -        # As the text doesn't match, skip running the listener
    -        return resp
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class MessageListenerMatches -(keyword:ย Union[str,ย Pattern]) -
    -
    -

    A middleware can process request data before other middleware and listener functions.

    -

    Captures matched keywords and saves the values in context.

    -
    - -Expand source code - -
    class MessageListenerMatches(Middleware):  # type: ignore
    -    def __init__(self, keyword: Union[str, Pattern]):
    -        """Captures matched keywords and saves the values in context."""
    -        self.keyword = keyword
    -
    -    def process(
    -        self,
    -        *,
    -        req: BoltRequest,
    -        resp: BoltResponse,
    -        # 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
    -        next: Callable[[], BoltResponse],
    -    ) -> BoltResponse:
    -        text = req.body.get("event", {}).get("text", "")
    -        if text:
    -            m = re.findall(self.keyword, text)
    -            if m is not None and m != []:
    -                if type(m[0]) is not tuple:
    -                    m = tuple(m)
    -                else:
    -                    m = m[0]
    -                req.context["matches"] = m  # tuple or list
    -                return next()
    -
    -        # As the text doesn't match, skip running the listener
    -        return resp
    -
    -

    Ancestors

    - -

    Inherited members

    - -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/middleware/request_verification/async_request_verification.html b/docs/api-docs/slack_bolt/middleware/request_verification/async_request_verification.html deleted file mode 100644 index 9d532ffff..000000000 --- a/docs/api-docs/slack_bolt/middleware/request_verification/async_request_verification.html +++ /dev/null @@ -1,176 +0,0 @@ - - - - - - -slack_bolt.middleware.request_verification.async_request_verification API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.middleware.request_verification.async_request_verification

    -
    -
    -
    - -Expand source code - -
    from typing import Callable, Awaitable
    -
    -from slack_bolt.middleware import RequestVerification
    -from slack_bolt.middleware.async_middleware import AsyncMiddleware
    -from slack_bolt.request.async_request import AsyncBoltRequest
    -from slack_bolt.response import BoltResponse
    -
    -
    -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.
    -    """
    -
    -    async def async_process(
    -        self,
    -        *,
    -        req: AsyncBoltRequest,
    -        resp: BoltResponse,
    -        # 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
    -        next: Callable[[], Awaitable[BoltResponse]],
    -    ) -> BoltResponse:
    -        if self._can_skip(req.mode, req.body):
    -            return await next()
    -
    -        body = req.raw_body
    -        timestamp = req.headers.get("x-slack-request-timestamp", ["0"])[0]
    -        signature = req.headers.get("x-slack-signature", [""])[0]
    -        if self.verifier.is_valid(body, timestamp, signature):
    -            return await next()
    -        else:
    -            self._debug_log_error(signature, timestamp, body)
    -            return self._build_error_response()
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class AsyncRequestVerification -(signing_secret:ย str, base_logger:ย Optional[logging.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.

    -

    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.

    -

    Args

    -
    -
    signing_secret
    -
    The signing secret
    -
    base_logger
    -
    The base logger
    -
    -
    - -Expand source code - -
    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.
    -    """
    -
    -    async def async_process(
    -        self,
    -        *,
    -        req: AsyncBoltRequest,
    -        resp: BoltResponse,
    -        # 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
    -        next: Callable[[], Awaitable[BoltResponse]],
    -    ) -> BoltResponse:
    -        if self._can_skip(req.mode, req.body):
    -            return await next()
    -
    -        body = req.raw_body
    -        timestamp = req.headers.get("x-slack-request-timestamp", ["0"])[0]
    -        signature = req.headers.get("x-slack-signature", [""])[0]
    -        if self.verifier.is_valid(body, timestamp, signature):
    -            return await next()
    -        else:
    -            self._debug_log_error(signature, timestamp, body)
    -            return self._build_error_response()
    -
    -

    Ancestors

    - -

    Inherited members

    - -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/middleware/request_verification/request_verification.html b/docs/api-docs/slack_bolt/middleware/request_verification/request_verification.html deleted file mode 100644 index df8d00520..000000000 --- a/docs/api-docs/slack_bolt/middleware/request_verification/request_verification.html +++ /dev/null @@ -1,218 +0,0 @@ - - - - - - -slack_bolt.middleware.request_verification.request_verification API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.middleware.request_verification.request_verification

    -
    -
    -
    - -Expand source code - -
    from logging import Logger
    -from typing import Callable, Dict, Any, Optional
    -
    -from slack_sdk.signature import SignatureVerifier
    -
    -from slack_bolt.logger import get_bolt_logger
    -from slack_bolt.middleware.middleware import Middleware
    -from slack_bolt.request import BoltRequest
    -from slack_bolt.response import BoltResponse
    -
    -
    -class RequestVerification(Middleware):  # type: ignore
    -    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.
    -
    -        Args:
    -            signing_secret: The signing secret
    -            base_logger: The base logger
    -        """
    -        self.verifier = SignatureVerifier(signing_secret=signing_secret)
    -        self.logger = get_bolt_logger(RequestVerification, base_logger=base_logger)
    -
    -    def process(
    -        self,
    -        *,
    -        req: BoltRequest,
    -        resp: BoltResponse,
    -        # 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
    -        next: Callable[[], BoltResponse],
    -    ) -> BoltResponse:
    -        if self._can_skip(req.mode, req.body):
    -            return next()
    -
    -        body = req.raw_body
    -        timestamp = req.headers.get("x-slack-request-timestamp", ["0"])[0]
    -        signature = req.headers.get("x-slack-signature", [""])[0]
    -        if self.verifier.is_valid(body, timestamp, signature):
    -            return next()
    -        else:
    -            self._debug_log_error(signature, timestamp, body)
    -            return self._build_error_response()
    -
    -    # -----------------------------------------
    -
    -    @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")
    -
    -    @staticmethod
    -    def _build_error_response() -> BoltResponse:
    -        return BoltResponse(status=401, body={"error": "invalid request"})
    -
    -    def _debug_log_error(self, signature, timestamp, body) -> None:
    -        self.logger.info(
    -            "Invalid request signature detected " f"(signature: {signature}, timestamp: {timestamp}, body: {body})"
    -        )
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class RequestVerification -(signing_secret:ย str, base_logger:ย Optional[logging.Logger]ย =ย None) -
    -
    -

    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.

    -

    Args

    -
    -
    signing_secret
    -
    The signing secret
    -
    base_logger
    -
    The base logger
    -
    -
    - -Expand source code - -
    class RequestVerification(Middleware):  # type: ignore
    -    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.
    -
    -        Args:
    -            signing_secret: The signing secret
    -            base_logger: The base logger
    -        """
    -        self.verifier = SignatureVerifier(signing_secret=signing_secret)
    -        self.logger = get_bolt_logger(RequestVerification, base_logger=base_logger)
    -
    -    def process(
    -        self,
    -        *,
    -        req: BoltRequest,
    -        resp: BoltResponse,
    -        # 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
    -        next: Callable[[], BoltResponse],
    -    ) -> BoltResponse:
    -        if self._can_skip(req.mode, req.body):
    -            return next()
    -
    -        body = req.raw_body
    -        timestamp = req.headers.get("x-slack-request-timestamp", ["0"])[0]
    -        signature = req.headers.get("x-slack-signature", [""])[0]
    -        if self.verifier.is_valid(body, timestamp, signature):
    -            return next()
    -        else:
    -            self._debug_log_error(signature, timestamp, body)
    -            return self._build_error_response()
    -
    -    # -----------------------------------------
    -
    -    @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")
    -
    -    @staticmethod
    -    def _build_error_response() -> BoltResponse:
    -        return BoltResponse(status=401, body={"error": "invalid request"})
    -
    -    def _debug_log_error(self, signature, timestamp, body) -> None:
    -        self.logger.info(
    -            "Invalid request signature detected " f"(signature: {signature}, timestamp: {timestamp}, body: {body})"
    -        )
    -
    -

    Ancestors

    - -

    Subclasses

    - -

    Inherited members

    - -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/middleware/ssl_check/async_ssl_check.html b/docs/api-docs/slack_bolt/middleware/ssl_check/async_ssl_check.html deleted file mode 100644 index 6624b8e10..000000000 --- a/docs/api-docs/slack_bolt/middleware/ssl_check/async_ssl_check.html +++ /dev/null @@ -1,167 +0,0 @@ - - - - - - -slack_bolt.middleware.ssl_check.async_ssl_check API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.middleware.ssl_check.async_ssl_check

    -
    -
    -
    - -Expand source code - -
    from typing import Callable, Awaitable
    -
    -from .ssl_check import SslCheck
    -from slack_bolt.middleware.async_middleware import AsyncMiddleware
    -from slack_bolt.request.async_request import AsyncBoltRequest
    -from slack_bolt.response import BoltResponse
    -
    -
    -class AsyncSslCheck(SslCheck, AsyncMiddleware):
    -    async def async_process(
    -        self,
    -        *,
    -        req: AsyncBoltRequest,
    -        resp: BoltResponse,
    -        # 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
    -        next: Callable[[], Awaitable[BoltResponse]],
    -    ) -> BoltResponse:
    -        if self._is_ssl_check_request(req.body):
    -            if self._verify_token_if_needed(req.body):
    -                return self._build_error_response()
    -            return self._build_success_response()
    -        else:
    -            return await next()
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class AsyncSslCheck -(verification_token:ย Optional[str]ย =ย None, base_logger:ย Optional[logging.Logger]ย =ย None) -
    -
    -

    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.

    -

    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)
    -
    base_logger
    -
    The base logger
    -
    -
    - -Expand source code - -
    class AsyncSslCheck(SslCheck, AsyncMiddleware):
    -    async def async_process(
    -        self,
    -        *,
    -        req: AsyncBoltRequest,
    -        resp: BoltResponse,
    -        # 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
    -        next: Callable[[], Awaitable[BoltResponse]],
    -    ) -> BoltResponse:
    -        if self._is_ssl_check_request(req.body):
    -            if self._verify_token_if_needed(req.body):
    -                return self._build_error_response()
    -            return self._build_success_response()
    -        else:
    -            return await next()
    -
    -

    Ancestors

    - -

    Class variables

    -
    -
    var logger :ย logging.Logger
    -
    -
    -
    -
    var verification_token :ย Optional[str]
    -
    -
    -
    -
    -

    Inherited members

    - -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/middleware/ssl_check/ssl_check.html b/docs/api-docs/slack_bolt/middleware/ssl_check/ssl_check.html deleted file mode 100644 index 550b05fd2..000000000 --- a/docs/api-docs/slack_bolt/middleware/ssl_check/ssl_check.html +++ /dev/null @@ -1,237 +0,0 @@ - - - - - - -slack_bolt.middleware.ssl_check.ssl_check API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.middleware.ssl_check.ssl_check

    -
    -
    -
    - -Expand source code - -
    from logging import Logger
    -from typing import Callable, Optional
    -
    -from slack_bolt.logger import get_bolt_logger
    -from slack_bolt.middleware.middleware import Middleware
    -from slack_bolt.request import BoltRequest
    -from slack_bolt.response import BoltResponse
    -
    -
    -class SslCheck(Middleware):  # type: ignore
    -    verification_token: Optional[str]
    -    logger: Logger
    -
    -    def __init__(
    -        self,
    -        verification_token: Optional[str] = None,
    -        base_logger: Optional[Logger] = None,
    -    ):
    -        """Handles `ssl_check` requests.
    -        Refer to https://api.slack.com/interactivity/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)
    -            base_logger: The base logger
    -        """  # noqa: E501
    -        self.verification_token = verification_token
    -        self.logger = get_bolt_logger(SslCheck, base_logger=base_logger)
    -
    -    def process(
    -        self,
    -        *,
    -        req: BoltRequest,
    -        resp: BoltResponse,
    -        # 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
    -        next: Callable[[], BoltResponse],
    -    ) -> BoltResponse:
    -        if self._is_ssl_check_request(req.body):
    -            if self._verify_token_if_needed(req.body):
    -                return self._build_error_response()
    -            return self._build_success_response()
    -        else:
    -            return next()
    -
    -    # -----------------------------------------
    -
    -    @staticmethod
    -    def _is_ssl_check_request(body: dict):
    -        return "ssl_check" in body and body["ssl_check"] == "1"
    -
    -    def _verify_token_if_needed(self, body: dict):
    -        return self.verification_token and self.verification_token == body["token"]
    -
    -    @staticmethod
    -    def _build_success_response() -> BoltResponse:
    -        return BoltResponse(status=200, body="")
    -
    -    @staticmethod
    -    def _build_error_response() -> BoltResponse:
    -        return BoltResponse(status=401, body={"error": "invalid verification token"})
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class SslCheck -(verification_token:ย Optional[str]ย =ย None, base_logger:ย Optional[logging.Logger]ย =ย None) -
    -
    -

    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.

    -

    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)
    -
    base_logger
    -
    The base logger
    -
    -
    - -Expand source code - -
    class SslCheck(Middleware):  # type: ignore
    -    verification_token: Optional[str]
    -    logger: Logger
    -
    -    def __init__(
    -        self,
    -        verification_token: Optional[str] = None,
    -        base_logger: Optional[Logger] = None,
    -    ):
    -        """Handles `ssl_check` requests.
    -        Refer to https://api.slack.com/interactivity/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)
    -            base_logger: The base logger
    -        """  # noqa: E501
    -        self.verification_token = verification_token
    -        self.logger = get_bolt_logger(SslCheck, base_logger=base_logger)
    -
    -    def process(
    -        self,
    -        *,
    -        req: BoltRequest,
    -        resp: BoltResponse,
    -        # 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
    -        next: Callable[[], BoltResponse],
    -    ) -> BoltResponse:
    -        if self._is_ssl_check_request(req.body):
    -            if self._verify_token_if_needed(req.body):
    -                return self._build_error_response()
    -            return self._build_success_response()
    -        else:
    -            return next()
    -
    -    # -----------------------------------------
    -
    -    @staticmethod
    -    def _is_ssl_check_request(body: dict):
    -        return "ssl_check" in body and body["ssl_check"] == "1"
    -
    -    def _verify_token_if_needed(self, body: dict):
    -        return self.verification_token and self.verification_token == body["token"]
    -
    -    @staticmethod
    -    def _build_success_response() -> BoltResponse:
    -        return BoltResponse(status=200, body="")
    -
    -    @staticmethod
    -    def _build_error_response() -> BoltResponse:
    -        return BoltResponse(status=401, body={"error": "invalid verification token"})
    -
    -

    Ancestors

    - -

    Subclasses

    - -

    Class variables

    -
    -
    var logger :ย logging.Logger
    -
    -
    -
    -
    var verification_token :ย Optional[str]
    -
    -
    -
    -
    -

    Inherited members

    - -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/middleware/url_verification/async_url_verification.html b/docs/api-docs/slack_bolt/middleware/url_verification/async_url_verification.html deleted file mode 100644 index 98c4cc403..000000000 --- a/docs/api-docs/slack_bolt/middleware/url_verification/async_url_verification.html +++ /dev/null @@ -1,147 +0,0 @@ - - - - - - -slack_bolt.middleware.url_verification.async_url_verification API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.middleware.url_verification.async_url_verification

    -
    -
    -
    - -Expand source code - -
    from logging import Logger
    -from typing import Callable, Awaitable, Optional
    -
    -from slack_bolt.logger import get_bolt_logger
    -from .url_verification import UrlVerification
    -from slack_bolt.middleware.async_middleware import AsyncMiddleware
    -from slack_bolt.request.async_request import AsyncBoltRequest
    -from slack_bolt.response import BoltResponse
    -
    -
    -class AsyncUrlVerification(UrlVerification, AsyncMiddleware):
    -    def __init__(self, base_logger: Optional[Logger] = None):
    -        self.logger = get_bolt_logger(AsyncUrlVerification, base_logger=base_logger)
    -
    -    async def async_process(
    -        self,
    -        *,
    -        req: AsyncBoltRequest,
    -        resp: BoltResponse,
    -        next: Callable[[], Awaitable[BoltResponse]],
    -    ) -> BoltResponse:
    -        if self._is_url_verification_request(req.body):
    -            return self._build_success_response(req.body)
    -        else:
    -            return await next()
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class AsyncUrlVerification -(base_logger:ย Optional[logging.Logger]ย =ย None) -
    -
    -

    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.

    -

    Args

    -
    -
    base_logger
    -
    The base logger
    -
    -
    - -Expand source code - -
    class AsyncUrlVerification(UrlVerification, AsyncMiddleware):
    -    def __init__(self, base_logger: Optional[Logger] = None):
    -        self.logger = get_bolt_logger(AsyncUrlVerification, base_logger=base_logger)
    -
    -    async def async_process(
    -        self,
    -        *,
    -        req: AsyncBoltRequest,
    -        resp: BoltResponse,
    -        next: Callable[[], Awaitable[BoltResponse]],
    -    ) -> BoltResponse:
    -        if self._is_url_verification_request(req.body):
    -            return self._build_success_response(req.body)
    -        else:
    -            return await next()
    -
    -

    Ancestors

    - -

    Inherited members

    - -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/middleware/url_verification/url_verification.html b/docs/api-docs/slack_bolt/middleware/url_verification/url_verification.html deleted file mode 100644 index be99bb396..000000000 --- a/docs/api-docs/slack_bolt/middleware/url_verification/url_verification.html +++ /dev/null @@ -1,183 +0,0 @@ - - - - - - -slack_bolt.middleware.url_verification.url_verification API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.middleware.url_verification.url_verification

    -
    -
    -
    - -Expand source code - -
    from logging import Logger
    -from typing import Callable, Optional
    -
    -from slack_bolt.logger import get_bolt_logger
    -from slack_bolt.middleware.middleware import Middleware
    -from slack_bolt.request import BoltRequest
    -from slack_bolt.response import BoltResponse
    -
    -
    -class UrlVerification(Middleware):  # type: ignore
    -    def __init__(self, base_logger: Optional[Logger] = None):
    -        """Handles url_verification requests.
    -
    -        Refer to https://api.slack.com/events/url_verification for details.
    -
    -        Args:
    -            base_logger: The base logger
    -        """
    -        self.logger = get_bolt_logger(UrlVerification, base_logger=base_logger)
    -
    -    def process(
    -        self,
    -        *,
    -        req: BoltRequest,
    -        resp: BoltResponse,
    -        # 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
    -        next: Callable[[], BoltResponse],
    -    ) -> BoltResponse:
    -        if self._is_url_verification_request(req.body):
    -            return self._build_success_response(req.body)
    -        else:
    -            return next()
    -
    -    # -----------------------------------------
    -
    -    @staticmethod
    -    def _is_url_verification_request(body: dict) -> bool:
    -        return body is not None and body.get("type") == "url_verification"
    -
    -    @staticmethod
    -    def _build_success_response(body: dict) -> BoltResponse:
    -        return BoltResponse(status=200, body={"challenge": body.get("challenge")})
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class UrlVerification -(base_logger:ย Optional[logging.Logger]ย =ย None) -
    -
    -

    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.

    -

    Args

    -
    -
    base_logger
    -
    The base logger
    -
    -
    - -Expand source code - -
    class UrlVerification(Middleware):  # type: ignore
    -    def __init__(self, base_logger: Optional[Logger] = None):
    -        """Handles url_verification requests.
    -
    -        Refer to https://api.slack.com/events/url_verification for details.
    -
    -        Args:
    -            base_logger: The base logger
    -        """
    -        self.logger = get_bolt_logger(UrlVerification, base_logger=base_logger)
    -
    -    def process(
    -        self,
    -        *,
    -        req: BoltRequest,
    -        resp: BoltResponse,
    -        # 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
    -        next: Callable[[], BoltResponse],
    -    ) -> BoltResponse:
    -        if self._is_url_verification_request(req.body):
    -            return self._build_success_response(req.body)
    -        else:
    -            return next()
    -
    -    # -----------------------------------------
    -
    -    @staticmethod
    -    def _is_url_verification_request(body: dict) -> bool:
    -        return body is not None and body.get("type") == "url_verification"
    -
    -    @staticmethod
    -    def _build_success_response(body: dict) -> BoltResponse:
    -        return BoltResponse(status=200, body={"challenge": body.get("challenge")})
    -
    -

    Ancestors

    - -

    Subclasses

    - -

    Inherited members

    - -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/oauth/async_callback_options.html b/docs/api-docs/slack_bolt/oauth/async_callback_options.html deleted file mode 100644 index 7bf7a9274..000000000 --- a/docs/api-docs/slack_bolt/oauth/async_callback_options.html +++ /dev/null @@ -1,399 +0,0 @@ - - - - - - -slack_bolt.oauth.async_callback_options API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.oauth.async_callback_options

    -
    -
    -
    - -Expand source code - -
    import logging
    -from logging import Logger
    -from typing import Optional, Callable, Awaitable
    -
    -from slack_sdk.oauth import RedirectUriPageRenderer, OAuthStateUtils
    -from slack_sdk.oauth.installation_store import Installation
    -from slack_bolt.oauth.internals import CallbackResponseBuilder
    -
    -from slack_bolt.request.async_request import AsyncBoltRequest
    -from slack_bolt.response import BoltResponse
    -
    -
    -class AsyncSuccessArgs:
    -    def __init__(  # type: ignore
    -        self,
    -        *,
    -        request: AsyncBoltRequest,
    -        installation: Installation,
    -        settings: "AsyncOAuthSettings",
    -        default: "AsyncCallbackOptions",
    -    ):
    -        """The arguments for a success function.
    -
    -        Args:
    -            request: The request.
    -            installation: The installation data.
    -            settings: The settings for Slack OAuth flow.
    -            default: The default `AsyncCallbackOptions`.
    -        """
    -        self.request = request
    -        self.installation = installation
    -        self.settings = settings
    -        self.default = default
    -
    -
    -class AsyncFailureArgs:
    -    def __init__(  # type: ignore
    -        self,
    -        *,
    -        request: AsyncBoltRequest,
    -        reason: str,
    -        error: Optional[Exception] = None,
    -        suggested_status_code: int,
    -        settings: "AsyncOAuthSettings",
    -        default: "AsyncCallbackOptions",
    -    ):
    -        """The arguments for a failure function.
    -
    -        Args:
    -            request: The request.
    -            reason: The response.
    -            error: An exception if exists.
    -            suggested_status_code: The recommended HTTP status code for the failure.
    -            settings: The settings for Slack OAuth flow.
    -            default: The default `AsyncCallbackOptions`.
    -        """
    -        self.request = request
    -        self.reason = reason
    -        self.error = error
    -        self.suggested_status_code = suggested_status_code
    -        self.settings = settings
    -        self.default = default
    -
    -
    -class AsyncCallbackOptions:
    -    success: Callable[[AsyncSuccessArgs], Awaitable[BoltResponse]]
    -    failure: Callable[[AsyncFailureArgs], Awaitable[BoltResponse]]
    -
    -    def __init__(
    -        self,
    -        success: Callable[[AsyncSuccessArgs], Awaitable[BoltResponse]],
    -        failure: Callable[[AsyncFailureArgs], Awaitable[BoltResponse]],
    -    ):
    -        self.success = success
    -        self.failure = failure
    -
    -
    -class DefaultAsyncCallbackOptions(AsyncCallbackOptions):
    -    success: Callable[[AsyncSuccessArgs], Awaitable[BoltResponse]]
    -    failure: Callable[[AsyncFailureArgs], Awaitable[BoltResponse]]
    -
    -    def __init__(
    -        self,
    -        *,
    -        logger: Logger,
    -        state_utils: OAuthStateUtils,
    -        redirect_uri_page_renderer: RedirectUriPageRenderer,
    -    ):
    -        self._response_builder = CallbackResponseBuilder(
    -            logger=logger or logging.getLogger(__name__),
    -            state_utils=state_utils,
    -            redirect_uri_page_renderer=redirect_uri_page_renderer,
    -        )
    -        # Note that pytype 2021.4.26 misunderstands these assignments.
    -        # Thus, we put "type: ignore" for the following two lines
    -        self.success = self._success_handler  # type: ignore
    -        self.failure = self._failure_handler  # type: ignore
    -
    -    # --------------------------
    -    # Internal methods
    -    # --------------------------
    -
    -    async def _success_handler(self, args: AsyncSuccessArgs) -> BoltResponse:
    -        return self._response_builder._build_callback_success_response(
    -            request=args.request,
    -            installation=args.installation,
    -        )
    -
    -    async def _failure_handler(self, args: AsyncFailureArgs) -> BoltResponse:
    -        return self._response_builder._build_callback_failure_response(
    -            request=args.request,
    -            reason=args.reason,
    -            status=args.suggested_status_code,
    -        )
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class AsyncCallbackOptions -(success:ย Callable[[AsyncSuccessArgs],ย Awaitable[BoltResponse]], failure:ย Callable[[AsyncFailureArgs],ย Awaitable[BoltResponse]]) -
    -
    -
    -
    - -Expand source code - -
    class AsyncCallbackOptions:
    -    success: Callable[[AsyncSuccessArgs], Awaitable[BoltResponse]]
    -    failure: Callable[[AsyncFailureArgs], Awaitable[BoltResponse]]
    -
    -    def __init__(
    -        self,
    -        success: Callable[[AsyncSuccessArgs], Awaitable[BoltResponse]],
    -        failure: Callable[[AsyncFailureArgs], Awaitable[BoltResponse]],
    -    ):
    -        self.success = success
    -        self.failure = failure
    -
    -

    Subclasses

    - -

    Class variables

    -
    -
    var failure :ย Callable[[AsyncFailureArgs],ย Awaitable[BoltResponse]]
    -
    -
    -
    -
    var success :ย Callable[[AsyncSuccessArgs],ย Awaitable[BoltResponse]]
    -
    -
    -
    -
    -
    -
    -class AsyncFailureArgs -(*, request:ย AsyncBoltRequest, reason:ย str, error:ย Optional[Exception]ย =ย None, suggested_status_code:ย int, settings:ย AsyncOAuthSettings, default:ย AsyncCallbackOptions) -
    -
    -

    The arguments for a failure function.

    -

    Args

    -
    -
    request
    -
    The request.
    -
    reason
    -
    The response.
    -
    error
    -
    An exception if exists.
    -
    suggested_status_code
    -
    The recommended HTTP status code for the failure.
    -
    settings
    -
    The settings for Slack OAuth flow.
    -
    default
    -
    The default AsyncCallbackOptions.
    -
    -
    - -Expand source code - -
    class AsyncFailureArgs:
    -    def __init__(  # type: ignore
    -        self,
    -        *,
    -        request: AsyncBoltRequest,
    -        reason: str,
    -        error: Optional[Exception] = None,
    -        suggested_status_code: int,
    -        settings: "AsyncOAuthSettings",
    -        default: "AsyncCallbackOptions",
    -    ):
    -        """The arguments for a failure function.
    -
    -        Args:
    -            request: The request.
    -            reason: The response.
    -            error: An exception if exists.
    -            suggested_status_code: The recommended HTTP status code for the failure.
    -            settings: The settings for Slack OAuth flow.
    -            default: The default `AsyncCallbackOptions`.
    -        """
    -        self.request = request
    -        self.reason = reason
    -        self.error = error
    -        self.suggested_status_code = suggested_status_code
    -        self.settings = settings
    -        self.default = default
    -
    -
    -
    -class AsyncSuccessArgs -(*, request:ย AsyncBoltRequest, installation:ย slack_sdk.oauth.installation_store.models.installation.Installation, settings:ย AsyncOAuthSettings, default:ย AsyncCallbackOptions) -
    -
    -

    The arguments for a success function.

    -

    Args

    -
    -
    request
    -
    The request.
    -
    installation
    -
    The installation data.
    -
    settings
    -
    The settings for Slack OAuth flow.
    -
    default
    -
    The default AsyncCallbackOptions.
    -
    -
    - -Expand source code - -
    class AsyncSuccessArgs:
    -    def __init__(  # type: ignore
    -        self,
    -        *,
    -        request: AsyncBoltRequest,
    -        installation: Installation,
    -        settings: "AsyncOAuthSettings",
    -        default: "AsyncCallbackOptions",
    -    ):
    -        """The arguments for a success function.
    -
    -        Args:
    -            request: The request.
    -            installation: The installation data.
    -            settings: The settings for Slack OAuth flow.
    -            default: The default `AsyncCallbackOptions`.
    -        """
    -        self.request = request
    -        self.installation = installation
    -        self.settings = settings
    -        self.default = default
    -
    -
    -
    -class DefaultAsyncCallbackOptions -(*, logger:ย logging.Logger, state_utils:ย slack_sdk.oauth.state_utils.OAuthStateUtils, redirect_uri_page_renderer:ย slack_sdk.oauth.redirect_uri_page_renderer.RedirectUriPageRenderer) -
    -
    -
    -
    - -Expand source code - -
    class DefaultAsyncCallbackOptions(AsyncCallbackOptions):
    -    success: Callable[[AsyncSuccessArgs], Awaitable[BoltResponse]]
    -    failure: Callable[[AsyncFailureArgs], Awaitable[BoltResponse]]
    -
    -    def __init__(
    -        self,
    -        *,
    -        logger: Logger,
    -        state_utils: OAuthStateUtils,
    -        redirect_uri_page_renderer: RedirectUriPageRenderer,
    -    ):
    -        self._response_builder = CallbackResponseBuilder(
    -            logger=logger or logging.getLogger(__name__),
    -            state_utils=state_utils,
    -            redirect_uri_page_renderer=redirect_uri_page_renderer,
    -        )
    -        # Note that pytype 2021.4.26 misunderstands these assignments.
    -        # Thus, we put "type: ignore" for the following two lines
    -        self.success = self._success_handler  # type: ignore
    -        self.failure = self._failure_handler  # type: ignore
    -
    -    # --------------------------
    -    # Internal methods
    -    # --------------------------
    -
    -    async def _success_handler(self, args: AsyncSuccessArgs) -> BoltResponse:
    -        return self._response_builder._build_callback_success_response(
    -            request=args.request,
    -            installation=args.installation,
    -        )
    -
    -    async def _failure_handler(self, args: AsyncFailureArgs) -> BoltResponse:
    -        return self._response_builder._build_callback_failure_response(
    -            request=args.request,
    -            reason=args.reason,
    -            status=args.suggested_status_code,
    -        )
    -
    -

    Ancestors

    - -

    Class variables

    -
    -
    var failure :ย Callable[[AsyncFailureArgs],ย Awaitable[BoltResponse]]
    -
    -
    -
    -
    var success :ย Callable[[AsyncSuccessArgs],ย Awaitable[BoltResponse]]
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/oauth/async_internals.html b/docs/api-docs/slack_bolt/oauth/async_internals.html deleted file mode 100644 index f779ee254..000000000 --- a/docs/api-docs/slack_bolt/oauth/async_internals.html +++ /dev/null @@ -1,163 +0,0 @@ - - - - - - -slack_bolt.oauth.async_internals API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.oauth.async_internals

    -
    -
    -
    - -Expand source code - -
    from logging import Logger
    -from typing import Optional
    -
    -from slack_sdk.oauth.installation_store import FileInstallationStore
    -from slack_sdk.oauth.installation_store.async_installation_store import (
    -    AsyncInstallationStore,
    -)
    -
    -from ..logger.messages import warning_installation_store_conflicts
    -
    -# key: client_id, value: AsyncInstallationStore
    -default_installation_stores = {}
    -
    -
    -def get_or_create_default_installation_store(client_id: str) -> AsyncInstallationStore:
    -    store = default_installation_stores.get(client_id)
    -    if store is None:
    -        store = FileInstallationStore(client_id=client_id)
    -        default_installation_stores[client_id] = store
    -    return store
    -
    -
    -def select_consistent_installation_store(
    -    client_id: str,
    -    app_store: Optional[AsyncInstallationStore],
    -    oauth_flow_store: Optional[AsyncInstallationStore],
    -    logger: Logger,
    -) -> Optional[AsyncInstallationStore]:
    -    default = get_or_create_default_installation_store(client_id)
    -    if app_store is not None:
    -        if oauth_flow_store is not None:
    -            if oauth_flow_store is default:
    -                # only app_store is intentionally set in this case
    -                return app_store
    -
    -            # if both are intentionally set, prioritize app_store
    -            if oauth_flow_store is not app_store:
    -                logger.warning(warning_installation_store_conflicts())
    -            return oauth_flow_store
    -        else:
    -            # only app_store is available
    -            return app_store
    -    else:
    -        # only oauth_flow_store is available
    -        return oauth_flow_store
    -
    -
    -
    -
    -
    -
    -
    -

    Functions

    -
    -
    -def get_or_create_default_installation_store(client_id:ย str) โ€‘>ย slack_sdk.oauth.installation_store.async_installation_store.AsyncInstallationStore -
    -
    -
    -
    - -Expand source code - -
    def get_or_create_default_installation_store(client_id: str) -> AsyncInstallationStore:
    -    store = default_installation_stores.get(client_id)
    -    if store is None:
    -        store = FileInstallationStore(client_id=client_id)
    -        default_installation_stores[client_id] = store
    -    return store
    -
    -
    -
    -def select_consistent_installation_store(client_id:ย str, app_store:ย Optional[slack_sdk.oauth.installation_store.async_installation_store.AsyncInstallationStore], oauth_flow_store:ย Optional[slack_sdk.oauth.installation_store.async_installation_store.AsyncInstallationStore], logger:ย logging.Logger) โ€‘>ย Optional[slack_sdk.oauth.installation_store.async_installation_store.AsyncInstallationStore] -
    -
    -
    -
    - -Expand source code - -
    def select_consistent_installation_store(
    -    client_id: str,
    -    app_store: Optional[AsyncInstallationStore],
    -    oauth_flow_store: Optional[AsyncInstallationStore],
    -    logger: Logger,
    -) -> Optional[AsyncInstallationStore]:
    -    default = get_or_create_default_installation_store(client_id)
    -    if app_store is not None:
    -        if oauth_flow_store is not None:
    -            if oauth_flow_store is default:
    -                # only app_store is intentionally set in this case
    -                return app_store
    -
    -            # if both are intentionally set, prioritize app_store
    -            if oauth_flow_store is not app_store:
    -                logger.warning(warning_installation_store_conflicts())
    -            return oauth_flow_store
    -        else:
    -            # only app_store is available
    -            return app_store
    -    else:
    -        # only oauth_flow_store is available
    -        return oauth_flow_store
    -
    -
    -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/oauth/internals.html b/docs/api-docs/slack_bolt/oauth/internals.html deleted file mode 100644 index 5d0efb4b7..000000000 --- a/docs/api-docs/slack_bolt/oauth/internals.html +++ /dev/null @@ -1,368 +0,0 @@ - - - - - - -slack_bolt.oauth.internals API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.oauth.internals

    -
    -
    -
    - -Expand source code - -
    import html
    -from logging import Logger
    -from typing import Optional
    -from typing import Union
    -
    -from slack_sdk.oauth import InstallationStore
    -from slack_sdk.oauth import OAuthStateUtils, RedirectUriPageRenderer
    -from slack_sdk.oauth.installation_store import FileInstallationStore
    -from slack_sdk.oauth.installation_store import Installation
    -
    -from slack_bolt.request import BoltRequest
    -from slack_bolt.response import BoltResponse
    -from ..logger.messages import warning_installation_store_conflicts
    -
    -
    -class CallbackResponseBuilder:
    -    def __init__(
    -        self,
    -        *,
    -        logger: Logger,
    -        state_utils: OAuthStateUtils,
    -        redirect_uri_page_renderer: RedirectUriPageRenderer,
    -    ):
    -        self._logger = logger
    -        self._state_utils = state_utils
    -        self._redirect_uri_page_renderer = redirect_uri_page_renderer
    -
    -    def _build_callback_success_response(  # type: ignore
    -        self,
    -        request: Union[BoltRequest, "AsyncBoltRequest"],
    -        installation: Installation,
    -    ) -> BoltResponse:
    -        debug_message = f"Handling an OAuth callback success (request: {request.query})"
    -        self._logger.debug(debug_message)
    -
    -        page_content = self._redirect_uri_page_renderer.render_success_page(
    -            app_id=installation.app_id,
    -            team_id=installation.team_id,
    -            is_enterprise_install=installation.is_enterprise_install,
    -            enterprise_url=installation.enterprise_url,
    -        )
    -        return BoltResponse(
    -            status=200,
    -            headers={
    -                "Content-Type": "text/html; charset=utf-8",
    -                "Set-Cookie": self._state_utils.build_set_cookie_for_deletion(),
    -            },
    -            body=page_content,
    -        )
    -
    -    def _build_callback_failure_response(  # type: ignore
    -        self,
    -        request: Union[BoltRequest, "AsyncBoltRequest"],
    -        reason: str,
    -        status: int = 500,
    -        error: Optional[Exception] = None,
    -    ) -> BoltResponse:
    -        debug_message = "Handling an OAuth callback failure " f"(reason: {reason}, error: {error}, request: {request.query})"
    -        self._logger.debug(debug_message)
    -
    -        # Adding a bit more details to the error code to help installers understand what's happening.
    -        # This modification in the HTML page works only when developers use this built-in failure handler.
    -        detailed_error = build_detailed_error(reason)
    -        return BoltResponse(
    -            status=status,
    -            headers={
    -                "Content-Type": "text/html; charset=utf-8",
    -                "Set-Cookie": self._state_utils.build_set_cookie_for_deletion(),
    -            },
    -            body=self._redirect_uri_page_renderer.render_failure_page(detailed_error),
    -        )
    -
    -
    -def _build_default_install_page_html(url: str) -> str:
    -    return f"""<html>
    -<head>
    -<link rel="icon" href="data:,">
    -<style>
    -body {{
    -  padding: 10px 15px;
    -  font-family: verdana;
    -  text-align: center;
    -}}
    -</style>
    -</head>
    -<body>
    -<h2>Slack App Installation</h2>
    -<p><a href="{html.escape(url)}"><img alt=""Add to Slack"" height="40" width="139" src="https://platform.slack-edge.com/img/add_to_slack.png" srcset="https://platform.slack-edge.com/img/add_to_slack.png 1x, https://platform.slack-edge.com/img/add_to_slack@2x.png 2x" /></a></p>
    -</body>
    -</html>
    -"""  # noqa: E501
    -
    -
    -# key: client_id, value: InstallationStore
    -default_installation_stores = {}
    -
    -
    -def get_or_create_default_installation_store(client_id: str) -> InstallationStore:
    -    store = default_installation_stores.get(client_id)
    -    if store is None:
    -        store = FileInstallationStore(client_id=client_id)
    -        default_installation_stores[client_id] = store
    -    return store
    -
    -
    -def select_consistent_installation_store(
    -    client_id: str,
    -    app_store: Optional[InstallationStore],
    -    oauth_flow_store: Optional[InstallationStore],
    -    logger: Logger,
    -) -> Optional[InstallationStore]:
    -    default = get_or_create_default_installation_store(client_id)
    -    if app_store is not None:
    -        if oauth_flow_store is not None:
    -            if oauth_flow_store is default:
    -                # only app_store is intentionally set in this case
    -                return app_store
    -
    -            # if both are intentionally set, prioritize app_store
    -            if oauth_flow_store is not app_store:
    -                logger.warning(warning_installation_store_conflicts())
    -            return oauth_flow_store
    -        else:
    -            # only app_store is available
    -            return app_store
    -    else:
    -        # only oauth_flow_store is available
    -        return oauth_flow_store
    -
    -
    -def build_detailed_error(reason: str) -> str:
    -    if reason == "invalid_browser":
    -        return (
    -            f"{reason}: This can occur due to page reload, "
    -            "not beginning the OAuth flow from the valid starting URL, or "
    -            "the /slack/install URL not using https://"
    -        )
    -    elif reason == "invalid_state":
    -        return f"{reason}: The state parameter is no longer valid."
    -    elif reason == "missing_code":
    -        return f"{reason}: The code parameter is missing in this redirection."
    -    elif reason == "storage_error":
    -        return f"{reason}: The app's server encountered an issue. Contact the app developer."
    -    else:
    -        return f"{html.escape(reason)}: This error code is returned from Slack. Refer to the documents for details."
    -
    -
    -
    -
    -
    -
    -
    -

    Functions

    -
    -
    -def build_detailed_error(reason:ย str) โ€‘>ย str -
    -
    -
    -
    - -Expand source code - -
    def build_detailed_error(reason: str) -> str:
    -    if reason == "invalid_browser":
    -        return (
    -            f"{reason}: This can occur due to page reload, "
    -            "not beginning the OAuth flow from the valid starting URL, or "
    -            "the /slack/install URL not using https://"
    -        )
    -    elif reason == "invalid_state":
    -        return f"{reason}: The state parameter is no longer valid."
    -    elif reason == "missing_code":
    -        return f"{reason}: The code parameter is missing in this redirection."
    -    elif reason == "storage_error":
    -        return f"{reason}: The app's server encountered an issue. Contact the app developer."
    -    else:
    -        return f"{html.escape(reason)}: This error code is returned from Slack. Refer to the documents for details."
    -
    -
    -
    -def get_or_create_default_installation_store(client_id:ย str) โ€‘>ย slack_sdk.oauth.installation_store.installation_store.InstallationStore -
    -
    -
    -
    - -Expand source code - -
    def get_or_create_default_installation_store(client_id: str) -> InstallationStore:
    -    store = default_installation_stores.get(client_id)
    -    if store is None:
    -        store = FileInstallationStore(client_id=client_id)
    -        default_installation_stores[client_id] = store
    -    return store
    -
    -
    -
    -def select_consistent_installation_store(client_id:ย str, app_store:ย Optional[slack_sdk.oauth.installation_store.installation_store.InstallationStore], oauth_flow_store:ย Optional[slack_sdk.oauth.installation_store.installation_store.InstallationStore], logger:ย logging.Logger) โ€‘>ย Optional[slack_sdk.oauth.installation_store.installation_store.InstallationStore] -
    -
    -
    -
    - -Expand source code - -
    def select_consistent_installation_store(
    -    client_id: str,
    -    app_store: Optional[InstallationStore],
    -    oauth_flow_store: Optional[InstallationStore],
    -    logger: Logger,
    -) -> Optional[InstallationStore]:
    -    default = get_or_create_default_installation_store(client_id)
    -    if app_store is not None:
    -        if oauth_flow_store is not None:
    -            if oauth_flow_store is default:
    -                # only app_store is intentionally set in this case
    -                return app_store
    -
    -            # if both are intentionally set, prioritize app_store
    -            if oauth_flow_store is not app_store:
    -                logger.warning(warning_installation_store_conflicts())
    -            return oauth_flow_store
    -        else:
    -            # only app_store is available
    -            return app_store
    -    else:
    -        # only oauth_flow_store is available
    -        return oauth_flow_store
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class CallbackResponseBuilder -(*, logger:ย logging.Logger, state_utils:ย slack_sdk.oauth.state_utils.OAuthStateUtils, redirect_uri_page_renderer:ย slack_sdk.oauth.redirect_uri_page_renderer.RedirectUriPageRenderer) -
    -
    -
    -
    - -Expand source code - -
    class CallbackResponseBuilder:
    -    def __init__(
    -        self,
    -        *,
    -        logger: Logger,
    -        state_utils: OAuthStateUtils,
    -        redirect_uri_page_renderer: RedirectUriPageRenderer,
    -    ):
    -        self._logger = logger
    -        self._state_utils = state_utils
    -        self._redirect_uri_page_renderer = redirect_uri_page_renderer
    -
    -    def _build_callback_success_response(  # type: ignore
    -        self,
    -        request: Union[BoltRequest, "AsyncBoltRequest"],
    -        installation: Installation,
    -    ) -> BoltResponse:
    -        debug_message = f"Handling an OAuth callback success (request: {request.query})"
    -        self._logger.debug(debug_message)
    -
    -        page_content = self._redirect_uri_page_renderer.render_success_page(
    -            app_id=installation.app_id,
    -            team_id=installation.team_id,
    -            is_enterprise_install=installation.is_enterprise_install,
    -            enterprise_url=installation.enterprise_url,
    -        )
    -        return BoltResponse(
    -            status=200,
    -            headers={
    -                "Content-Type": "text/html; charset=utf-8",
    -                "Set-Cookie": self._state_utils.build_set_cookie_for_deletion(),
    -            },
    -            body=page_content,
    -        )
    -
    -    def _build_callback_failure_response(  # type: ignore
    -        self,
    -        request: Union[BoltRequest, "AsyncBoltRequest"],
    -        reason: str,
    -        status: int = 500,
    -        error: Optional[Exception] = None,
    -    ) -> BoltResponse:
    -        debug_message = "Handling an OAuth callback failure " f"(reason: {reason}, error: {error}, request: {request.query})"
    -        self._logger.debug(debug_message)
    -
    -        # Adding a bit more details to the error code to help installers understand what's happening.
    -        # This modification in the HTML page works only when developers use this built-in failure handler.
    -        detailed_error = build_detailed_error(reason)
    -        return BoltResponse(
    -            status=status,
    -            headers={
    -                "Content-Type": "text/html; charset=utf-8",
    -                "Set-Cookie": self._state_utils.build_set_cookie_for_deletion(),
    -            },
    -            body=self._redirect_uri_page_renderer.render_failure_page(detailed_error),
    -        )
    -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/request/async_internals.html b/docs/api-docs/slack_bolt/request/async_internals.html deleted file mode 100644 index b36d03677..000000000 --- a/docs/api-docs/slack_bolt/request/async_internals.html +++ /dev/null @@ -1,169 +0,0 @@ - - - - - - -slack_bolt.request.async_internals API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.request.async_internals

    -
    -
    -
    - -Expand source code - -
    from typing import Dict, Any
    -
    -from slack_bolt.context.async_context import AsyncBoltContext
    -from slack_bolt.request.internals import (
    -    extract_enterprise_id,
    -    extract_is_enterprise_install,
    -    extract_team_id,
    -    extract_user_id,
    -    extract_channel_id,
    -    debug_multiple_response_urls_detected,
    -    extract_actor_enterprise_id,
    -    extract_actor_team_id,
    -    extract_actor_user_id,
    -)
    -
    -
    -def build_async_context(
    -    context: AsyncBoltContext,
    -    body: Dict[str, Any],
    -) -> AsyncBoltContext:
    -    context["is_enterprise_install"] = extract_is_enterprise_install(body)
    -    enterprise_id = extract_enterprise_id(body)
    -    if enterprise_id:
    -        context["enterprise_id"] = enterprise_id
    -    team_id = extract_team_id(body)
    -    if team_id:
    -        context["team_id"] = team_id
    -    user_id = extract_user_id(body)
    -    if user_id:
    -        context["user_id"] = user_id
    -    # Actor IDs are useful for Events API on a Slack Connect channel
    -    actor_enterprise_id = extract_actor_enterprise_id(body)
    -    if actor_enterprise_id:
    -        context["actor_enterprise_id"] = actor_enterprise_id
    -    actor_team_id = extract_actor_team_id(body)
    -    if actor_team_id:
    -        context["actor_team_id"] = actor_team_id
    -    actor_user_id = extract_actor_user_id(body)
    -    if actor_user_id:
    -        context["actor_user_id"] = actor_user_id
    -    channel_id = extract_channel_id(body)
    -    if channel_id:
    -        context["channel_id"] = channel_id
    -    if "response_url" in body:
    -        context["response_url"] = body["response_url"]
    -    elif "response_urls" in body:
    -        # In the case where response_url_enabled: true in a modal exists
    -        response_urls = body["response_urls"]
    -        if len(response_urls) >= 1:
    -            if len(response_urls) > 1:
    -                context.logger.debug(debug_multiple_response_urls_detected())
    -            response_url = response_urls[0].get("response_url")
    -            context["response_url"] = response_url
    -    return context
    -
    -
    -
    -
    -
    -
    -
    -

    Functions

    -
    -
    -def build_async_context(context:ย AsyncBoltContext, body:ย Dict[str,ย Any]) โ€‘>ย AsyncBoltContext -
    -
    -
    -
    - -Expand source code - -
    def build_async_context(
    -    context: AsyncBoltContext,
    -    body: Dict[str, Any],
    -) -> AsyncBoltContext:
    -    context["is_enterprise_install"] = extract_is_enterprise_install(body)
    -    enterprise_id = extract_enterprise_id(body)
    -    if enterprise_id:
    -        context["enterprise_id"] = enterprise_id
    -    team_id = extract_team_id(body)
    -    if team_id:
    -        context["team_id"] = team_id
    -    user_id = extract_user_id(body)
    -    if user_id:
    -        context["user_id"] = user_id
    -    # Actor IDs are useful for Events API on a Slack Connect channel
    -    actor_enterprise_id = extract_actor_enterprise_id(body)
    -    if actor_enterprise_id:
    -        context["actor_enterprise_id"] = actor_enterprise_id
    -    actor_team_id = extract_actor_team_id(body)
    -    if actor_team_id:
    -        context["actor_team_id"] = actor_team_id
    -    actor_user_id = extract_actor_user_id(body)
    -    if actor_user_id:
    -        context["actor_user_id"] = actor_user_id
    -    channel_id = extract_channel_id(body)
    -    if channel_id:
    -        context["channel_id"] = channel_id
    -    if "response_url" in body:
    -        context["response_url"] = body["response_url"]
    -    elif "response_urls" in body:
    -        # In the case where response_url_enabled: true in a modal exists
    -        response_urls = body["response_urls"]
    -        if len(response_urls) >= 1:
    -            if len(response_urls) > 1:
    -                context.logger.debug(debug_multiple_response_urls_detected())
    -            response_url = response_urls[0].get("response_url")
    -            context["response_url"] = response_url
    -    return context
    -
    -
    -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/util/async_utils.html b/docs/api-docs/slack_bolt/util/async_utils.html deleted file mode 100644 index 77c0296b3..000000000 --- a/docs/api-docs/slack_bolt/util/async_utils.html +++ /dev/null @@ -1,97 +0,0 @@ - - - - - - -slack_bolt.util.async_utils API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.util.async_utils

    -
    -
    -
    - -Expand source code - -
    from logging import Logger
    -from typing import Optional
    -
    -from slack_sdk.web.async_client import AsyncWebClient
    -
    -from slack_bolt.version import __version__ as bolt_version
    -
    -
    -def create_async_web_client(token: Optional[str] = None, logger: Optional[Logger] = None) -> AsyncWebClient:
    -    return AsyncWebClient(
    -        token=token,
    -        logger=logger,
    -        user_agent_prefix=f"Bolt-Async/{bolt_version}",
    -    )
    -
    -
    -
    -
    -
    -
    -
    -

    Functions

    -
    -
    -def create_async_web_client(token:ย Optional[str]ย =ย None, logger:ย Optional[logging.Logger]ย =ย None) โ€‘>ย slack_sdk.web.async_client.AsyncWebClient -
    -
    -
    -
    - -Expand source code - -
    def create_async_web_client(token: Optional[str] = None, logger: Optional[Logger] = None) -> AsyncWebClient:
    -    return AsyncWebClient(
    -        token=token,
    -        logger=logger,
    -        user_agent_prefix=f"Bolt-Async/{bolt_version}",
    -    )
    -
    -
    -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/util/index.html b/docs/api-docs/slack_bolt/util/index.html deleted file mode 100644 index ec43aa5a6..000000000 --- a/docs/api-docs/slack_bolt/util/index.html +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - -slack_bolt.util API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.util

    -
    -
    -

    Internal utilities for the Bolt framework.

    -
    - -Expand source code - -
    """Internal utilities for the Bolt framework."""
    -
    -
    -
    -

    Sub-modules

    -
    -
    slack_bolt.util.async_utils
    -
    -
    -
    -
    slack_bolt.util.utils
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/util/utils.html b/docs/api-docs/slack_bolt/util/utils.html deleted file mode 100644 index 8ee747aff..000000000 --- a/docs/api-docs/slack_bolt/util/utils.html +++ /dev/null @@ -1,311 +0,0 @@ - - - - - - -slack_bolt.util.utils API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.util.utils

    -
    -
    -
    - -Expand source code - -
    import copy
    -import inspect
    -import sys
    -from logging import Logger
    -from typing import Optional, Union, Dict, Any, Sequence, Callable, List
    -
    -from slack_sdk import WebClient
    -from slack_sdk.models import JsonObject
    -
    -from slack_bolt.error import BoltError
    -from slack_bolt.version import __version__ as bolt_version
    -
    -
    -def create_web_client(token: Optional[str] = None, logger: Optional[Logger] = None) -> WebClient:
    -    return WebClient(
    -        token=token,
    -        logger=logger,
    -        user_agent_prefix=f"Bolt/{bolt_version}",
    -    )
    -
    -
    -def convert_to_dict_list(objects: Sequence[Union[Dict, JsonObject]]) -> Sequence[Dict]:
    -    return [convert_to_dict(elm) for elm in objects]
    -
    -
    -def convert_to_dict(obj: Union[Dict, JsonObject]) -> Dict:
    -    if isinstance(obj, dict):
    -        return obj
    -    if isinstance(obj, JsonObject) or hasattr(obj, "to_dict"):
    -        return obj.to_dict()
    -    raise BoltError(f"{obj} (type: {type(obj)}) is unsupported")
    -
    -
    -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)
    -
    -
    -def get_boot_message(development_server: bool = False) -> str:
    -    if sys.platform == "win32":
    -        # Some Windows environments may fail to parse this str value
    -        # and result in UnicodeEncodeError
    -        if development_server:
    -            return "Bolt app is running! (development server)"
    -        else:
    -            return "Bolt app is running!"
    -
    -    try:
    -        if development_server:
    -            return "โšก๏ธ Bolt app is running! (development server)"
    -        else:
    -            return "โšก๏ธ Bolt app is running!"
    -    except ValueError:
    -        # ValueError is a runtime exception for a given value
    -        # It's a super class of UnicodeEncodeError, which may be raised in the scenario
    -        # see also: https://github.com/slackapi/bolt-python/issues/170
    -        if development_server:
    -            return "Bolt app is running! (development server)"
    -        else:
    -            return "Bolt app is running!"
    -
    -
    -def get_name_for_callable(func: Callable) -> str:
    -    """Returns the name for the given Callable function object.
    -
    -    Args:
    -        func: Either a `Callable` instance or a function, which as `__name__`
    -
    -    Returns:
    -        The name of the given Callable object
    -    """
    -    if hasattr(func, "__name__"):
    -        return func.__name__
    -    else:
    -        return f"{func.__class__.__module__}.{func.__class__.__name__}"
    -
    -
    -def get_arg_names_of_callable(func: Callable) -> List[str]:
    -    return inspect.getfullargspec(inspect.unwrap(func)).args
    -
    -
    -
    -
    -
    -
    -
    -

    Functions

    -
    -
    -def convert_to_dict(obj:ย Union[Dict,ย slack_sdk.models.basic_objects.JsonObject]) โ€‘>ย Dict -
    -
    -
    -
    - -Expand source code - -
    def convert_to_dict(obj: Union[Dict, JsonObject]) -> Dict:
    -    if isinstance(obj, dict):
    -        return obj
    -    if isinstance(obj, JsonObject) or hasattr(obj, "to_dict"):
    -        return obj.to_dict()
    -    raise BoltError(f"{obj} (type: {type(obj)}) is unsupported")
    -
    -
    -
    -def convert_to_dict_list(objects:ย Sequence[Union[Dict,ย slack_sdk.models.basic_objects.JsonObject]]) โ€‘>ย Sequence[Dict] -
    -
    -
    -
    - -Expand source code - -
    def convert_to_dict_list(objects: Sequence[Union[Dict, JsonObject]]) -> Sequence[Dict]:
    -    return [convert_to_dict(elm) for elm in objects]
    -
    -
    -
    -def create_copy(original:ย Any) โ€‘>ย Any -
    -
    -
    -
    - -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)
    -
    -
    -
    -def create_web_client(token:ย Optional[str]ย =ย None, logger:ย Optional[logging.Logger]ย =ย None) โ€‘>ย slack_sdk.web.client.WebClient -
    -
    -
    -
    - -Expand source code - -
    def create_web_client(token: Optional[str] = None, logger: Optional[Logger] = None) -> WebClient:
    -    return WebClient(
    -        token=token,
    -        logger=logger,
    -        user_agent_prefix=f"Bolt/{bolt_version}",
    -    )
    -
    -
    -
    -def get_arg_names_of_callable(func:ย Callable) โ€‘>ย List[str] -
    -
    -
    -
    - -Expand source code - -
    def get_arg_names_of_callable(func: Callable) -> List[str]:
    -    return inspect.getfullargspec(inspect.unwrap(func)).args
    -
    -
    -
    -def get_boot_message(development_server:ย boolย =ย False) โ€‘>ย str -
    -
    -
    -
    - -Expand source code - -
    def get_boot_message(development_server: bool = False) -> str:
    -    if sys.platform == "win32":
    -        # Some Windows environments may fail to parse this str value
    -        # and result in UnicodeEncodeError
    -        if development_server:
    -            return "Bolt app is running! (development server)"
    -        else:
    -            return "Bolt app is running!"
    -
    -    try:
    -        if development_server:
    -            return "โšก๏ธ Bolt app is running! (development server)"
    -        else:
    -            return "โšก๏ธ Bolt app is running!"
    -    except ValueError:
    -        # ValueError is a runtime exception for a given value
    -        # It's a super class of UnicodeEncodeError, which may be raised in the scenario
    -        # see also: https://github.com/slackapi/bolt-python/issues/170
    -        if development_server:
    -            return "Bolt app is running! (development server)"
    -        else:
    -            return "Bolt app is running!"
    -
    -
    -
    -def get_name_for_callable(func:ย Callable) โ€‘>ย str -
    -
    -

    Returns the name for the given Callable function object.

    -

    Args

    -
    -
    func
    -
    Either a Callable instance or a function, which as __name__
    -
    -

    Returns

    -

    The name of the given Callable object

    -
    - -Expand source code - -
    def get_name_for_callable(func: Callable) -> str:
    -    """Returns the name for the given Callable function object.
    -
    -    Args:
    -        func: Either a `Callable` instance or a function, which as `__name__`
    -
    -    Returns:
    -        The name of the given Callable object
    -    """
    -    if hasattr(func, "__name__"):
    -        return func.__name__
    -    else:
    -        return f"{func.__class__.__module__}.{func.__class__.__name__}"
    -
    -
    -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/version.html b/docs/api-docs/slack_bolt/version.html deleted file mode 100644 index eccade944..000000000 --- a/docs/api-docs/slack_bolt/version.html +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - -slack_bolt.version API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.version

    -
    -
    -

    Check the latest version at https://pypi.org/project/slack-bolt/

    -
    - -Expand source code - -
    """Check the latest version at https://pypi.org/project/slack-bolt/"""
    -__version__ = "1.18.0"
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/workflows/index.html b/docs/api-docs/slack_bolt/workflows/index.html deleted file mode 100644 index ac7f67988..000000000 --- a/docs/api-docs/slack_bolt/workflows/index.html +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - -slack_bolt.workflows API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.workflows

    -
    -
    -

    Workflow Steps from Apps enables developers to build their own custom workflow steps.

    -

    Check the following API documents first:

    - -

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

    -
    - -Expand source code - -
    """Workflow Steps from Apps enables developers to build their own custom workflow steps.
    -
    -Check the following API documents first:
    -
    -* `slack_bolt.workflows.step.step`
    -* `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.
    -"""
    -
    -
    -
    -

    Sub-modules

    -
    -
    slack_bolt.workflows.step
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/workflows/step/async_step_middleware.html b/docs/api-docs/slack_bolt/workflows/step/async_step_middleware.html deleted file mode 100644 index fb96639f2..000000000 --- a/docs/api-docs/slack_bolt/workflows/step/async_step_middleware.html +++ /dev/null @@ -1,196 +0,0 @@ - - - - - - -slack_bolt.workflows.step.async_step_middleware API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.workflows.step.async_step_middleware

    -
    -
    -
    - -Expand source code - -
    from typing import Callable, Optional, Awaitable
    -
    -from slack_bolt.listener.async_listener import AsyncListener
    -from slack_bolt.listener.asyncio_runner import AsyncioListenerRunner  # type: ignore
    -from slack_bolt.middleware.async_middleware import AsyncMiddleware
    -from slack_bolt.request.async_request import AsyncBoltRequest
    -from slack_bolt.response import BoltResponse
    -from slack_bolt.util.utils import get_name_for_callable
    -from slack_bolt.workflows.step.async_step import AsyncWorkflowStep
    -
    -
    -class AsyncWorkflowStepMiddleware(AsyncMiddleware):  # type:ignore
    -    """Base middleware for workflow step specific ones"""
    -
    -    def __init__(self, step: AsyncWorkflowStep, listener_runner: AsyncioListenerRunner):
    -        self.step = step
    -        self.listener_runner = listener_runner
    -
    -    async def async_process(
    -        self,
    -        *,
    -        req: AsyncBoltRequest,
    -        resp: BoltResponse,
    -        next: Callable[[], Awaitable[BoltResponse]],
    -    ) -> BoltResponse:
    -
    -        if await self.step.edit.async_matches(req=req, resp=resp):
    -            resp = await self._run(self.step.edit, req, resp)
    -            if resp is not None:
    -                return resp
    -        elif await self.step.save.async_matches(req=req, resp=resp):
    -            resp = await self._run(self.step.save, req, resp)
    -            if resp is not None:
    -                return resp
    -        elif await self.step.execute.async_matches(req=req, resp=resp):
    -            resp = await self._run(self.step.execute, req, resp)
    -            if resp is not None:
    -                return resp
    -
    -        return await next()
    -
    -    async def _run(
    -        self,
    -        listener: AsyncListener,
    -        req: AsyncBoltRequest,
    -        resp: BoltResponse,
    -    ) -> Optional[BoltResponse]:
    -        resp, next_was_not_called = await listener.run_async_middleware(req=req, resp=resp)
    -        if next_was_not_called:
    -            return None
    -
    -        return await self.listener_runner.run(
    -            request=req,
    -            response=resp,
    -            listener_name=get_name_for_callable(listener.ack_function),
    -            listener=listener,
    -        )
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class AsyncWorkflowStepMiddleware -(step:ย AsyncWorkflowStep, listener_runner:ย AsyncioListenerRunner) -
    -
    -

    Base middleware for workflow step specific ones

    -
    - -Expand source code - -
    class AsyncWorkflowStepMiddleware(AsyncMiddleware):  # type:ignore
    -    """Base middleware for workflow step specific ones"""
    -
    -    def __init__(self, step: AsyncWorkflowStep, listener_runner: AsyncioListenerRunner):
    -        self.step = step
    -        self.listener_runner = listener_runner
    -
    -    async def async_process(
    -        self,
    -        *,
    -        req: AsyncBoltRequest,
    -        resp: BoltResponse,
    -        next: Callable[[], Awaitable[BoltResponse]],
    -    ) -> BoltResponse:
    -
    -        if await self.step.edit.async_matches(req=req, resp=resp):
    -            resp = await self._run(self.step.edit, req, resp)
    -            if resp is not None:
    -                return resp
    -        elif await self.step.save.async_matches(req=req, resp=resp):
    -            resp = await self._run(self.step.save, req, resp)
    -            if resp is not None:
    -                return resp
    -        elif await self.step.execute.async_matches(req=req, resp=resp):
    -            resp = await self._run(self.step.execute, req, resp)
    -            if resp is not None:
    -                return resp
    -
    -        return await next()
    -
    -    async def _run(
    -        self,
    -        listener: AsyncListener,
    -        req: AsyncBoltRequest,
    -        resp: BoltResponse,
    -    ) -> Optional[BoltResponse]:
    -        resp, next_was_not_called = await listener.run_async_middleware(req=req, resp=resp)
    -        if next_was_not_called:
    -            return None
    -
    -        return await self.listener_runner.run(
    -            request=req,
    -            response=resp,
    -            listener_name=get_name_for_callable(listener.ack_function),
    -            listener=listener,
    -        )
    -
    -

    Ancestors

    - -

    Inherited members

    - -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/workflows/step/internals.html b/docs/api-docs/slack_bolt/workflows/step/internals.html deleted file mode 100644 index ad20571a6..000000000 --- a/docs/api-docs/slack_bolt/workflows/step/internals.html +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - -slack_bolt.workflows.step.internals API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.workflows.step.internals

    -
    -
    -
    - -Expand source code - -
    def _is_used_without_argument(args):
    -    """Tests if a decorator invocation is without () or (args).
    -
    -    Args:
    -        args: arguments
    -
    -    Returns:
    -        True if it's an invocation without args
    -    """
    -    return len(args) == 1
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/workflows/step/step_middleware.html b/docs/api-docs/slack_bolt/workflows/step/step_middleware.html deleted file mode 100644 index 191009385..000000000 --- a/docs/api-docs/slack_bolt/workflows/step/step_middleware.html +++ /dev/null @@ -1,202 +0,0 @@ - - - - - - -slack_bolt.workflows.step.step_middleware API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.workflows.step.step_middleware

    -
    -
    -
    - -Expand source code - -
    from typing import Callable, Optional
    -
    -from slack_bolt.listener import Listener
    -from slack_bolt.listener.thread_runner import ThreadListenerRunner
    -from slack_bolt.middleware import Middleware
    -from slack_bolt.request import BoltRequest
    -from slack_bolt.response import BoltResponse
    -from slack_bolt.util.utils import get_name_for_callable
    -from slack_bolt.workflows.step.step import WorkflowStep
    -
    -
    -class WorkflowStepMiddleware(Middleware):  # type:ignore
    -    """Base middleware for workflow step specific ones"""
    -
    -    def __init__(self, step: WorkflowStep, listener_runner: ThreadListenerRunner):
    -        self.step = step
    -        self.listener_runner = listener_runner
    -
    -    def process(
    -        self,
    -        *,
    -        req: BoltRequest,
    -        resp: BoltResponse,
    -        # 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
    -        next: Callable[[], BoltResponse],
    -    ) -> Optional[BoltResponse]:
    -
    -        if self.step.edit.matches(req=req, resp=resp):
    -            resp = self._run(self.step.edit, req, resp)
    -            if resp is not None:
    -                return resp
    -        elif self.step.save.matches(req=req, resp=resp):
    -            resp = self._run(self.step.save, req, resp)
    -            if resp is not None:
    -                return resp
    -        elif self.step.execute.matches(req=req, resp=resp):
    -            resp = self._run(self.step.execute, req, resp)
    -            if resp is not None:
    -                return resp
    -
    -        return next()
    -
    -    def _run(
    -        self,
    -        listener: Listener,
    -        req: BoltRequest,
    -        resp: BoltResponse,
    -    ) -> Optional[BoltResponse]:
    -        resp, next_was_not_called = listener.run_middleware(req=req, resp=resp)
    -        if next_was_not_called:
    -            return None
    -
    -        return self.listener_runner.run(
    -            request=req,
    -            response=resp,
    -            listener_name=get_name_for_callable(listener.ack_function),
    -            listener=listener,
    -        )
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class WorkflowStepMiddleware -(step:ย WorkflowStep, listener_runner:ย ThreadListenerRunner) -
    -
    -

    Base middleware for workflow step specific ones

    -
    - -Expand source code - -
    class WorkflowStepMiddleware(Middleware):  # type:ignore
    -    """Base middleware for workflow step specific ones"""
    -
    -    def __init__(self, step: WorkflowStep, listener_runner: ThreadListenerRunner):
    -        self.step = step
    -        self.listener_runner = listener_runner
    -
    -    def process(
    -        self,
    -        *,
    -        req: BoltRequest,
    -        resp: BoltResponse,
    -        # 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
    -        next: Callable[[], BoltResponse],
    -    ) -> Optional[BoltResponse]:
    -
    -        if self.step.edit.matches(req=req, resp=resp):
    -            resp = self._run(self.step.edit, req, resp)
    -            if resp is not None:
    -                return resp
    -        elif self.step.save.matches(req=req, resp=resp):
    -            resp = self._run(self.step.save, req, resp)
    -            if resp is not None:
    -                return resp
    -        elif self.step.execute.matches(req=req, resp=resp):
    -            resp = self._run(self.step.execute, req, resp)
    -            if resp is not None:
    -                return resp
    -
    -        return next()
    -
    -    def _run(
    -        self,
    -        listener: Listener,
    -        req: BoltRequest,
    -        resp: BoltResponse,
    -    ) -> Optional[BoltResponse]:
    -        resp, next_was_not_called = listener.run_middleware(req=req, resp=resp)
    -        if next_was_not_called:
    -            return None
    -
    -        return self.listener_runner.run(
    -            request=req,
    -            response=resp,
    -            listener_name=get_name_for_callable(listener.ack_function),
    -            listener=listener,
    -        )
    -
    -

    Ancestors

    - -

    Inherited members

    - -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/workflows/step/utilities/async_complete.html b/docs/api-docs/slack_bolt/workflows/step/utilities/async_complete.html deleted file mode 100644 index 304931e44..000000000 --- a/docs/api-docs/slack_bolt/workflows/step/utilities/async_complete.html +++ /dev/null @@ -1,168 +0,0 @@ - - - - - - -slack_bolt.workflows.step.utilities.async_complete API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.workflows.step.utilities.async_complete

    -
    -
    -
    - -Expand source code - -
    from slack_sdk.web.async_client import AsyncWebClient
    -
    -
    -class AsyncComplete:
    -    """`complete()` utility to tell Slack the completion of a workflow step execution.
    -
    -        async 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"],
    -            }
    -            await complete(outputs=outputs)
    -
    -        ws = AsyncWorkflowStep(
    -            callback_id="add_task",
    -            edit=edit,
    -            save=save,
    -            execute=execute,
    -        )
    -        app.step(ws)
    -
    -    This utility is a thin wrapper of workflows.stepCompleted API method.
    -    Refer to https://api.slack.com/methods/workflows.stepCompleted for details.
    -    """
    -
    -    def __init__(self, *, client: AsyncWebClient, body: dict):
    -        self.client = client
    -        self.body = body
    -
    -    async def __call__(self, **kwargs) -> None:
    -        await self.client.workflows_stepCompleted(
    -            workflow_step_execute_id=self.body["event"]["workflow_step"]["workflow_step_execute_id"],
    -            **kwargs,
    -        )
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class AsyncComplete -(*, client:ย slack_sdk.web.async_client.AsyncWebClient, body:ย dict) -
    -
    -

    complete() utility to tell Slack the completion of a workflow step execution.

    -
    async 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"],
    -    }
    -    await complete(outputs=outputs)
    -
    -ws = AsyncWorkflowStep(
    -    callback_id="add_task",
    -    edit=edit,
    -    save=save,
    -    execute=execute,
    -)
    -app.step(ws)
    -
    -

    This utility is a thin wrapper of workflows.stepCompleted API method. -Refer to https://api.slack.com/methods/workflows.stepCompleted for details.

    -
    - -Expand source code - -
    class AsyncComplete:
    -    """`complete()` utility to tell Slack the completion of a workflow step execution.
    -
    -        async 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"],
    -            }
    -            await complete(outputs=outputs)
    -
    -        ws = AsyncWorkflowStep(
    -            callback_id="add_task",
    -            edit=edit,
    -            save=save,
    -            execute=execute,
    -        )
    -        app.step(ws)
    -
    -    This utility is a thin wrapper of workflows.stepCompleted API method.
    -    Refer to https://api.slack.com/methods/workflows.stepCompleted for details.
    -    """
    -
    -    def __init__(self, *, client: AsyncWebClient, body: dict):
    -        self.client = client
    -        self.body = body
    -
    -    async def __call__(self, **kwargs) -> None:
    -        await self.client.workflows_stepCompleted(
    -            workflow_step_execute_id=self.body["event"]["workflow_step"]["workflow_step_execute_id"],
    -            **kwargs,
    -        )
    -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/workflows/step/utilities/async_configure.html b/docs/api-docs/slack_bolt/workflows/step/utilities/async_configure.html deleted file mode 100644 index 54d89a110..000000000 --- a/docs/api-docs/slack_bolt/workflows/step/utilities/async_configure.html +++ /dev/null @@ -1,210 +0,0 @@ - - - - - - -slack_bolt.workflows.step.utilities.async_configure API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.workflows.step.utilities.async_configure

    -
    -
    -
    - -Expand source code - -
    from typing import Optional, Union, Sequence
    -
    -from slack_sdk.web.async_client import AsyncWebClient
    -from slack_sdk.models.blocks import Block
    -
    -
    -class AsyncConfigure:
    -    """`configure()` utility to send the modal view in Workflow Builder.
    -
    -        async def edit(ack, step, configure):
    -            await 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"},
    -                },
    -            ]
    -            await configure(blocks=blocks)
    -
    -        ws = AsyncWorkflowStep(
    -            callback_id="add_task",
    -            edit=edit,
    -            save=save,
    -            execute=execute,
    -        )
    -        app.step(ws)
    -
    -    Refer to https://api.slack.com/workflows/steps for details.
    -    """
    -
    -    def __init__(self, *, callback_id: str, client: AsyncWebClient, body: dict):
    -        self.callback_id = callback_id
    -        self.client = client
    -        self.body = body
    -
    -    async def __call__(
    -        self,
    -        *,
    -        blocks: Optional[Sequence[Union[dict, Block]]] = None,
    -    ) -> None:
    -        await self.client.views_open(
    -            trigger_id=self.body["trigger_id"],
    -            view={
    -                "type": "workflow_step",
    -                "callback_id": self.callback_id,
    -                "blocks": blocks,
    -            },
    -        )
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class AsyncConfigure -(*, callback_id:ย str, client:ย slack_sdk.web.async_client.AsyncWebClient, body:ย dict) -
    -
    -

    configure() utility to send the modal view in Workflow Builder.

    -
    async def edit(ack, step, configure):
    -    await 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"},
    -        },
    -    ]
    -    await configure(blocks=blocks)
    -
    -ws = AsyncWorkflowStep(
    -    callback_id="add_task",
    -    edit=edit,
    -    save=save,
    -    execute=execute,
    -)
    -app.step(ws)
    -
    -

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

    -
    - -Expand source code - -
    class AsyncConfigure:
    -    """`configure()` utility to send the modal view in Workflow Builder.
    -
    -        async def edit(ack, step, configure):
    -            await 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"},
    -                },
    -            ]
    -            await configure(blocks=blocks)
    -
    -        ws = AsyncWorkflowStep(
    -            callback_id="add_task",
    -            edit=edit,
    -            save=save,
    -            execute=execute,
    -        )
    -        app.step(ws)
    -
    -    Refer to https://api.slack.com/workflows/steps for details.
    -    """
    -
    -    def __init__(self, *, callback_id: str, client: AsyncWebClient, body: dict):
    -        self.callback_id = callback_id
    -        self.client = client
    -        self.body = body
    -
    -    async def __call__(
    -        self,
    -        *,
    -        blocks: Optional[Sequence[Union[dict, Block]]] = None,
    -    ) -> None:
    -        await self.client.views_open(
    -            trigger_id=self.body["trigger_id"],
    -            view={
    -                "type": "workflow_step",
    -                "callback_id": self.callback_id,
    -                "blocks": blocks,
    -            },
    -        )
    -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/workflows/step/utilities/async_fail.html b/docs/api-docs/slack_bolt/workflows/step/utilities/async_fail.html deleted file mode 100644 index ef019c286..000000000 --- a/docs/api-docs/slack_bolt/workflows/step/utilities/async_fail.html +++ /dev/null @@ -1,167 +0,0 @@ - - - - - - -slack_bolt.workflows.step.utilities.async_fail API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.workflows.step.utilities.async_fail

    -
    -
    -
    - -Expand source code - -
    from slack_sdk.web.async_client import AsyncWebClient
    -
    -
    -class AsyncFail:
    -    """`fail()` utility to tell Slack the execution failure of a workflow step.
    -
    -        async def execute(step, complete, fail):
    -            inputs = step["inputs"]
    -            # if something went wrong
    -            error = {"message": "Just testing step failure!"}
    -            await fail(error=error)
    -
    -        ws = AsyncWorkflowStep(
    -            callback_id="add_task",
    -            edit=edit,
    -            save=save,
    -            execute=execute,
    -        )
    -        app.step(ws)
    -
    -    This utility is a thin wrapper of workflows.stepFailed API method.
    -    Refer to https://api.slack.com/methods/workflows.stepFailed for details.
    -    """
    -
    -    def __init__(self, *, client: AsyncWebClient, body: dict):
    -        self.client = client
    -        self.body = body
    -
    -    async def __call__(
    -        self,
    -        *,
    -        error: dict,
    -    ) -> None:
    -        await self.client.workflows_stepFailed(
    -            workflow_step_execute_id=self.body["event"]["workflow_step"]["workflow_step_execute_id"],
    -            error=error,
    -        )
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class AsyncFail -(*, client:ย slack_sdk.web.async_client.AsyncWebClient, body:ย dict) -
    -
    -

    fail() utility to tell Slack the execution failure of a workflow step.

    -
    async def execute(step, complete, fail):
    -    inputs = step["inputs"]
    -    # if something went wrong
    -    error = {"message": "Just testing step failure!"}
    -    await fail(error=error)
    -
    -ws = AsyncWorkflowStep(
    -    callback_id="add_task",
    -    edit=edit,
    -    save=save,
    -    execute=execute,
    -)
    -app.step(ws)
    -
    -

    This utility is a thin wrapper of workflows.stepFailed API method. -Refer to https://api.slack.com/methods/workflows.stepFailed for details.

    -
    - -Expand source code - -
    class AsyncFail:
    -    """`fail()` utility to tell Slack the execution failure of a workflow step.
    -
    -        async def execute(step, complete, fail):
    -            inputs = step["inputs"]
    -            # if something went wrong
    -            error = {"message": "Just testing step failure!"}
    -            await fail(error=error)
    -
    -        ws = AsyncWorkflowStep(
    -            callback_id="add_task",
    -            edit=edit,
    -            save=save,
    -            execute=execute,
    -        )
    -        app.step(ws)
    -
    -    This utility is a thin wrapper of workflows.stepFailed API method.
    -    Refer to https://api.slack.com/methods/workflows.stepFailed for details.
    -    """
    -
    -    def __init__(self, *, client: AsyncWebClient, body: dict):
    -        self.client = client
    -        self.body = body
    -
    -    async def __call__(
    -        self,
    -        *,
    -        error: dict,
    -    ) -> None:
    -        await self.client.workflows_stepFailed(
    -            workflow_step_execute_id=self.body["event"]["workflow_step"]["workflow_step_execute_id"],
    -            error=error,
    -        )
    -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/workflows/step/utilities/async_update.html b/docs/api-docs/slack_bolt/workflows/step/utilities/async_update.html deleted file mode 100644 index bce9199a0..000000000 --- a/docs/api-docs/slack_bolt/workflows/step/utilities/async_update.html +++ /dev/null @@ -1,216 +0,0 @@ - - - - - - -slack_bolt.workflows.step.utilities.async_update API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.workflows.step.utilities.async_update

    -
    -
    -
    - -Expand source code - -
    from slack_sdk.web.async_client import AsyncWebClient
    -
    -
    -class AsyncUpdate:
    -    """`update()` utility to tell Slack the processing results of a `save` listener.
    -
    -        async def save(ack, view, update):
    -            await 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",
    -                }
    -            ]
    -            await update(inputs=inputs, outputs=outputs)
    -
    -        ws = AsyncWorkflowStep(
    -            callback_id="add_task",
    -            edit=edit,
    -            save=save,
    -            execute=execute,
    -        )
    -        app.step(ws)
    -
    -    This utility is a thin wrapper of workflows.stepFailed API method.
    -    Refer to https://api.slack.com/methods/workflows.updateStep for details.
    -    """
    -
    -    def __init__(self, *, client: AsyncWebClient, body: dict):
    -        self.client = client
    -        self.body = body
    -
    -    async def __call__(self, **kwargs) -> None:
    -        await self.client.workflows_updateStep(
    -            workflow_step_edit_id=self.body["workflow_step"]["workflow_step_edit_id"],
    -            **kwargs,
    -        )
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class AsyncUpdate -(*, client:ย slack_sdk.web.async_client.AsyncWebClient, body:ย dict) -
    -
    -

    update() utility to tell Slack the processing results of a save listener.

    -
    async def save(ack, view, update):
    -    await 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",
    -        }
    -    ]
    -    await update(inputs=inputs, outputs=outputs)
    -
    -ws = AsyncWorkflowStep(
    -    callback_id="add_task",
    -    edit=edit,
    -    save=save,
    -    execute=execute,
    -)
    -app.step(ws)
    -
    -

    This utility is a thin wrapper of workflows.stepFailed API method. -Refer to https://api.slack.com/methods/workflows.updateStep for details.

    -
    - -Expand source code - -
    class AsyncUpdate:
    -    """`update()` utility to tell Slack the processing results of a `save` listener.
    -
    -        async def save(ack, view, update):
    -            await 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",
    -                }
    -            ]
    -            await update(inputs=inputs, outputs=outputs)
    -
    -        ws = AsyncWorkflowStep(
    -            callback_id="add_task",
    -            edit=edit,
    -            save=save,
    -            execute=execute,
    -        )
    -        app.step(ws)
    -
    -    This utility is a thin wrapper of workflows.stepFailed API method.
    -    Refer to https://api.slack.com/methods/workflows.updateStep for details.
    -    """
    -
    -    def __init__(self, *, client: AsyncWebClient, body: dict):
    -        self.client = client
    -        self.body = body
    -
    -    async def __call__(self, **kwargs) -> None:
    -        await self.client.workflows_updateStep(
    -            workflow_step_edit_id=self.body["workflow_step"]["workflow_step_edit_id"],
    -            **kwargs,
    -        )
    -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/workflows/step/utilities/complete.html b/docs/api-docs/slack_bolt/workflows/step/utilities/complete.html deleted file mode 100644 index c833daef0..000000000 --- a/docs/api-docs/slack_bolt/workflows/step/utilities/complete.html +++ /dev/null @@ -1,168 +0,0 @@ - - - - - - -slack_bolt.workflows.step.utilities.complete API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.workflows.step.utilities.complete

    -
    -
    -
    - -Expand source code - -
    from slack_sdk.web import WebClient
    -
    -
    -class Complete:
    -    """`complete()` utility to tell Slack the completion of a workflow step execution.
    -
    -        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)
    -
    -        ws = WorkflowStep(
    -            callback_id="add_task",
    -            edit=edit,
    -            save=save,
    -            execute=execute,
    -        )
    -        app.step(ws)
    -
    -    This utility is a thin wrapper of workflows.stepCompleted API method.
    -    Refer to https://api.slack.com/methods/workflows.stepCompleted for details.
    -    """
    -
    -    def __init__(self, *, client: WebClient, body: dict):
    -        self.client = client
    -        self.body = body
    -
    -    def __call__(self, **kwargs) -> None:
    -        self.client.workflows_stepCompleted(
    -            workflow_step_execute_id=self.body["event"]["workflow_step"]["workflow_step_execute_id"],
    -            **kwargs,
    -        )
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class Complete -(*, client:ย slack_sdk.web.client.WebClient, body:ย dict) -
    -
    -

    complete() utility to tell Slack the completion of a workflow step execution.

    -
    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)
    -
    -ws = WorkflowStep(
    -    callback_id="add_task",
    -    edit=edit,
    -    save=save,
    -    execute=execute,
    -)
    -app.step(ws)
    -
    -

    This utility is a thin wrapper of workflows.stepCompleted API method. -Refer to https://api.slack.com/methods/workflows.stepCompleted for details.

    -
    - -Expand source code - -
    class Complete:
    -    """`complete()` utility to tell Slack the completion of a workflow step execution.
    -
    -        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)
    -
    -        ws = WorkflowStep(
    -            callback_id="add_task",
    -            edit=edit,
    -            save=save,
    -            execute=execute,
    -        )
    -        app.step(ws)
    -
    -    This utility is a thin wrapper of workflows.stepCompleted API method.
    -    Refer to https://api.slack.com/methods/workflows.stepCompleted for details.
    -    """
    -
    -    def __init__(self, *, client: WebClient, body: dict):
    -        self.client = client
    -        self.body = body
    -
    -    def __call__(self, **kwargs) -> None:
    -        self.client.workflows_stepCompleted(
    -            workflow_step_execute_id=self.body["event"]["workflow_step"]["workflow_step_execute_id"],
    -            **kwargs,
    -        )
    -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/workflows/step/utilities/configure.html b/docs/api-docs/slack_bolt/workflows/step/utilities/configure.html deleted file mode 100644 index ff609fbdc..000000000 --- a/docs/api-docs/slack_bolt/workflows/step/utilities/configure.html +++ /dev/null @@ -1,204 +0,0 @@ - - - - - - -slack_bolt.workflows.step.utilities.configure API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.workflows.step.utilities.configure

    -
    -
    -
    - -Expand source code - -
    from typing import Optional, Union, Sequence
    -
    -from slack_sdk.web import WebClient
    -from slack_sdk.models.blocks import Block
    -
    -
    -class Configure:
    -    """`configure()` utility to send the modal view in Workflow Builder.
    -
    -        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"},
    -                },
    -            ]
    -            configure(blocks=blocks)
    -
    -        ws = WorkflowStep(
    -            callback_id="add_task",
    -            edit=edit,
    -            save=save,
    -            execute=execute,
    -        )
    -        app.step(ws)
    -
    -    Refer to https://api.slack.com/workflows/steps for details.
    -    """
    -
    -    def __init__(self, *, callback_id: str, client: WebClient, body: dict):
    -        self.callback_id = callback_id
    -        self.client = client
    -        self.body = body
    -
    -    def __call__(self, *, blocks: Optional[Sequence[Union[dict, Block]]] = None, **kwargs) -> None:
    -        self.client.views_open(
    -            trigger_id=self.body["trigger_id"],
    -            view={
    -                "type": "workflow_step",
    -                "callback_id": self.callback_id,
    -                "blocks": blocks,
    -                **kwargs,
    -            },
    -        )
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class Configure -(*, callback_id:ย str, client:ย slack_sdk.web.client.WebClient, body:ย dict) -
    -
    -

    configure() utility to send the modal view in Workflow Builder.

    -
    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"},
    -        },
    -    ]
    -    configure(blocks=blocks)
    -
    -ws = WorkflowStep(
    -    callback_id="add_task",
    -    edit=edit,
    -    save=save,
    -    execute=execute,
    -)
    -app.step(ws)
    -
    -

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

    -
    - -Expand source code - -
    class Configure:
    -    """`configure()` utility to send the modal view in Workflow Builder.
    -
    -        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"},
    -                },
    -            ]
    -            configure(blocks=blocks)
    -
    -        ws = WorkflowStep(
    -            callback_id="add_task",
    -            edit=edit,
    -            save=save,
    -            execute=execute,
    -        )
    -        app.step(ws)
    -
    -    Refer to https://api.slack.com/workflows/steps for details.
    -    """
    -
    -    def __init__(self, *, callback_id: str, client: WebClient, body: dict):
    -        self.callback_id = callback_id
    -        self.client = client
    -        self.body = body
    -
    -    def __call__(self, *, blocks: Optional[Sequence[Union[dict, Block]]] = None, **kwargs) -> None:
    -        self.client.views_open(
    -            trigger_id=self.body["trigger_id"],
    -            view={
    -                "type": "workflow_step",
    -                "callback_id": self.callback_id,
    -                "blocks": blocks,
    -                **kwargs,
    -            },
    -        )
    -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/workflows/step/utilities/fail.html b/docs/api-docs/slack_bolt/workflows/step/utilities/fail.html deleted file mode 100644 index ce719bf98..000000000 --- a/docs/api-docs/slack_bolt/workflows/step/utilities/fail.html +++ /dev/null @@ -1,167 +0,0 @@ - - - - - - -slack_bolt.workflows.step.utilities.fail API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.workflows.step.utilities.fail

    -
    -
    -
    - -Expand source code - -
    from slack_sdk.web import WebClient
    -
    -
    -class Fail:
    -    """`fail()` utility to tell Slack the execution failure of a workflow step.
    -
    -        def execute(step, complete, fail):
    -            inputs = step["inputs"]
    -            # 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)
    -
    -    This utility is a thin wrapper of workflows.stepFailed API method.
    -    Refer to https://api.slack.com/methods/workflows.stepFailed for details.
    -    """
    -
    -    def __init__(self, *, client: WebClient, body: dict):
    -        self.client = client
    -        self.body = body
    -
    -    def __call__(
    -        self,
    -        *,
    -        error: dict,
    -    ) -> None:
    -        self.client.workflows_stepFailed(
    -            workflow_step_execute_id=self.body["event"]["workflow_step"]["workflow_step_execute_id"],
    -            error=error,
    -        )
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class Fail -(*, client:ย slack_sdk.web.client.WebClient, body:ย dict) -
    -
    -

    fail() utility to tell Slack the execution failure of a workflow step.

    -
    def execute(step, complete, fail):
    -    inputs = step["inputs"]
    -    # 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)
    -
    -

    This utility is a thin wrapper of workflows.stepFailed API method. -Refer to https://api.slack.com/methods/workflows.stepFailed for details.

    -
    - -Expand source code - -
    class Fail:
    -    """`fail()` utility to tell Slack the execution failure of a workflow step.
    -
    -        def execute(step, complete, fail):
    -            inputs = step["inputs"]
    -            # 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)
    -
    -    This utility is a thin wrapper of workflows.stepFailed API method.
    -    Refer to https://api.slack.com/methods/workflows.stepFailed for details.
    -    """
    -
    -    def __init__(self, *, client: WebClient, body: dict):
    -        self.client = client
    -        self.body = body
    -
    -    def __call__(
    -        self,
    -        *,
    -        error: dict,
    -    ) -> None:
    -        self.client.workflows_stepFailed(
    -            workflow_step_execute_id=self.body["event"]["workflow_step"]["workflow_step_execute_id"],
    -            error=error,
    -        )
    -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/api-docs/slack_bolt/workflows/step/utilities/update.html b/docs/api-docs/slack_bolt/workflows/step/utilities/update.html deleted file mode 100644 index 51e87b126..000000000 --- a/docs/api-docs/slack_bolt/workflows/step/utilities/update.html +++ /dev/null @@ -1,216 +0,0 @@ - - - - - - -slack_bolt.workflows.step.utilities.update API documentation - - - - - - - - - - - -
    -
    -
    -

    Module slack_bolt.workflows.step.utilities.update

    -
    -
    -
    - -Expand source code - -
    from slack_sdk.web import WebClient
    -
    -
    -class Update:
    -    """`update()` utility to tell Slack the processing results of a `save` listener.
    -
    -        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)
    -
    -    This utility is a thin wrapper of workflows.stepFailed API method.
    -    Refer to https://api.slack.com/methods/workflows.updateStep for details.
    -    """
    -
    -    def __init__(self, *, client: WebClient, body: dict):
    -        self.client = client
    -        self.body = body
    -
    -    def __call__(self, **kwargs) -> None:
    -        self.client.workflows_updateStep(
    -            workflow_step_edit_id=self.body["workflow_step"]["workflow_step_edit_id"],
    -            **kwargs,
    -        )
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -

    Classes

    -
    -
    -class Update -(*, client:ย slack_sdk.web.client.WebClient, body:ย dict) -
    -
    -

    update() utility to tell Slack the processing results of a save listener.

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

    This utility is a thin wrapper of workflows.stepFailed API method. -Refer to https://api.slack.com/methods/workflows.updateStep for details.

    -
    - -Expand source code - -
    class Update:
    -    """`update()` utility to tell Slack the processing results of a `save` listener.
    -
    -        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)
    -
    -    This utility is a thin wrapper of workflows.stepFailed API method.
    -    Refer to https://api.slack.com/methods/workflows.updateStep for details.
    -    """
    -
    -    def __init__(self, *, client: WebClient, body: dict):
    -        self.client = client
    -        self.body = body
    -
    -    def __call__(self, **kwargs) -> None:
    -        self.client.workflows_updateStep(
    -            workflow_step_edit_id=self.body["workflow_step"]["workflow_step_edit_id"],
    -            **kwargs,
    -        )
    -
    -
    -
    -
    -
    - -
    - - - \ No newline at end of file diff --git a/docs/assets/bolt-favicon.png b/docs/assets/bolt-favicon.png deleted file mode 100644 index bfe5456c1..000000000 Binary files a/docs/assets/bolt-favicon.png and /dev/null differ diff --git a/docs/assets/bolt-logo.svg b/docs/assets/bolt-logo.svg deleted file mode 100644 index 5077600d5..000000000 --- a/docs/assets/bolt-logo.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/docs/assets/bolt-py-logo.svg b/docs/assets/bolt-py-logo.svg deleted file mode 100644 index 1dcab5261..000000000 --- a/docs/assets/bolt-py-logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/assets/ngrok.gif b/docs/assets/ngrok.gif deleted file mode 100644 index c7c94d51a..000000000 Binary files a/docs/assets/ngrok.gif and /dev/null differ diff --git a/docs/assets/request-url-config.png b/docs/assets/request-url-config.png deleted file mode 100644 index 5315298a8..000000000 Binary files a/docs/assets/request-url-config.png and /dev/null differ diff --git a/docs/assets/signing-secret.png b/docs/assets/signing-secret.png deleted file mode 100644 index d32afa03e..000000000 Binary files a/docs/assets/signing-secret.png and /dev/null differ diff --git a/docs/assets/style.css b/docs/assets/style.css deleted file mode 100644 index 92ba66b52..000000000 --- a/docs/assets/style.css +++ /dev/null @@ -1,526 +0,0 @@ -/* Color variables */ -:root { - --light-grey: #F8F8F8; - --grey: #868686; - --dark-grey: #616061; - --soft-grey: #DDDDDD; - --blue: #1264A3; - --green: #00B073; - --light-blue: #B8D1E3; - --white: #FFFFFF; - --black: #1D1C1D; -} - -html, body { - background-color: var(--white); - font-family: 'Noto Sans JP', 'Slack-Lato', sans-serif; - font-size: 100%; -} - -.content { - grid-area: content; -} - -span.beta { - background-color: #E8F5FA; - color: var(--blue); - padding: 4px 9px; - margin-right: 2px; - border-radius: 16px; - border: 1px solid #D4ECF6; - text-transform: uppercase; - font-weight: 600; - font-size: 0.7em; -} - -/* Sidebar */ -.panel { - position: fixed; - width: 20%; - height: 100%; - overflow: auto; - top: 0; - left: 0; - background-color: var(--light-grey); -} - -.panel .sidebar-content { - width: 75%; - margin: 30px auto 20px auto; -} - -.panel .sidebar-content .logo { - padding-top: 1.15em; - position: relative; -} - -.panel .sidebar-content .logo .icon img { - width: 2.15em; - margin-right: 6px; -} - -.panel .sidebar-content .logo .name { - font-weight: 800; - font-size: 2em; - vertical-align: bottom; -} - -.panel .sidebar-content .logo .version { - line-height: 1em; - vertical-align: bottom; -} - -.panel .sidebar-content .logo .version a { - color: var(--dark-grey); - background-color: var(--soft-grey); - font-size: 0.5em; - font-weight: 800; - padding: 4px 10px; - border-radius: 12px; - margin-left: 10px; - -} - -.panel .sidebar-content ul.sidebar-section { - list-style: none; - list-style-position: inside; - padding-top: 0.9em; - margin: 0 0 0 -0.5em; - font-size: 0.95em; -} - -.panel .sidebar-content ul.sidebar-section li { - border-radius: 8px; - padding: 2px 0 2px 8px; - margin: 0.1em 0; - color: var(--black); -} - -.panel .sidebar-content ul.sidebar-section li:hover { - background-color: #D7D7D7; -} - -.panel .sidebar-content ul.sidebar-section li.madeby:hover { - background-color: transparent; -} - -.panel .sidebar-content a:hover { - text-decoration: none; -} - -.panel .sidebar-content ul.sidebar-section li.active { - background-color: var(--blue); - color: var(--white); -} - -.panel .sidebar-content ul.sidebar-section li.title { - font-weight: 600; -} - -.panel .sidebar-content ul.sidebar-section .label-legacy { - line-height: 1em; - vertical-align: middle; - color: var(--white); - background-color: var(--dark-grey); - font-size: 0.5em; - font-weight: 800; - padding: 4px 10px; - border-radius: 12px; -} - -.panel .sidebar-content ul.sidebar-section .label-beta { - line-height: 1em; - vertical-align: middle; - color: var(--white); - background-color: var(--blue); - font-size: 0.5em; - font-weight: 800; - padding: 4px 10px; - border-radius: 12px; -} -/* Main page */ -.header { - width: 95%; - margin: 0 auto 0.8em auto; - height: 5em; - padding-top: 1.5em; -} - -.header a:hover { - text-decoration: none; -} - -.header a.language-switcher { - color: var(--grey); - font-weight: 700; - padding: 6px 14px 9px; - font-size: 0.9em; -} - -.header a.language-switcher:hover { - color: var(--black); -} - -.wrapper { - width: 100%; - margin: 0 auto; -} - -/* Main page content */ -.section-wrapper { - width: 90%; - margin: 0 auto 30px auto; - display: grid; - grid-gap: 20px; - grid-template-areas: - "head" - "body" - "code" - "secondary" - "divider" -} - -.tutorial-nav { - width: 20%; - position: fixed; -} - -.tutorial-nav ul { - margin-left: 3em; - padding-left: 1em; - border-width: 4px; - border-left-style: solid; - border-color: #F2F2F2; - border-image: linear-gradient( - to bottom, - var(--white) 0%, - #F2F2F2 6%, - var(--white) 100% - ) 1 100%; - list-style: none; - padding-top: 1.5em; -} - -.circle { - background: var(--soft-grey); - border-radius: 50%; - height: 1em; - width: 1em; - float: left; - margin: 5px 0 0 -1.6em; -} - -.completed { - background: #53b3e1; -} - -.tutorial-nav ul li { - padding-bottom: 2.5em; -} - -.tutorial-nav a { - font-weight: 700; - font-size: 0.9em; - color: #757575; -} - -.tutorial-nav a:hover { - color: #000; - text-decoration: none; -} - -.tutorial { - width: 55%; - margin: 1em 0 0 33%; - padding-bottom: 2em; -} - -.tutorial img { - width: 85%; - margin: 0.2em auto; - display: block; - box-shadow: 0 0 15px var(--soft-grey); -} - -.tutorial blockquote { - margin: 0 0 0 1em; - padding: 0 6em 0 2em; - border-radius: 6px; - border-left: 6px solid var(--soft-grey); - font-size: 1em; -} - -.tutorial h3 { - padding-bottom: 1em; -} - -.tutorial .label-beta { - line-height: 1em; - vertical-align: middle; - color: var(--white); - background-color: var(--blue); - font-size: 0.45em; - font-weight: 800; - padding: 4px 10px; - border-radius: 12px; - margin-left: 10px; -} - -.section-wrapper .label-beta { - line-height: 1em; - vertical-align: middle; - color: var(--white); - background-color: var(--blue); - font-size: 0.45em; - font-weight: 800; - padding: 4px 10px; - border-radius: 12px; - margin-left: 10px; -} -.content .section-wrapper .highlighter-rouge { - grid-area: code; -} - -pre { - background-color: var(--light-grey) !important; - background-image: none; - padding: 1em 1.5em; - border: 1px solid var(--soft-grey); - margin: 0; - border-radius: 1em; -} - -pre.structure { - padding: 0.5em; - line-height: 1.1em; - font-size: 0.9em !important; -} - -/* Code block with column numbers */ -pre.highlight { - line-height: 2em; - overflow-x: auto; -} - -pre.highlight code span { - padding: 0; - margin: 0; - height: 0; -} - -pre.highlight code pre { - padding: 0; - border: 0; - font-size: 0.9em; - overflow: visible; -} - -table.rouge-table, td.rouge-code, td.rouge-gutter { - padding: 0; - border: 0; - margin: 0; -} - -td.rouge-gutter { - padding-right: 1em; - user-select: none; - color: var(--dark-grey); -} -/* End Code block with column numbers */ - -.content .section-wrapper .section-content { - grid-area: body; -} - -.content .section-wrapper, .tutorial { - font-size: 1.15em; - line-height: 1.9em; -} - -.content .section-wrapper .annotation { - font-size: 0.7em; -} - -.content .section-wrapper h3 { - grid-area: head; - font-size: 1.45em; - font-weight: 600; -} - -.content .section-wrapper hr { - grid-area: divider; - height: 1px; - border-top: 1px solid var(--soft-grey); - width: 100%; -} - -.content .section-wrapper .label-legacy { - line-height: 1em; - vertical-align: middle; - color: var(--white); - background-color: var(--dark-grey); - font-size: 0.5em; - font-weight: 800; - padding: 4px 10px; - border-radius: 12px; - margin-left: 10px; -} - -a:hover { - text-decoration: underline; -} - -/* Secondary content */ -.secondary-wrapper { - width: 100%; - grid-area: secondary; - margin: 0.6em auto 0 auto; -} - -.secondary-wrapper div.highlighter-rouge { - width: 50%; - float: left; - margin-top: 1em; -} - -.content .section-wrapper .secondary-content { - width: 45%; - float: left; - margin-right: 5%; - margin-top: 1em; -} - -.content .section-wrapper .secondary-content div.highlighter-rouge { - width: 100%; -} - -summary h4 { - display: inline; -} - -/* Responsive */ -@media (min-width: 1024px) { - .tutorial-nav ul { - margin-left: 5em; - } -} - -@media (min-width: 768px) { - .wrapper { - display: grid; - grid-template-columns: 20% 75%; - grid-template-areas: - "sidebar content" - } - - .section-wrapper { - grid-template-columns: 50% 50%; - grid-template-areas: - "head head" - "body code" - "secondary secondary" - "divider divider" - } -} - -@media (max-width: 768px) { - .panel { - display: none; - } - - .language-switcher { - display: none; - } - - .tutorial-nav { - display: none; - } - - .tutorial { - width: 85%; - margin: 1em auto; - } - - .wrapper { - display: grid; - grid-template-columns: 100%; - grid-template-areas: - "content" - } - - .section-wrapper { - grid-template-columns: 100%; - grid-template-areas: - "head" - "body" - "code" - "secondary" - "divider" - } -} - - -/* - * Github theme stylesheet from: http://jwarby.github.io/jekyll-pygments-themes/languages/python.html - */ - .highlight .hll { background-color: #ffffcc } - .highlight .c { color: #999988; font-style: italic } /* Comment */ - .highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ - .highlight .k { color: #000000; font-weight: bold } /* Keyword */ - .highlight .o { color: #000000; font-weight: bold } /* Operator */ - .highlight .cm { color: #999988; font-style: italic } /* Comment.Multiline */ - .highlight .cp { color: #999999; font-weight: bold; font-style: italic } /* Comment.Preproc */ - .highlight .c1 { color: #999988; font-style: italic } /* Comment.Single */ - .highlight .cs { color: #999999; font-weight: bold; font-style: italic } /* Comment.Special */ - .highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ - .highlight .ge { color: #000000; font-style: italic } /* Generic.Emph */ - .highlight .gr { color: #aa0000 } /* Generic.Error */ - .highlight .gh { color: #999999 } /* Generic.Heading */ - .highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ - .highlight .go { color: #888888 } /* Generic.Output */ - .highlight .gp { color: #555555 } /* Generic.Prompt */ - .highlight .gs { font-weight: bold } /* Generic.Strong */ - .highlight .gu { color: #aaaaaa } /* Generic.Subheading */ - .highlight .gt { color: #aa0000 } /* Generic.Traceback */ - .highlight .kc { color: #000000; font-weight: bold } /* Keyword.Constant */ - .highlight .kd { color: #000000; font-weight: bold } /* Keyword.Declaration */ - .highlight .kn { color: #000000; font-weight: bold } /* Keyword.Namespace */ - .highlight .kp { color: #000000; font-weight: bold } /* Keyword.Pseudo */ - .highlight .kr { color: #000000; font-weight: bold } /* Keyword.Reserved */ - .highlight .kt { color: #445588; font-weight: bold } /* Keyword.Type */ - .highlight .m { color: #009999 } /* Literal.Number */ - .highlight .s { color: #d01040 } /* Literal.String */ - .highlight .na { color: #008080 } /* Name.Attribute */ - .highlight .nb { color: #0086B3 } /* Name.Builtin */ - .highlight .nc { color: #445588; font-weight: bold } /* Name.Class */ - .highlight .no { color: #008080 } /* Name.Constant */ - .highlight .nd { color: #3c5d5d; font-weight: bold } /* Name.Decorator */ - .highlight .ni { color: #800080 } /* Name.Entity */ - .highlight .ne { color: #990000; font-weight: bold } /* Name.Exception */ - .highlight .nf { color: #990000; font-weight: bold } /* Name.Function */ - .highlight .nl { color: #990000; font-weight: bold } /* Name.Label */ - .highlight .nn { color: #555555 } /* Name.Namespace */ - .highlight .nt { color: #000080 } /* Name.Tag */ - .highlight .nv { color: #008080 } /* Name.Variable */ - .highlight .ow { color: #000000; font-weight: bold } /* Operator.Word */ - .highlight .w { color: #bbbbbb } /* Text.Whitespace */ - .highlight .mf { color: #009999 } /* Literal.Number.Float */ - .highlight .mh { color: #009999 } /* Literal.Number.Hex */ - .highlight .mi { color: #009999 } /* Literal.Number.Integer */ - .highlight .mo { color: #009999 } /* Literal.Number.Oct */ - .highlight .sb { color: #d01040 } /* Literal.String.Backtick */ - .highlight .sc { color: #d01040 } /* Literal.String.Char */ - .highlight .sd { color: #d01040 } /* Literal.String.Doc */ - .highlight .s2 { color: #d01040 } /* Literal.String.Double */ - .highlight .se { color: #d01040 } /* Literal.String.Escape */ - .highlight .sh { color: #d01040 } /* Literal.String.Heredoc */ - .highlight .si { color: #d01040 } /* Literal.String.Interpol */ - .highlight .sx { color: #d01040 } /* Literal.String.Other */ - .highlight .sr { color: #009926 } /* Literal.String.Regex */ - .highlight .s1 { color: #d01040 } /* Literal.String.Single */ - .highlight .ss { color: #990073 } /* Literal.String.Symbol */ - .highlight .bp { color: #999999 } /* Name.Builtin.Pseudo */ - .highlight .vc { color: #008080 } /* Name.Variable.Class */ - .highlight .vg { color: #008080 } /* Name.Variable.Global */ - .highlight .vi { color: #008080 } /* Name.Variable.Instance */ - .highlight .il { color: #009999 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/docs/english/_sidebar.json b/docs/english/_sidebar.json new file mode 100644 index 000000000..79721bdcd --- /dev/null +++ b/docs/english/_sidebar.json @@ -0,0 +1,222 @@ +[ + { + "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"] + } + ] + }, + { "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" + } +] diff --git a/docs/_basic/acknowledging_requests.md b/docs/english/concepts/acknowledge.md similarity index 58% rename from docs/_basic/acknowledging_requests.md rename to docs/english/concepts/acknowledge.md index 41aeef193..57b346bd3 100644 --- a/docs/_basic/acknowledging_requests.md +++ b/docs/english/concepts/acknowledge.md @@ -1,23 +1,19 @@ ---- -title: Acknowledging requests -lang: en -slug: acknowledge -order: 7 ---- - -
    +# 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](#update-views-on-submission). +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. -๐Ÿ’ก When working in a FaaS / serverless environment, our guidelines for when to `ack()` are different. See the section on [Lazy listeners (FaaS)](#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://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html) to learn the available listener arguments. + +## Example -
    -Refer to the module document to learn the available listener arguments. ```python # Example of responding to an external_select options request @app.options("menu_selection") @@ -34,4 +30,3 @@ def show_menu_options(ack): ] ack(options=options) ``` -
    \ No newline at end of file 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/_advanced/adapters.md b/docs/english/concepts/adapters.md similarity index 55% rename from docs/_advanced/adapters.md rename to docs/english/concepts/adapters.md index c811fadef..ad43a27da 100644 --- a/docs/_advanced/adapters.md +++ b/docs/english/concepts/adapters.md @@ -1,19 +1,14 @@ ---- -title: Adapters -lang: en -slug: adapters -order: 0 ---- +# Adapters -
    -Adapters are responsible for handling and parsing incoming requests from Slack to conform to `BoltRequest`, then dispatching those requests to your Bolt app. +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. -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..cbd164630 --- /dev/null +++ b/docs/english/concepts/adding-agent-features.md @@ -0,0 +1,746 @@ +--- +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/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 & AI Apps** 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 agents import Runner +from slack_bolt import BoltContext, Say, SayStream, SetStatus +from slack_sdk import WebClient + +from agent import CaseyDeps, casey_agent +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: + await client.reactions_add( + channel=channel_id, + timestamp=event["ts"], + name="eyes", + ) + + ... +``` + + + + + +:::tip[Using the Assistant side panel] +The Assistant side panel requires additional setup. See the [Assistant class guide](/tools/bolt-python/concepts/assistant-class). +::: + + +```py +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 neither a `channel_id` or `thread_ts` can be sourced, then 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, casey_agent, get_model +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"], + ) + result = casey_agent.run_sync( + cleaned_text, + model=get_model(), + deps=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"], + ) + 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}") + await 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 agents import Runner +from slack_bolt import BoltContext, Say, SayStream, SetStatus +from slack_sdk import WebClient + +from agent import CaseyDeps, casey_agent +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"], + ) + result = Runner.run_sync(casey_agent, input=input_items, context=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/_basic/publishing_views.md b/docs/english/concepts/app-home.md similarity index 50% rename from docs/_basic/publishing_views.md rename to docs/english/concepts/app-home.md index 38d4e793d..f4f15337f 100644 --- a/docs/_basic/publishing_views.md +++ b/docs/english/concepts/app-home.md @@ -1,18 +1,13 @@ ---- -title: Publishing views to App Home -lang: en -slug: app-home -order: 13 ---- +# 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 to learn the available listener arguments. ```python @app.event("app_home_opened") def update_home_tab(client, event, logger): @@ -36,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 ." } } ] @@ -44,5 +39,4 @@ def update_home_tab(client, event, logger): ) except Exception as e: logger.error(f"Error publishing home tab: {e}") -``` -
    \ No newline at end of file +``` \ No newline at end of file diff --git a/docs/_advanced/async.md b/docs/english/concepts/async.md similarity index 75% rename from docs/_advanced/async.md rename to docs/english/concepts/async.md index fddf82f99..e6ae28fc6 100644 --- a/docs/_advanced/async.md +++ b/docs/english/concepts/async.md @@ -1,15 +1,8 @@ ---- -title: Using async (asyncio) -lang: en -slug: async -order: 2 ---- +# 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 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](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`). -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 @@ -29,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 +36,6 @@ pip install slack_bolt sanic uvicorn # Save the source as async_app.py uvicorn async_app:api --reload --port 3000 --log-level debug ``` -
    ```python from slack_bolt.async_app import AsyncApp @@ -77,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/_basic/authenticating_oauth.md b/docs/english/concepts/authenticating-oauth.md similarity index 85% rename from docs/_basic/authenticating_oauth.md rename to docs/english/concepts/authenticating-oauth.md index ab42beefc..4ce9b205b 100644 --- a/docs/_basic/authenticating_oauth.md +++ b/docs/english/concepts/authenticating-oauth.md @@ -1,23 +1,14 @@ ---- -title: Authenticating with OAuth -lang: en -slug: authenticating-oauth -order: 15 ---- +# 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://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**. - -To learn more about the OAuth installation flow with Slack, [read the API documentation](https://api.slack.com/authentication/oauth-v2). +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](/authentication/installing-with-oauth). ```python import os @@ -40,12 +31,8 @@ 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: - `install_path`: Override default path for "Add to Slack" button @@ -54,8 +41,6 @@ You can override the default OAuth using `oauth_settings`, which can be passed i - `state_store`: Provide a custom state store instead of using the built in `FileOAuthStateStore` - `installation_store`: Provide a custom installation store instead of the built-in `FileInstallationStore` -
    - ```python from slack_bolt.oauth.callback_options import CallbackOptions, SuccessArgs, FailureArgs from slack_bolt.response import BoltResponse @@ -98,6 +83,4 @@ app = App( callback_options=callback_options, ), ) -``` - -
    +``` \ No newline at end of file diff --git a/docs/_advanced/authorization.md b/docs/english/concepts/authorization.md similarity index 86% rename from docs/_advanced/authorization.md rename to docs/english/concepts/authorization.md index f81db0ce1..f6a258491 100644 --- a/docs/_advanced/authorization.md +++ b/docs/english/concepts/authorization.md @@ -1,14 +1,8 @@ ---- -title: Authorization -lang: en -slug: authorization -order: 5 ---- +# 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](#authenticating-oauth) for details. +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](/tools/bolt-python/concepts/authenticating-oauth) for details. 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. @@ -17,7 +11,8 @@ For a more custom solution, you can set the `authorize` parameter to a function - **`bot_user_id`** and **`bot_id`**, if using a `bot_token`. - **`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 @@ -48,8 +43,8 @@ def authorize(enterprise_id, team_id, logger): # You can implement your own logic to fetch token here for team in installations: # enterprise_id doesn't exist for some teams - is_valid_enterprise = True if (("enterprise_id" not in team) or (enterprise_id == team["enterprise_id"])) else False - if ((is_valid_enterprise == True) and (team["team_id"] == team_id)): + is_valid_enterprise = "enterprise_id" not in team or enterprise_id == team["enterprise_id"] + if is_valid_enterprise and team["team_id"] == team_id: # Return an instance of AuthorizeResult # If you don't store bot_id and bot_user_id, could also call `from_auth_test_response` with your bot_token to automatically fetch them return AuthorizeResult( diff --git a/docs/_basic/listening_responding_commands.md b/docs/english/concepts/commands.md similarity index 68% rename from docs/_basic/listening_responding_commands.md rename to docs/english/concepts/commands.md index 1d991b5bf..cd772c57b 100644 --- a/docs/_basic/listening_responding_commands.md +++ b/docs/english/concepts/commands.md @@ -1,24 +1,17 @@ ---- -title: Listening and responding to commands -lang: en -slug: commands -order: 9 ---- - -
    +# 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](#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://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html) to learn the available listener arguments. + +## Example -
    -Refer to the module document to learn the available listener arguments. ```python # The echo command simply echoes on command @app.command("/echo") @@ -27,4 +20,3 @@ def repeat_text(ack, respond, command): ack() respond(f"{command['text']}") ``` -
    \ No newline at end of file diff --git a/docs/_advanced/context.md b/docs/english/concepts/context.md similarity index 95% rename from docs/_advanced/context.md rename to docs/english/concepts/context.md index 574f1b0ca..46684ea28 100644 --- a/docs/_advanced/context.md +++ b/docs/english/concepts/context.md @@ -1,15 +1,10 @@ ---- -title: Adding context -lang: en -slug: context -order: 9 ---- +# 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/_advanced/custom_adapters.md b/docs/english/concepts/custom-adapters.md similarity index 91% rename from docs/_advanced/custom_adapters.md rename to docs/english/concepts/custom-adapters.md index f89a1ee46..21f7f33e0 100644 --- a/docs/_advanced/custom_adapters.md +++ b/docs/english/concepts/custom-adapters.md @@ -1,12 +1,6 @@ ---- -title: Custom adapters -lang: en -slug: custom-adapters -order: 1 ---- +# Custom adapters -
    -[Adapters](#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,7 +17,8 @@ order: 1 Your adapter will return [an instance of `BoltResponse`](https://github.com/slackapi/bolt-python/blob/main/slack_bolt/response/response.py) from the Bolt app. 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/english/concepts/custom-steps.md b/docs/english/concepts/custom-steps.md new file mode 100644 index 000000000..720c53421 --- /dev/null +++ b/docs/english/concepts/custom-steps.md @@ -0,0 +1,153 @@ +--- +sidebar_label: Custom steps +--- + +# 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://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 +@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](/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](/tools/bolt-python/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](/interactivity/handling-user-interaction). diff --git a/docs/_advanced/errors.md b/docs/english/concepts/errors.md similarity index 85% rename from docs/_advanced/errors.md rename to docs/english/concepts/errors.md index d045331db..7b40adb7f 100644 --- a/docs/_advanced/errors.md +++ b/docs/english/concepts/errors.md @@ -1,15 +1,10 @@ ---- -title: Handling errors -lang: en -slug: errors -order: 3 ---- +# 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/_basic/listening_events.md b/docs/english/concepts/event-listening.md similarity index 50% rename from docs/_basic/listening_events.md rename to docs/english/concepts/event-listening.md index 2a4dc1d28..d7b8e5930 100644 --- a/docs/_basic/listening_events.md +++ b/docs/english/concepts/event-listening.md @@ -1,20 +1,10 @@ ---- -title: Listening to events -lang: en -slug: event-listening -order: 3 ---- +# 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 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") @@ -24,22 +14,14 @@ 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 # Matches all modified messages @app.event({ @@ -49,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 diff --git a/docs/_advanced/global_middleware.md b/docs/english/concepts/global-middleware.md similarity index 76% rename from docs/_advanced/global_middleware.md rename to docs/english/concepts/global-middleware.md index 1f9c6d230..7b7bdb059 100644 --- a/docs/_advanced/global_middleware.md +++ b/docs/english/concepts/global-middleware.md @@ -1,18 +1,13 @@ ---- -title: Global middleware -lang: en -slug: global-middleware -order: 8 ---- - -
    +# 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 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 @app.use def auth_acme(client, context, logger, payload, next): @@ -34,4 +29,4 @@ def auth_acme(client, context, logger, payload, next): # Pass control to the next middleware next() ``` -
    + diff --git a/docs/_advanced/lazy_listener.md b/docs/english/concepts/lazy-listeners.md similarity index 90% rename from docs/_advanced/lazy_listener.md rename to docs/english/concepts/lazy-listeners.md index a7058085b..d775106b9 100644 --- a/docs/_advanced/lazy_listener.md +++ b/docs/english/concepts/lazy-listeners.md @@ -1,11 +1,5 @@ ---- -title: Lazy listeners (FaaS) -lang: en -slug: lazy-listeners -order: 10 ---- - -
    +# 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. Typically when handling actions, commands, shortcuts, options and view submissions, you must acknowledge the request from Slack by calling `ack()` within 3 seconds. Calling `ack()` results in sending an HTTP 200 OK response to Slack, letting Slack know that you're handling the response. We normally encourage you to do this as the very first step in your handler function. @@ -15,7 +9,6 @@ However, when running your app on FaaS or similar runtimes which **do not allow To allow you to still run more time-consuming processes as part of your handler, we've added a lazy listener function mechanism. Rather than acting as a decorator, a lazy listener accepts two keyword args: * `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): @@ -38,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 +50,6 @@ export SLACK_BOT_TOKEN=xoxb-*** echo 'slack_bolt' > requirements.txt lambda deploy --config-file config.yaml --requirements requirements.txt ``` -
    ```python from slack_bolt import App @@ -109,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/_advanced/listener_middleware.md b/docs/english/concepts/listener-middleware.md similarity index 63% rename from docs/_advanced/listener_middleware.md rename to docs/english/concepts/listener-middleware.md index 1331e6f79..dd020373f 100644 --- a/docs/_advanced/listener_middleware.md +++ b/docs/english/concepts/listener-middleware.md @@ -1,25 +1,18 @@ ---- -title: Listener middleware -lang: en -slug: listener-middleware -order: 7 ---- - -
    +# 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 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]) @@ -28,14 +21,13 @@ 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"] ) 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/_advanced/logging.md b/docs/english/concepts/logging.md similarity index 90% rename from docs/_advanced/logging.md rename to docs/english/concepts/logging.md index 9ee44f5fa..599431550 100644 --- a/docs/_advanced/logging.md +++ b/docs/english/concepts/logging.md @@ -1,15 +1,10 @@ ---- -title: Logging -lang: en -slug: logging -order: 4 ---- +# 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/english/concepts/message-listening.md b/docs/english/concepts/message-listening.md new file mode 100644 index 000000000..be6e74678 --- /dev/null +++ b/docs/english/concepts/message-listening.md @@ -0,0 +1,30 @@ +# 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://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html) to learn the available listener arguments.] + +::: + +```python +# This will match any message that contains ๐Ÿ‘‹ +@app.message(":wave:") +def say_hello(message, say): + user = message['user'] + say(f"Hi there, <@{user}>!") +``` + +## Using a regular expression pattern + +The `re.compile()` method can be used instead of a string for more granular matching. + +```python +import re + +@app.message(re.compile("(hi|hello|hey)")) +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 diff --git a/docs/english/concepts/message-sending.md b/docs/english/concepts/message-sending.md new file mode 100644 index 000000000..090503ff2 --- /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 neither a `channel_id` or `thread_ts` can be sourced, then 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/_basic/opening_modals.md b/docs/english/concepts/opening-modals.md similarity index 58% rename from docs/_basic/opening_modals.md rename to docs/english/concepts/opening-modals.md index c42c5fee0..01716f613 100644 --- a/docs/_basic/opening_modals.md +++ b/docs/english/concepts/opening-modals.md @@ -1,22 +1,15 @@ ---- -title: Opening modals -lang: en -slug: opening-modals -order: 10 ---- +# Opening modals -
    +[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. -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 to the built-in client's `views.open` method. +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. -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. +Read more about modal composition in the [API documentation](/surfaces/modals#composing_views). -Read more about modal composition in the API documentation. +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 to learn the available listener arguments. ```python # Listen for a shortcut invocation @app.shortcut("open_modal") @@ -58,4 +51,3 @@ def open_modal(ack, body, client): } ) ``` -
    \ No newline at end of file diff --git a/docs/_basic/listening_responding_options.md b/docs/english/concepts/select-menu-options.md similarity index 67% rename from docs/_basic/listening_responding_options.md rename to docs/english/concepts/select-menu-options.md index 07b9fd12f..8e6cbb9fe 100644 --- a/docs/_basic/listening_responding_options.md +++ b/docs/english/concepts/select-menu-options.md @@ -1,23 +1,18 @@ ---- -title: Listening and responding to options -lang: en -slug: options -order: 14 ---- +# Listening & responding to select menu options -
    -The `options()` method listens for incoming option request payloads from Slack. [Similar to `action()`](#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://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 to learn the available listener arguments. ```python # Example of responding to an external_select options request @app.options("external_action") @@ -36,5 +31,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/_basic/listening_responding_shortcuts.md b/docs/english/concepts/shortcuts.md similarity index 67% rename from docs/_basic/listening_responding_shortcuts.md rename to docs/english/concepts/shortcuts.md index 1952ede27..b28f0b352 100644 --- a/docs/_basic/listening_responding_shortcuts.md +++ b/docs/english/concepts/shortcuts.md @@ -1,28 +1,18 @@ ---- -title: Listening and responding to shortcuts -lang: en -slug: shortcuts -order: 8 ---- +# 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](#creating-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 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") @@ -42,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 ." } }, { @@ -58,17 +48,11 @@ 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' @app.shortcut({"callback_id": "open_modal", "type": "message_action"}) @@ -87,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 ." } }, { @@ -102,6 +86,4 @@ def open_modal(ack, shortcut, client): ] } ) -``` - -
    +``` \ No newline at end of file diff --git a/docs/_basic/socket_mode.md b/docs/english/concepts/socket-mode.md similarity index 72% rename from docs/_basic/socket_mode.md rename to docs/english/concepts/socket-mode.md index 2e23266a9..5156f6e13 100644 --- a/docs/_basic/socket_mode.md +++ b/docs/english/concepts/socket-mode.md @@ -1,12 +1,6 @@ ---- -title: Using Socket Mode -lang: en -slug: socket-mode -order: 16 ---- +# 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. @@ -19,8 +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 @@ -38,16 +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](https://slack.dev/bolt-python/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 @@ -65,6 +52,4 @@ async def main(): if __name__ == "__main__": import asyncio asyncio.run(main()) -``` - -
    +``` \ No newline at end of file diff --git a/docs/_advanced/token_rotation.md b/docs/english/concepts/token-rotation.md similarity index 68% rename from docs/_advanced/token_rotation.md rename to docs/english/concepts/token-rotation.md index 7ae23faf4..96a41bb3c 100644 --- a/docs/_advanced/token_rotation.md +++ b/docs/english/concepts/token-rotation.md @@ -1,16 +1,9 @@ ---- -title: Token rotation -lang: en -slug: token-rotation -order: 6 ---- +# 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](https://slack.dev/bolt-python/concepts#authenticating-oauth) functionality is used. +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](https://api.slack.com/authentication/rotation). -
    +For more information about token rotation, please see the [documentation](/authentication/using-token-rotation). \ No newline at end of file diff --git a/docs/_basic/updating_pushing_modals.md b/docs/english/concepts/updating-pushing-views.md similarity index 55% rename from docs/_basic/updating_pushing_modals.md rename to docs/english/concepts/updating-pushing-views.md index 353745c45..8c05e79c8 100644 --- a/docs/_basic/updating_pushing_modals.md +++ b/docs/english/concepts/updating-pushing-views.md @@ -1,26 +1,18 @@ ---- -title: Updating and pushing views -lang: en -slug: updating-pushing-views -order: 11 ---- +# Updating & pushing views -
    +Modals contain a stack of views. When you call [`views_open`](https://api./reference/methods/views.open/slack.com/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/) -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`. +## The `views_update` method -**`views_update`**
    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`**
    -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. +## The `views_push` method -Learn more about updating and pushing views in our API documentation. +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](/surfaces/modals) -
    -Refer to the module document 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") @@ -52,5 +44,4 @@ def update_modal(ack, body, client): ] } ) -``` -
    \ No newline at end of file +``` \ No newline at end of file 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..ed004dc35 --- /dev/null +++ b/docs/english/concepts/using-the-assistant-class.md @@ -0,0 +1,329 @@ +# 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 & AI Apps feature enabled. + +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 `get` 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 & AI Apps** 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) +* [`setTitle`](https://docs.slack.dev/tools/bolt-python/reference/#slack_bolt.SetTitle) +* [`setStatus`](https://docs.slack.dev/tools/bolt-python/reference/#slack_bolt.SetStatus) + +Within the `setStatus` utility, you can cycle through strings passed into a `loading_messages` array. + +```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 [App Agent Template](https://github.com/slack-samples/bolt-python-assistant-template) repo for you to build from. \ No newline at end of file diff --git a/docs/_basic/listening_modals.md b/docs/english/concepts/view-submissions.md similarity index 68% rename from docs/_basic/listening_modals.md rename to docs/english/concepts/view-submissions.md index 4080b260b..4ff4c2da7 100644 --- a/docs/_basic/listening_modals.md +++ b/docs/english/concepts/view-submissions.md @@ -1,13 +1,6 @@ ---- -title: Listening for view submissions -lang: en -slug: view_submissions -order: 12 ---- - -
    +# 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. @@ -21,10 +14,14 @@ To update a view in response to a `view_submission` event, you may pass a `respo # Update the view on submission @app.view("view_1") def handle_submission(ack, body): + # The build_new_view() method returns a modal view + # To build a modal view, we recommend using Block Kit Builder: + # 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) --- @@ -32,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 @@ -61,10 +58,7 @@ def handle_view_closed(ack, body, logger): logger.info(body) ``` -
    - -
    -Refer to the module document 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") @@ -99,4 +93,3 @@ def handle_submission(ack, body, client, view, logger): except e: logger.exception(f"Failed to post a message {e}") ``` -
    \ No newline at end of file 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..7f06e9d42 --- /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](/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](/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..13adf0a32 --- /dev/null +++ b/docs/english/experiments.md @@ -0,0 +1,30 @@ +# Experiments + +Bolt for Python 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 +* [Agent listener argument](#agent) + +## Agent listener argument {#agent} + +The `agent: BoltAgent` listener argument provides access to AI agent-related features. + +The `BoltAgent` and `AsyncBoltAgent` classes offer a `chat_stream()` method that comes pre-configured with event context defaults: `channel_id`, `thread_ts`, `team_id`, and `user_id` fields. + +The listener argument is wired into the Bolt `kwargs` injection system, so listeners can declare it as a parameter or access it via the `context.agent` property. + +### Example + +```python +from slack_bolt import BoltAgent + +@app.event("app_mention") +def handle_mention(agent: BoltAgent): + stream = agent.chat_stream() + stream.append(markdown_text="Hello!") + stream.stop() +``` diff --git a/docs/english/getting-started.md b/docs/english/getting-started.md new file mode 100644 index 000000000..6964df23b --- /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/building-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](/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/english/index.md b/docs/english/index.md new file mode 100644 index 000000000..212bd9690 --- /dev/null +++ b/docs/english/index.md @@ -0,0 +1,21 @@ +# 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](/tools/bolt-python/getting-started) to set up and run your first Bolt app. + +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 + +These docs have lots of information on Bolt for Python. There's also an in-depth Reference section. Please explore! + +If you otherwise get stuck, we're here to help. The following are the best ways to get assistance working through your issue: + +* [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`. + +## Contributing + +These docs live within the [Bolt-Python](https://github.com/slackapi/bolt-python/) repository and are open source. + +We welcome contributions from everyone! Please check out our +[Contributor's Guide](https://github.com/slackapi/bolt-python/blob/main/.github/contributing.md) for how to contribute in a helpful and collaborative way. \ No newline at end of file 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/english/tutorial/ai-chatbot/1.png b/docs/english/tutorial/ai-chatbot/1.png new file mode 100644 index 000000000..7198bc235 Binary files /dev/null and b/docs/english/tutorial/ai-chatbot/1.png differ diff --git a/docs/english/tutorial/ai-chatbot/2.png b/docs/english/tutorial/ai-chatbot/2.png new file mode 100644 index 000000000..fe29f2407 Binary files /dev/null and b/docs/english/tutorial/ai-chatbot/2.png differ diff --git a/docs/english/tutorial/ai-chatbot/3.png b/docs/english/tutorial/ai-chatbot/3.png new file mode 100644 index 000000000..fbf795ad8 Binary files /dev/null and b/docs/english/tutorial/ai-chatbot/3.png differ diff --git a/docs/english/tutorial/ai-chatbot/4.png b/docs/english/tutorial/ai-chatbot/4.png new file mode 100644 index 000000000..c004fa465 Binary files /dev/null and b/docs/english/tutorial/ai-chatbot/4.png differ diff --git a/docs/english/tutorial/ai-chatbot/5.png b/docs/english/tutorial/ai-chatbot/5.png new file mode 100644 index 000000000..7beede412 Binary files /dev/null and b/docs/english/tutorial/ai-chatbot/5.png differ diff --git a/docs/english/tutorial/ai-chatbot/6.png b/docs/english/tutorial/ai-chatbot/6.png new file mode 100644 index 000000000..e70c9714e Binary files /dev/null and b/docs/english/tutorial/ai-chatbot/6.png differ diff --git a/docs/english/tutorial/ai-chatbot/7.png b/docs/english/tutorial/ai-chatbot/7.png new file mode 100644 index 000000000..9d0b94976 Binary files /dev/null and b/docs/english/tutorial/ai-chatbot/7.png differ diff --git a/docs/english/tutorial/ai-chatbot/8.png b/docs/english/tutorial/ai-chatbot/8.png new file mode 100644 index 000000000..bb502e539 Binary files /dev/null and b/docs/english/tutorial/ai-chatbot/8.png differ diff --git a/docs/english/tutorial/ai-chatbot/ai-chatbot.md b/docs/english/tutorial/ai-chatbot/ai-chatbot.md new file mode 100644 index 000000000..2fcc16e9a --- /dev/null +++ b/docs/english/tutorial/ai-chatbot/ai-chatbot.md @@ -0,0 +1,245 @@ +# 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. + +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} + +You will also need the following: + +- 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. + +#### 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 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: + +```bash +export ANTHROPIC_API_KEY= +``` + + + + +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. + +Once your project and credentials are configured, export environment variables to select from Gemini models: + +```bash +export VERTEX_AI_PROJECT_ID= +export VERTEX_AI_LOCATION= +``` + +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 +export OPENAI_API_KEY= +``` + + + + +## Setting up and running your local project {#configure-project} + + +Start your Python virtual environment: + + + + +```bash +python3 -m venv .venv +source .venv/bin/activate +``` + + + + +```bash +py -m venv .venv +.venv\Scripts\activate +``` + + + + +Install the required dependencies: + +```bash +pip install -r requirements.txt +``` + +Run your app locally: + +```bash +slack run +``` + +If your app is indeed up and running, you'll see a message that says "โšก๏ธ Bolt app is running!" + +## Choosing your provider {#provider} + +Navigate to the Bolty **App Home** and select a provider from the drop-down menu. The options listed will be dependent on which secret keys you added when setting your environment variables. + +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](6.png) + +## Setting up your workflow {#workflow} + +Within your development workspace, open Workflow Builder by clicking on your workspace name and then **Tools > Workflow Builder**. Select **New Workflow** > **Build Workflow**. + +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](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](2.png) + +Under **Then, do these things**, click **Add steps** and complete the following: + +1. Select **Messages** > **Send a message to a person**. +2. Under **Select a member**, choose **The user who joined the channel** from the drop-down menu. +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](3.png) + +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](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](5.png) + +When finished, click **Finish Up**, then click **Publish** to make the workflow available in your workspace. + +## Interacting with Bolty {#interact} + +### 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. + +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](7.png) + +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 +from logging import Logger +from slack_bolt import Complete, Fail, Ack +from slack_sdk import WebClient +from ..listener_utils.listener_constants import SUMMARIZE_CHANNEL_WORKFLOW +from ..listener_utils.parse_conversation import parse_conversation + +""" +Handles the event to summarize a Slack channel's conversation history. +It retrieves the conversation history, parses it, generates a summary using an AI response, +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 +): + ack() + try: + user_context = inputs["user_context"] + channel_id = inputs["channel_id"] + history = client.conversations_history(channel=channel_id, limit=10)["messages"] + conversation = parse_conversation(history) + + summary = get_provider_response(user_context["id"], SUMMARIZE_CHANNEL_WORKFLOW, conversation) + + complete({"user_context": user_context, "response": summary}) + except Exception as e: + logger.exception(e) + fail(e) +``` + +### Asking Bolty a question {#ask-app} + +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. + +![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](/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/english/tutorial/custom-steps-for-jira/1.png b/docs/english/tutorial/custom-steps-for-jira/1.png new file mode 100644 index 000000000..5d8bb0448 Binary files /dev/null and b/docs/english/tutorial/custom-steps-for-jira/1.png differ diff --git a/docs/english/tutorial/custom-steps-for-jira/2.png b/docs/english/tutorial/custom-steps-for-jira/2.png new file mode 100644 index 000000000..67e55c65d Binary files /dev/null and b/docs/english/tutorial/custom-steps-for-jira/2.png differ diff --git a/docs/english/tutorial/custom-steps-for-jira/3.png b/docs/english/tutorial/custom-steps-for-jira/3.png new file mode 100644 index 000000000..76829fcd7 Binary files /dev/null and b/docs/english/tutorial/custom-steps-for-jira/3.png differ diff --git a/docs/english/tutorial/custom-steps-for-jira/4.png b/docs/english/tutorial/custom-steps-for-jira/4.png new file mode 100644 index 000000000..ac4d3e89a Binary files /dev/null and b/docs/english/tutorial/custom-steps-for-jira/4.png differ diff --git a/docs/english/tutorial/custom-steps-for-jira/5.png b/docs/english/tutorial/custom-steps-for-jira/5.png new file mode 100644 index 000000000..c68db2c86 Binary files /dev/null and b/docs/english/tutorial/custom-steps-for-jira/5.png differ diff --git a/docs/english/tutorial/custom-steps-for-jira/6.png b/docs/english/tutorial/custom-steps-for-jira/6.png new file mode 100644 index 000000000..e7cc1f0ca Binary files /dev/null and b/docs/english/tutorial/custom-steps-for-jira/6.png differ diff --git a/docs/english/tutorial/custom-steps-for-jira/7.png b/docs/english/tutorial/custom-steps-for-jira/7.png new file mode 100644 index 000000000..0b10523a3 Binary files /dev/null and b/docs/english/tutorial/custom-steps-for-jira/7.png differ diff --git a/docs/english/tutorial/custom-steps-for-jira/custom-steps-for-jira.md b/docs/english/tutorial/custom-steps-for-jira/custom-steps-for-jira.md new file mode 100644 index 000000000..d74b82b8e --- /dev/null +++ b/docs/english/tutorial/custom-steps-for-jira/custom-steps-for-jira.md @@ -0,0 +1,172 @@ +# Custom steps for JIRA + +In this tutorial, you'll learn how to configure custom steps for use with JIRA. 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 with a custom step using Workflow Builder +4. Create an issue in JIRA using your custom step + +## 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.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. + +## 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, 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 +``` + +4. Review the configuration and click **Create**. +5. You're now in your app configuration's **Basic Information** page. Click **Install App**, then **Install to _your-workspace-name_**, then **Allow** on the screen that follows. + +### Obtaining and storing your environment variables {#environment-variables} + +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`](/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**). + +**For macOS** +```bash +export SLACK_BOT_TOKEN= +export SLACK_APP_TOKEN= +export JIRA_CLIENT_ID= +export JIRA_CLIENT_SECRET= +``` + +**For Windows** +```bash +set SLACK_BOT_TOKEN= +set SLACK_APP_TOKEN= +set JIRA_CLIENT_ID= +set JIRA_CLIENT_SECRET= +``` + +## Setting up and running your local project {#configure-project} + +Clone the starter template onto your machine by running the following command: + +```bash +git clone https://github.com/slack-samples/bolt-python-jira-functions.git +``` + +Change into the new project directory: + +```bash +cd bolt-python-jira-functions +``` + +Start your Python virtual environment: + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + + + + +```bash +python3 -m venv .venv +source .venv/bin/activate +``` + + + + +```bash +py -m venv .venv +.venv\Scripts\activate +``` + + + +Install the required dependencies: + +```bash +pip install -r requirements.txt +``` + +Rename the `.example.env` file to `.env` and replace the values for each of the variables listed in the file: + +``` +JIRA_BASE_URL=https://your-jira-instance.com +SECRET_HEADER_KEY=Your-Header +SECRET_HEADER_VALUE=abc123 +JIRA_CLIENT_ID=abc123 +JIRA_CLIENT_SECRET=abc123 +APP_BASE_URL=https://1234-123-123-12.ngrok-free.app +APP_HOME_PAGE_URL=slack://app?team=YOUR_TEAM_ID&id=YOUR_APP_ID&tab=home +``` + +You could also store the values for your `SLACK_BOT_TOKEN` and `SLACK_APP_TOKEN` here. + +Start your local server: + +```bash +python app.py +``` + +If your app is up and running, you'll see a message noting that the app is starting to receive messages from a new connection. + +## Setting up your workflow in Workflow Builder {#workflow} + +1. Within your development workspace, open Workflow Builder by clicking your workspace name and then selecting **Tools** > **Workflow Builder**. +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](1.png) + +4. Select **Choose an event** under **Start the workflow...**, and then select **From a link in Slack**. Click **Continue**. + +![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](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](4.png) + +7. Click **Publish** to make the workflow available to your workspace. + +## Running your app {#run} + +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](5.png) + +3. Click **Allow** on the screen that appears. + +![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](7.png) + +When finished, you can click the **Disconnect Account** button in the home tab to disconnect your app from your JIRA account. + +## Next steps {#next-steps} + +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](/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..c3c5e2af7 --- /dev/null +++ b/docs/english/tutorial/custom-steps-workflow-builder-existing/custom-steps-workflow-builder-existing.md @@ -0,0 +1,281 @@ +# 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 `package.json` file to reflect version 1.20.0 of Bolt, then run the following command 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'], + message=body['message'], + 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..1dceed45a --- /dev/null +++ b/docs/english/tutorial/custom-steps-workflow-builder-new/custom-steps-workflow-builder-new.md @@ -0,0 +1,356 @@ +# 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 +npm install +``` + +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 `npm start`. 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 +npm start +``` + +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..66dc16198 --- /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(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` requires you include an `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/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/assets/basic-information-page.png b/docs/img/basic-information-page.png similarity index 100% rename from docs/assets/basic-information-page.png rename to docs/img/basic-information-page.png diff --git a/docs/assets/bot-token.png b/docs/img/bot-token.png similarity index 100% rename from docs/assets/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/index.md b/docs/index.md deleted file mode 100644 index 7694fecc9..000000000 --- a/docs/index.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -permalink: /concepts -redirect_from: - - / -layout: default -lang: en ---- diff --git a/docs/_basic/ja_acknowledging_requests.md b/docs/japanese/concepts/acknowledge.md similarity index 67% rename from docs/_basic/ja_acknowledging_requests.md rename to docs/japanese/concepts/acknowledge.md index 24ab1f979..36ba6cba4 100644 --- a/docs/_basic/ja_acknowledging_requests.md +++ b/docs/japanese/concepts/acknowledge.md @@ -1,23 +1,14 @@ ---- -title: ใƒชใ‚ฏใ‚จใ‚นใƒˆใฎ็ขบ่ช -lang: ja-jp -slug: acknowledge -order: 7 ---- - -
    +# ใƒชใ‚ฏใ‚จใ‚นใƒˆใฎ็ขบ่ช ใ‚ขใ‚ฏใ‚ทใƒงใƒณ๏ผˆaction๏ผ‰ใ€ใ‚ณใƒžใƒณใƒ‰๏ผˆcommand๏ผ‰ใ€ใ‚ทใƒงใƒผใƒˆใ‚ซใƒƒใƒˆ๏ผˆshortcut๏ผ‰ใ€ใ‚ชใƒ—ใ‚ทใƒงใƒณ๏ผˆoptions๏ผ‰ใ€ใŠใ‚ˆใณใƒขใƒผใƒ€ใƒซใ‹ใ‚‰ใฎใƒ‡ใƒผใ‚ฟ้€ไฟก๏ผˆview_submission๏ผ‰ใฎๅ„ใƒชใ‚ฏใ‚จใ‚นใƒˆใฏใ€**ๅฟ…ใš** `ack()` ้–ขๆ•ฐใ‚’ไฝฟใฃใฆ็ขบ่ชใ‚’่กŒใ†ๅฟ…่ฆใŒใ‚ใ‚Šใพใ™ใ€‚ใ“ใ‚Œใซใ‚ˆใฃใฆใƒชใ‚ฏใ‚จใ‚นใƒˆใŒๅ—ไฟกใ•ใ‚ŒใŸใ“ใจใŒ Slack ใซ่ช่ญ˜ใ•ใ‚Œใ€Slack ใฎใƒฆใƒผใ‚ถใƒผใ‚คใƒณใ‚ฟใƒผใƒ•ใ‚งใ‚คใ‚นใŒ้ฉๅˆ‡ใซๆ›ดๆ–ฐใ•ใ‚Œใพใ™ใ€‚ -ใƒชใ‚ฏใ‚จใ‚นใƒˆใฎ็จฎ้กžใซใ‚ˆใฃใฆใฏใ€็ขบ่ชใง้€š็Ÿฅๆ–นๆณ•ใŒ็•ฐใชใ‚‹ๅ ดๅˆใŒใ‚ใ‚Šใพใ™ใ€‚ไพ‹ใˆใฐใ€ๅค–้ƒจใƒ‡ใƒผใ‚ฟใ‚ฝใƒผใ‚นใ‚’ไฝฟ็”จใ™ใ‚‹้ธๆŠžใƒกใƒ‹ใƒฅใƒผใฎใ‚ชใƒ—ใ‚ทใƒงใƒณใฎใƒชใ‚ฏใ‚จใ‚นใƒˆใซๅฏพใ™ใ‚‹็ขบ่ชใงใฏใ€้ฉๅˆ‡ใช[ใ‚ชใƒ—ใ‚ทใƒงใƒณ](https://api.slack.com/reference/block-kit/composition-objects#option)ใฎใƒชใ‚นใƒˆใจใจใ‚‚ใซ `ack()` ใ‚’ๅ‘ผใณๅ‡บใ—ใพใ™ใ€‚ใƒขใƒผใƒ€ใƒซใ‹ใ‚‰ใฎใƒ‡ใƒผใ‚ฟ้€ไฟกใซๅฏพใ™ใ‚‹็ขบ่ชใงใฏใ€ `response_action` ใ‚’ๆธกใ™ใ“ใจใง[ใƒขใƒผใƒ€ใƒซใฎๆ›ดๆ–ฐ](#update-views-on-submission)ใชใฉใ‚’่กŒใˆใพใ™ใ€‚ +ใƒชใ‚ฏใ‚จใ‚นใƒˆใฎ็จฎ้กžใซใ‚ˆใฃใฆใฏใ€็ขบ่ชใง้€š็Ÿฅๆ–นๆณ•ใŒ็•ฐใชใ‚‹ๅ ดๅˆใŒใ‚ใ‚Šใพใ™ใ€‚ไพ‹ใˆใฐใ€ๅค–้ƒจใƒ‡ใƒผใ‚ฟใ‚ฝใƒผใ‚นใ‚’ไฝฟ็”จใ™ใ‚‹้ธๆŠžใƒกใƒ‹ใƒฅใƒผใฎใ‚ชใƒ—ใ‚ทใƒงใƒณใฎใƒชใ‚ฏใ‚จใ‚นใƒˆใซๅฏพใ™ใ‚‹็ขบ่ชใงใฏใ€้ฉๅˆ‡ใช[ใ‚ชใƒ—ใ‚ทใƒงใƒณ](/reference/block-kit/composition-objects/option-object)ใฎใƒชใ‚นใƒˆใจใจใ‚‚ใซ `ack()` ใ‚’ๅ‘ผใณๅ‡บใ—ใพใ™ใ€‚ใƒขใƒผใƒ€ใƒซใ‹ใ‚‰ใฎใƒ‡ใƒผใ‚ฟ้€ไฟกใซๅฏพใ™ใ‚‹็ขบ่ชใงใฏใ€ `response_action` ใ‚’ๆธกใ™ใ“ใจใง[ใƒขใƒผใƒ€ใƒซใฎๆ›ดๆ–ฐ](/tools/bolt-python/concepts/view-submissions)ใชใฉใ‚’่กŒใˆใพใ™ใ€‚ ็ขบ่ชใพใงใฎ็Œถไบˆใฏ 3 ็ง’ใ—ใ‹ใชใ„ใŸใ‚ใ€ๆ–ฐใ—ใ„ใƒกใƒƒใ‚ปใƒผใ‚ธใฎ้€ไฟกใ‚„ใƒ‡ใƒผใ‚ฟใƒ™ใƒผใ‚นใ‹ใ‚‰ใฎๆƒ…ๅ ฑใฎๅ–ๅพ—ใจใ„ใฃใŸๆ™‚้–“ใฎใ‹ใ‹ใ‚‹ๅ‡ฆ็†ใฏใ€`ack()` ใ‚’ๅ‘ผใณๅ‡บใ—ใŸๅพŒใง่กŒใ†ใ“ใจใ‚’ใŠใ™ใ™ใ‚ใ—ใพใ™ใ€‚ - FaaS / serverless ็’ฐๅขƒใ‚’ไฝฟใ†ๅ ดๅˆใ€ `ack()` ใ™ใ‚‹ใ‚ฟใ‚คใƒŸใƒณใ‚ฐใŒ็•ฐใชใ‚Šใพใ™ใ€‚ ใ“ใ‚Œใซ้–ขใ™ใ‚‹่ฉณ็ดฐใฏ [Lazy listeners (FaaS)](#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") @@ -34,4 +25,3 @@ def show_menu_options(ack): ] ack(options=options) ``` -
    \ No newline at end of file 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/_advanced/ja_adapters.md b/docs/japanese/concepts/adapters.md similarity index 52% rename from docs/_advanced/ja_adapters.md rename to docs/japanese/concepts/adapters.md index cdd840288..6ed804c26 100644 --- a/docs/_advanced/ja_adapters.md +++ b/docs/japanese/concepts/adapters.md @@ -1,19 +1,12 @@ ---- -title: ใ‚ขใƒ€ใƒ—ใ‚ฟใƒผ -lang: ja-jp -slug: adapters -order: 0 ---- +# ใ‚ขใƒ€ใƒ—ใ‚ฟใƒผ -
    -ใ‚ขใƒ€ใƒ—ใ‚ฟใƒผใฏ 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 @@ -42,4 +35,4 @@ handler = SlackRequestHandler(app) def slack_events(): # handler ใฏใ‚ขใƒ—ใƒชใฎใƒ‡ใ‚ฃใ‚นใƒ‘ใƒƒใƒใƒกใ‚ฝใƒƒใƒ‰ใ‚’ๅฎŸ่กŒใ—ใพใ™ return handler.handle(request) -``` +``` \ No newline at end of file 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/_advanced/ja_async.md b/docs/japanese/concepts/async.md similarity index 76% rename from docs/_advanced/ja_async.md rename to docs/japanese/concepts/async.md index aac6a1568..6687dcff5 100644 --- a/docs/_advanced/ja_async.md +++ b/docs/japanese/concepts/async.md @@ -1,15 +1,8 @@ ---- -title: Async๏ผˆasyncio๏ผ‰ใฎไฝฟ็”จ -lang: ja-jp -slug: async -order: 2 ---- +# Async๏ผˆasyncio๏ผ‰ใฎไฝฟ็”จ -
    -้žๅŒๆœŸใƒใƒผใ‚ธใƒงใƒณใฎ Bolt ใ‚’ไฝฟ็”จใ™ใ‚‹ๅ ดๅˆใฏใ€`App` ใฎไปฃใ‚ใ‚Šใซ `AsyncApp` ใ‚คใƒณใ‚นใ‚ฟใƒณใ‚นใ‚’ใ‚คใƒณใƒใƒผใƒˆใ—ใฆๅˆๆœŸๅŒ–ใ—ใพใ™ใ€‚`AsyncApp` ใงใฏ AIOHTTP ใ‚’ไฝฟใฃใฆ API ใƒชใ‚ฏใ‚จใ‚นใƒˆใ‚’่กŒใ†ใŸใ‚ใ€`aiohttp` ใ‚’ใ‚คใƒณใ‚นใƒˆใƒผใƒซใ™ใ‚‹ๅฟ…่ฆใŒใ‚ใ‚Šใพใ™๏ผˆ`requirements.txt` ใซ่ฟฝ่จ˜ใ™ใ‚‹ใ‹ใ€`pip install aiohttp` ใ‚’ๅฎŸ่กŒใ—ใพใ™๏ผ‰ใ€‚ +้žๅŒๆœŸใƒใƒผใ‚ธใƒงใƒณใฎ Bolt ใ‚’ไฝฟ็”จใ™ใ‚‹ๅ ดๅˆใฏใ€`App` ใฎไปฃใ‚ใ‚Šใซ `AsyncApp` ใ‚คใƒณใ‚นใ‚ฟใƒณใ‚นใ‚’ใ‚คใƒณใƒใƒผใƒˆใ—ใฆๅˆๆœŸๅŒ–ใ—ใพใ™ใ€‚`AsyncApp` ใงใฏ [AIOHTTP](https://docs.aiohttp.org/) ใ‚’ไฝฟใฃใฆ API ใƒชใ‚ฏใ‚จใ‚นใƒˆใ‚’่กŒใ†ใŸใ‚ใ€`aiohttp` ใ‚’ใ‚คใƒณใ‚นใƒˆใƒผใƒซใ™ใ‚‹ๅฟ…่ฆใŒใ‚ใ‚Šใพใ™๏ผˆ`requirements.txt` ใซ่ฟฝ่จ˜ใ™ใ‚‹ใ‹ใ€`pip install aiohttp` ใ‚’ๅฎŸ่กŒใ—ใพใ™๏ผ‰ใ€‚ -้žๅŒๆœŸใƒใƒผใ‚ธใƒงใƒณใฎใƒ—ใƒญใ‚ธใ‚งใ‚ฏใƒˆใฎใ‚ตใƒณใƒ—ใƒซใฏใ€ใƒชใƒใ‚ธใƒˆใƒชใฎ `examples` ใƒ•ใ‚ฉใƒซใƒ€ใซใ‚ใ‚Šใพใ™ใ€‚ -
    +้žๅŒๆœŸใƒใƒผใ‚ธใƒงใƒณใฎใƒ—ใƒญใ‚ธใ‚งใ‚ฏใƒˆใฎใ‚ตใƒณใƒ—ใƒซใฏใ€ใƒชใƒใ‚ธใƒˆใƒชใฎ [`examples` ใƒ•ใ‚ฉใƒซใƒ€](https://github.com/slackapi/bolt-python/tree/main/examples)ใซใ‚ใ‚Šใพใ™ใ€‚ ```python # aiohttp ใฎใ‚คใƒณใ‚นใƒˆใƒผใƒซใŒๅฟ…่ฆใงใ™ @@ -29,12 +22,7 @@ if __name__ == "__main__": app.start(3000) ``` -
    - -

    ไป–ใฎใƒ•ใƒฌใƒผใƒ ใƒฏใƒผใ‚ฏใ‚’ไฝฟ็”จใ™ใ‚‹

    -
    - -
    +## ไป–ใฎใƒ•ใƒฌใƒผใƒ ใƒฏใƒผใ‚ฏใ‚’ไฝฟ็”จใ™ใ‚‹ `AsyncApp#start()` ใงใฏๅ†…้ƒจ็š„ใซ [`AIOHTTP`](https://docs.aiohttp.org/) ใฎWebใ‚ตใƒผใƒใƒผใŒๅฎŸ่ฃ…ใ•ใ‚Œใฆใ„ใพใ™ใ€‚ๅฟ…่ฆใซๅฟœใ˜ใฆใ€ๅ—ไฟกใƒชใ‚ฏใ‚จใ‚นใƒˆใฎๅ‡ฆ็†ใซ `AIOHTTP` ไปฅๅค–ใฎใƒ•ใƒฌใƒผใƒ ใƒฏใƒผใ‚ฏใ‚’ไฝฟ็”จใ™ใ‚‹ใ“ใจใŒใงใใพใ™ใ€‚ @@ -48,7 +36,6 @@ pip install slack_bolt sanic uvicorn # ใ‚ฝใƒผใ‚นใƒ•ใ‚กใ‚คใƒซใ‚’ async_app.py ใจใ—ใฆไฟๅญ˜ใ—ใพใ™ uvicorn async_app:api --reload --port 3000 --log-level debug ``` -
    ```python from slack_bolt.async_app import AsyncApp @@ -77,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/_basic/ja_authenticating_oauth.md b/docs/japanese/concepts/authenticating-oauth.md similarity index 85% rename from docs/_basic/ja_authenticating_oauth.md rename to docs/japanese/concepts/authenticating-oauth.md index 2aca09500..e09443d64 100644 --- a/docs/_basic/ja_authenticating_oauth.md +++ b/docs/japanese/concepts/authenticating-oauth.md @@ -1,23 +1,14 @@ ---- -title: OAuth ใ‚’ไฝฟใฃใŸ่ช่จผ -lang: ja-jp -slug: authenticating-oauth -order: 15 ---- +# OAuth ใ‚’ไฝฟใฃใŸ่ช่จผ -
    - -Slack ใ‚ขใƒ—ใƒชใ‚’่ค‡ๆ•ฐใฎใƒฏใƒผใ‚ฏใ‚นใƒšใƒผใ‚นใซใ‚คใƒณใ‚นใƒˆใƒผใƒซใงใใ‚‹ใ‚ˆใ†ใซใ™ใ‚‹ใŸใ‚ใซใฏใ€OAuth ใƒ•ใƒญใƒผใ‚’ๅฎŸ่ฃ…ใ—ใŸไธŠใงใ€ใ‚ขใ‚ฏใ‚ปใ‚นใƒˆใƒผใ‚ฏใƒณใชใฉใฎใ‚คใƒณใ‚นใƒˆใƒผใƒซใซ้–ขใ™ใ‚‹ๆƒ…ๅ ฑใ‚’ใ‚ปใ‚ญใƒฅใ‚ขใชๆ–นๆณ•ใงไฟๅญ˜ใ™ใ‚‹ๅฟ…่ฆใŒใ‚ใ‚Šใพใ™ใ€‚ใ‚ขใƒ—ใƒชใ‚’ๅˆๆœŸๅŒ–ใ™ใ‚‹้š›ใซ `client_id`ใ€`client_secret`ใ€`scopes`ใ€`installation_store`ใ€`state_store` ใ‚’ๆŒ‡ๅฎšใ™ใ‚‹ใ“ใจใงใ€OAuth ใฎใ‚จใƒณใƒ‰ใƒใ‚คใƒณใƒˆใฎใƒซใƒผใƒˆๆƒ…ๅ ฑใ‚„ stateใƒ‘ใƒฉใƒกใƒผใ‚ฟใƒผใฎๆคœ่จผใ‚’Bolt for Python ใซใƒใƒณใƒ‰ใƒชใƒณใ‚ฐใ•ใ›ใ‚‹ใ“ใจใŒใงใใพใ™ใ€‚ใ‚ซใ‚นใ‚ฟใƒ ใฎใ‚ขใƒ€ใƒ—ใ‚ฟใƒผใ‚’ๅฎŸ่ฃ…ใ™ใ‚‹ๅ ดๅˆใฏใ€SDK ใŒๆไพ›ใ™ใ‚‹็ต„ใฟ่พผใฟใฎ[OAuth ใƒฉใ‚คใƒ–ใƒฉใƒช](https://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**ใ€ใงๆœ‰ๅŠนๅŒ–ใงใใพใ™ใ€‚ - -Slack ใงใฎ OAuth ใ‚’ไฝฟใฃใŸใ‚คใƒณใ‚นใƒˆใƒผใƒซใƒ•ใƒญใƒผใซใคใ„ใฆ่ฉณใ—ใใฏใ€[API ใƒ‰ใ‚ญใƒฅใƒกใƒณใƒˆใ‚’ๅ‚็…งใ—ใฆใใ ใ•ใ„](https://api.slack.com/authentication/oauth-v2)ใ€‚ +ใƒใƒผใ‚ธใƒงใƒณ 1.1.0 ไปฅ้™ใฎ Bolt for Python ใงใฏใ€[OrG ๅ…จไฝ“ใธใฎใ‚คใƒณใ‚นใƒˆใƒผใƒซ](/enterprise)ใŒใƒ‡ใƒ•ใ‚ฉใƒซใƒˆใงใ‚ตใƒใƒผใƒˆใ•ใ‚Œใฆใ„ใพใ™ใ€‚OrG ๅ…จไฝ“ใธใฎใ‚คใƒณใ‚นใƒˆใƒผใƒซใฏใ€ใ‚ขใƒ—ใƒชใฎ่จญๅฎšใฎใ€Œ**Org Level Apps**ใ€ใงๆœ‰ๅŠนๅŒ–ใงใใพใ™ใ€‚ -
    +Slack ใงใฎ OAuth ใ‚’ไฝฟใฃใŸใ‚คใƒณใ‚นใƒˆใƒผใƒซใƒ•ใƒญใƒผใซใคใ„ใฆ่ฉณใ—ใใฏใ€[API ใƒ‰ใ‚ญใƒฅใƒกใƒณใƒˆใ‚’ๅ‚็…งใ—ใฆใใ ใ•ใ„](/authentication/installing-with-oauth)ใ€‚ ```python import os @@ -40,12 +31,8 @@ app = App( ) ``` -
    - -

    OAuth ใƒ‡ใƒ•ใ‚ฉใƒซใƒˆ่จญๅฎšใ‚’ใ‚ซใ‚นใ‚ฟใƒžใ‚คใ‚บ

    -
    +## OAuth ใƒ‡ใƒ•ใ‚ฉใƒซใƒˆ่จญๅฎšใ‚’ใ‚ซใ‚นใ‚ฟใƒžใ‚คใ‚บ -
    `oauth_settings` ใ‚’ไฝฟใฃใฆ OAuth ใƒขใ‚ธใƒฅใƒผใƒซใฎใƒ‡ใƒ•ใ‚ฉใƒซใƒˆ่จญๅฎšใ‚’ไธŠๆ›ธใใ™ใ‚‹ใ“ใจใŒใงใใพใ™ใ€‚ใ“ใฎใ‚ซใ‚นใ‚ฟใƒžใ‚คใ‚บใ•ใ‚ŒใŸ่จญๅฎšใฏ App ใฎๅˆๆœŸๅŒ–ๆ™‚ใซๆธกใ—ใพใ™ใ€‚ไปฅไธ‹ใฎๆƒ…ๅ ฑใ‚’ๅค‰ๆ›ดๅฏ่ƒฝใงใ™: - `install_path` : ใ€ŒAdd to Slackใ€ใƒœใ‚ฟใƒณใฎใƒ‡ใƒ•ใ‚ฉใƒซใƒˆใฎใƒ‘ใ‚นใ‚’ไธŠๆ›ธใใ™ใ‚‹ใŸใ‚ใซไฝฟ็”จ @@ -54,8 +41,6 @@ app = App( - `state_store` : ็ต„ใฟ่พผใฟใฎ `FileOAuthStateStore` ใซไปฃใ‚ใ‚‹ใ€ใ‚ซใ‚นใ‚ฟใƒ ใฎ stateใซ้–ขใ™ใ‚‹ใƒ‡ใƒผใ‚ฟใ‚นใƒˆใ‚ขใ‚’ๆŒ‡ๅฎšใ™ใ‚‹ใŸใ‚ใซไฝฟ็”จ - `installation_store` : ็ต„ใฟ่พผใฟใฎ `FileInstallationStore` ใซไปฃใ‚ใ‚‹ใ€ใ‚ซใ‚นใ‚ฟใƒ ใฎใƒ‡ใƒผใ‚ฟใ‚นใƒˆใ‚ขใ‚’ๆŒ‡ๅฎšใ™ใ‚‹ใŸใ‚ใซไฝฟ็”จ -
    - ```python from slack_bolt.oauth.callback_options import CallbackOptions, SuccessArgs, FailureArgs from slack_bolt.response import BoltResponse @@ -98,6 +83,4 @@ app = App( callback_options=callback_options, ), ) -``` - -
    +``` \ No newline at end of file diff --git a/docs/_advanced/ja_authorization.md b/docs/japanese/concepts/authorization.md similarity index 88% rename from docs/_advanced/ja_authorization.md rename to docs/japanese/concepts/authorization.md index 98f6676fc..b6a14b30a 100644 --- a/docs/_advanced/ja_authorization.md +++ b/docs/japanese/concepts/authorization.md @@ -1,26 +1,18 @@ ---- -title: ่ชๅฏ๏ผˆAuthorization๏ผ‰ -lang: ja-jp -slug: authorization -order: 5 ---- +# ่ชๅฏ๏ผˆAuthorization๏ผ‰ -
    ่ชๅฏ๏ผˆAuthorization๏ผ‰ใฏใ€Slack ใ‹ใ‚‰ใฎๅ—ไฟกใƒชใ‚ฏใ‚จใ‚นใƒˆใ‚’ๅ‡ฆ็†ใ™ใ‚‹ใซใ‚ใŸใฃใฆใ€ใฉใฎใ‚ˆใ†ใชSlack ใ‚ฏใƒฌใƒ‡ใƒณใ‚ทใƒฃใƒซ (ใƒœใƒƒใƒˆใƒˆใƒผใ‚ฏใƒณใชใฉ) ใ‚’ไฝฟ็”จๅฏ่ƒฝใซใ™ใ‚‹ใ‹ใ‚’ๆฑบๅฎšใ™ใ‚‹ใƒ—ใƒญใ‚ปใ‚นใงใ™ใ€‚ -ๅ˜ไธ€ใฎใƒฏใƒผใ‚ฏใ‚นใƒšใƒผใ‚นใซใ‚คใƒณใ‚นใƒˆใƒผใƒซใ•ใ‚Œใ‚‹ใ‚ขใƒ—ใƒชใงใฏใ€`token` ใƒ‘ใƒฉใƒกใƒผใ‚ฟใƒผใ‚’ไฝฟใฃใฆ `App` ใฎใ‚ณใƒณใ‚นใƒˆใƒฉใ‚ฏใ‚ฟใƒผใซใƒœใƒƒใƒˆใƒˆใƒผใ‚ฏใƒณใ‚’ๆธกใ™ใจใ„ใ†ใ€ใ‚ทใƒณใƒ—ใƒซใชๆ–นๆณ•ใŒไฝฟใˆใพใ™ใ€‚ใใ‚Œใซๅฏพใ—ใฆใ€่ค‡ๆ•ฐใฎใƒฏใƒผใ‚ฏใ‚นใƒšใƒผใ‚นใซใ‚คใƒณใ‚นใƒˆใƒผใƒซใ•ใ‚Œใ‚‹ใ‚ขใƒ—ใƒชใงใฏใ€ๆฌกใฎ 2 ใคใฎๆ–นๆณ•ใฎใ„ใšใ‚Œใ‹ใ‚’ไฝฟ็”จใ™ใ‚‹ๅฟ…่ฆใŒใ‚ใ‚Šใพใ™ใ€‚็ฐกๅ˜ใชใฎใฏใ€็ต„ใฟ่พผใฟใฎ OAuth ใ‚ตใƒใƒผใƒˆใ‚’ไฝฟ็”จใ™ใ‚‹ๆ–นๆณ•ใงใ™ใ€‚OAuth ใ‚ตใƒใƒผใƒˆใฏใ€OAuth ใƒ•ใƒญใƒผ็”จใฎURLใฎใ‚ปใƒƒใƒˆใ‚ขใƒƒใƒ—ใจstateใฎๆคœ่จผใ‚’่กŒใ„ใพใ™ใ€‚่ฉณ็ดฐใฏใ€Œ[OAuth ใ‚’ไฝฟใฃใŸ่ช่จผ](#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)ใซใฏใ€ใฉใฎใƒฆใƒผใ‚ถใƒผใŒใฉใ“ใง็™บ็”Ÿใ•ใ›ใŸใƒชใ‚ฏใ‚จใ‚นใƒˆใ‹ใ‚’็คบใ™ๆƒ…ๅ ฑใŒๅซใพใ‚Œใพใ™ใ€‚ `AuthorizeResult` ใซใฏใ€ใ„ใใคใ‹็‰นๅฎšใฎใƒ—ใƒญใƒ‘ใƒ†ใ‚ฃใ‚’ๆŒ‡ๅฎšใ™ใ‚‹ๅฟ…่ฆใŒใ‚ใ‚Šใ€ใ„ใšใ‚Œใ‚‚ `str` ๅž‹ใงใ™ใ€‚ - - **`bot_token`**๏ผˆxoxb๏ผ‰*ใพใŸใฏ* **`user_token`**๏ผˆxoxp๏ผ‰: ใฉใกใ‚‰ใ‹ไธ€ๆ–นใŒ**ๅฟ…้ ˆ**ใงใ™ใ€‚ใปใจใ‚“ใฉใฎใ‚ขใƒ—ใƒชใงใฏใ€ใƒ‡ใƒ•ใ‚ฉใƒซใƒˆใฎ `bot_token` ใ‚’ไฝฟ็”จใ™ใ‚Œใฐใ‚ˆใ„ใงใ—ใ‚‡ใ†ใ€‚ใƒˆใƒผใ‚ฏใƒณใ‚’ๆธกใ™ใ“ใจใงใ€`say()` ใชใฉใฎ็ต„ใฟ่พผใฟใฎ้–ขๆ•ฐใ‚’ๆฉŸ่ƒฝใ•ใ›ใ‚‹ใ“ใจใŒใงใใพใ™ใ€‚ - **`bot_user_id`** ใŠใ‚ˆใณ **`bot_id`** : `bot_token` ใ‚’ไฝฟ็”จใ™ใ‚‹ๅ ดๅˆใซๆŒ‡ๅฎšใ—ใพใ™ใ€‚ - **`enterprise_id`** ใŠใ‚ˆใณ **`team_id`** : ใ‚ขใƒ—ใƒชใซๅฑŠใ„ใŸใƒชใ‚ฏใ‚จใ‚นใƒˆใ‹ใ‚‰่ฆ‹ใคใ‘ใ‚‹ใ“ใจใŒใงใใพใ™ใ€‚ - **`user_id`** : `user_token` ใ‚’ไฝฟ็”จใ™ใ‚‹ๅ ดๅˆใซๅฟ…้ ˆใงใ™ใ€‚ -
    ```python import os @@ -51,8 +43,8 @@ def authorize(enterprise_id, team_id, logger): # ใƒˆใƒผใ‚ฏใƒณใ‚’ๅ–ๅพ—ใ™ใ‚‹ใŸใ‚ใฎใ‚ใชใŸใฎใƒญใ‚ธใƒƒใ‚ฏใ‚’ใ“ใ“ใซ่จ˜่ฟฐใ—ใพใ™ for team in installations: # ไธ€้ƒจใฎใƒใƒผใƒ ใฏ enterprise_id ใ‚’ๆŒใŸใชใ„ๅ ดๅˆใŒใ‚ใ‚Šใพใ™ - is_valid_enterprise = True if (("enterprise_id" not in team) or (enterprise_id == team["enterprise_id"])) else False - if ((is_valid_enterprise == True) and (team["team_id"] == team_id)): + is_valid_enterprise = "enterprise_id" not in team or enterprise_id == team["enterprise_id"] + if is_valid_enterprise and team["team_id"] == team_id: # AuthorizeResult ใฎใ‚คใƒณใ‚นใ‚ฟใƒณใ‚นใ‚’่ฟ”ใ—ใพใ™ # bot_id ใจ bot_user_id ใ‚’ไฟๅญ˜ใ—ใฆใ„ใชใ„ๅ ดๅˆใ€bot_token ใ‚’ไฝฟใฃใฆ `from_auth_test_response` ใ‚’ๅ‘ผใณๅ‡บใ™ใจใ€่‡ชๅ‹•็š„ใซๅ–ๅพ—ใงใใพใ™ return AuthorizeResult( diff --git a/docs/_basic/ja_listening_responding_commands.md b/docs/japanese/concepts/commands.md similarity index 68% rename from docs/_basic/ja_listening_responding_commands.md rename to docs/japanese/concepts/commands.md index 1779e2d60..ebb43c4d3 100644 --- a/docs/_basic/ja_listening_responding_commands.md +++ b/docs/japanese/concepts/commands.md @@ -1,24 +1,14 @@ ---- -title: ใ‚ณใƒžใƒณใƒ‰ใฎใƒชใ‚นใƒ‹ใƒณใ‚ฐใจๅฟœ็ญ” -lang: ja-jp -slug: commands -order: 9 ---- - -
    +# ใ‚ณใƒžใƒณใƒ‰ใฎใƒชใ‚นใƒ‹ใƒณใ‚ฐใจๅฟœ็ญ” ใ‚นใƒฉใƒƒใ‚ทใƒฅใ‚ณใƒžใƒณใƒ‰ใŒๅฎŸ่กŒใ•ใ‚ŒใŸใƒชใ‚ฏใ‚จใ‚นใƒˆใ‚’ใƒชใƒƒใ‚นใƒณใ™ใ‚‹ใซใฏใ€`command()` ใƒกใ‚ฝใƒƒใƒ‰ใ‚’ไฝฟ็”จใ—ใพใ™ใ€‚ใ“ใฎใƒกใ‚ฝใƒƒใƒ‰ใงใฏ `str` ๅž‹ใฎ `command_name` ใฎๆŒ‡ๅฎšใŒๅฟ…่ฆใงใ™ใ€‚ ใ‚ณใƒžใƒณใƒ‰ใƒชใ‚ฏใ‚จใ‚นใƒˆใ‚’ใ‚ขใƒ—ใƒชใŒๅ—ไฟกใ—็ขบ่ชใ—ใŸใ“ใจใ‚’ Slack ใซ้€š็Ÿฅใ™ใ‚‹ใŸใ‚ใ€`ack()` ใ‚’ๅ‘ผใณๅ‡บใ™ๅฟ…่ฆใŒใ‚ใ‚Šใพใ™ใ€‚ -ใ‚นใƒฉใƒƒใ‚ทใƒฅใ‚ณใƒžใƒณใƒ‰ใซๅฟœ็ญ”ใ™ใ‚‹ๆ–นๆณ•ใฏ 2 ใคใ‚ใ‚Šใพใ™ใ€‚1 ใค็›ฎใฏ `say()` ใ‚’ไฝฟใ†ๆ–นๆณ•ใงใ€ๆ–‡ๅญ—ๅˆ—ใพใŸใฏ JSON ใฎใƒšใ‚คใƒญใƒผใƒ‰ใ‚’ๆธกใ™ใ“ใจใŒใงใใพใ™ใ€‚2 ใค็›ฎใฏ `respond()` ใ‚’ไฝฟใ†ๆ–นๆณ•ใงใ™ใ€‚ใ“ใ‚Œใฏ `response_url` ใŒใ‚ใ‚‹ๅ ดๅˆใซๆดป่บใ—ใพใ™ใ€‚ใ“ใ‚Œใ‚‰ใฎๆ–นๆณ•ใฏ[ใ‚ขใ‚ฏใ‚ทใƒงใƒณใธใฎๅฟœ็ญ”](#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") @@ -26,5 +16,4 @@ def repeat_text(ack, respond, command): # command ใƒชใ‚ฏใ‚จใ‚นใƒˆใ‚’็ขบ่ช ack() respond(f"{command['text']}") -``` -
    +``` \ No newline at end of file diff --git a/docs/_advanced/ja_context.md b/docs/japanese/concepts/context.md similarity index 95% rename from docs/_advanced/ja_context.md rename to docs/japanese/concepts/context.md index 759c8d50b..13a287728 100644 --- a/docs/_advanced/ja_context.md +++ b/docs/japanese/concepts/context.md @@ -1,15 +1,8 @@ ---- -title: ใ‚ณใƒณใƒ†ใ‚ญใ‚นใƒˆใฎ่ฟฝๅŠ  -lang: ja-jp -slug: context -order: 9 ---- +# ใ‚ณใƒณใƒ†ใ‚ญใ‚นใƒˆใฎ่ฟฝๅŠ  -
    ใ™ในใฆใฎใƒชใ‚นใƒŠใƒผใฏ `context` ใƒ‡ใ‚ฃใ‚ฏใ‚ทใƒงใƒŠใƒชใซใ‚ขใ‚ฏใ‚ปใ‚นใงใใพใ™ใ€‚ใƒชใ‚นใƒŠใƒผใฏใ“ใ‚Œใ‚’ไฝฟใฃใฆใƒชใ‚ฏใ‚จใ‚นใƒˆใฎไป˜ๅŠ ๆƒ…ๅ ฑใ‚’ๅพ—ใ‚‹ใ“ใจใŒใงใใพใ™ใ€‚ๅ—ไฟกใƒชใ‚ฏใ‚จใ‚นใƒˆใซๅซใพใ‚Œใ‚‹ `user_id`ใ€`team_id`ใ€`channel_id`ใ€`enterprise_id` ใชใฉใฎๆƒ…ๅ ฑใฏใ€Bolt ใซใ‚ˆใฃใฆ่‡ชๅ‹•็š„ใซ่จญๅฎšใ•ใ‚Œใพใ™ใ€‚ `context` ใฏๅ˜็ด”ใชใƒ‡ใ‚ฃใ‚ฏใ‚ทใƒงใƒŠใƒชใงใ€ๅค‰ๆ›ดใ‚’็›ดๆŽฅๅŠ ใˆใ‚‹ใ“ใจใ‚‚ใงใใพใ™ใ€‚ -
    ```python # ใƒฆใƒผใ‚ถใƒผID ใ‚’ไฝฟใฃใฆๅค–้ƒจใฎใ‚ทใ‚นใƒ†ใƒ ใ‹ใ‚‰ใ‚ฟใ‚นใ‚ฏใ‚’ๅ–ๅพ—ใ™ใ‚‹ใƒชใ‚นใƒŠใƒผใƒŸใƒ‰ใƒซใ‚ฆใ‚งใ‚ข @@ -69,4 +62,4 @@ def show_tasks(event, client, context): "blocks": context["blocks"] } ) -``` +``` \ No newline at end of file diff --git a/docs/_advanced/ja_custom_adapters.md b/docs/japanese/concepts/custom-adapters.md similarity index 90% rename from docs/_advanced/ja_custom_adapters.md rename to docs/japanese/concepts/custom-adapters.md index 88c6c54b1..584893511 100644 --- a/docs/_advanced/ja_custom_adapters.md +++ b/docs/japanese/concepts/custom-adapters.md @@ -1,12 +1,6 @@ ---- -title: ใ‚ซใ‚นใ‚ฟใƒ ใฎใ‚ขใƒ€ใƒ—ใ‚ฟใƒผ -lang: ja-jp -slug: custom-adapters -order: 1 ---- +# ใ‚ซใ‚นใ‚ฟใƒ ใฎใ‚ขใƒ€ใƒ—ใ‚ฟใƒผ -
    -[ใ‚ขใƒ€ใƒ—ใ‚ฟใƒผ](#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 ใ‚ขใƒ—ใƒชใซๅผ•ใๆธกใ—ใพใ™ใ€‚ @@ -23,7 +17,6 @@ order: 1 ใ‚ขใƒ€ใƒ—ใ‚ฟใƒผใฏใ€Bolt ใ‚ขใƒ—ใƒชใ‹ใ‚‰ใฎ [`BoltResponse` ใฎใ‚คใƒณใ‚นใ‚ฟใƒณใ‚น](https://github.com/slackapi/bolt-python/blob/main/slack_bolt/response/response.py)ใ‚’่ฟ”ใ—ใพใ™ใ€‚ ใ‚ซใ‚นใ‚ฟใƒ ใฎใ‚ขใƒ€ใƒ—ใ‚ฟใƒผใซ้–ข้€ฃใ—ใŸ่ฉณใ—ใ„ใ‚ตใƒณใƒ—ใƒซใซใคใ„ใฆใฏใ€[็ต„ใฟ่พผใฟใฎใ‚ขใƒ€ใƒ—ใ‚ฟใƒผ](https://github.com/slackapi/bolt-python/tree/main/slack_bolt/adapter)ใฎๅฎŸ่ฃ…ใ‚’ๅ‚่€ƒใซใ—ใฆใใ ใ•ใ„ใ€‚ -
    ```python # Flask ใงๅฟ…่ฆใชใƒ‘ใƒƒใ‚ฑใƒผใ‚ธใ‚’ใ‚คใƒณใƒใƒผใƒˆใ—ใพใ™ @@ -69,4 +62,4 @@ class SlackRequestHandler: return to_flask_response(bolt_resp) return make_response("Not Found", 404) -``` +``` \ No newline at end of file diff --git a/docs/_advanced/ja_errors.md b/docs/japanese/concepts/errors.md similarity index 88% rename from docs/_advanced/ja_errors.md rename to docs/japanese/concepts/errors.md index e0d9c61b0..2e8e27f90 100644 --- a/docs/_advanced/ja_errors.md +++ b/docs/japanese/concepts/errors.md @@ -1,15 +1,8 @@ ---- -title: ใ‚จใƒฉใƒผใฎๅ‡ฆ็† -lang: ja-jp -slug: errors -order: 3 ---- +# ใ‚จใƒฉใƒผใฎๅ‡ฆ็† -
    ใƒชใ‚นใƒŠใƒผๅ†…ใงใ‚จใƒฉใƒผใŒ็™บ็”Ÿใ—ใŸๅ ดๅˆใซ try/except ใƒ–ใƒญใƒƒใ‚ฏใ‚’ไฝฟ็”จใ—ใฆ็›ดๆŽฅใ‚จใƒฉใƒผใ‚’ๅ‡ฆ็†ใ™ใ‚‹ใ“ใจใŒใงใใพใ™ใ€‚ใ‚ขใƒ—ใƒชใซ้–ข้€ฃใ™ใ‚‹ใ‚จใƒฉใƒผใฏใ€`BoltError` ๅž‹ใงใ™ใ€‚Slack API ใฎๅ‘ผใณๅ‡บใ—ใซ้–ข้€ฃใ™ใ‚‹ใ‚จใƒฉใƒผใฏใ€`SlackApiError` ๅž‹ใจใชใ‚Šใพใ™ใ€‚ ใƒ‡ใƒ•ใ‚ฉใƒซใƒˆใงใฏใ€ใ™ในใฆใฎๅ‡ฆ็†ใ•ใ‚Œใชใ‹ใฃใŸไพ‹ๅค–ใฎใƒญใ‚ฐใฏใ‚ฐใƒญใƒผใƒใƒซใฎใ‚จใƒฉใƒผใƒใƒณใƒ‰ใƒฉใƒผใซใ‚ˆใฃใฆใ‚ณใƒณใ‚ฝใƒผใƒซใซๅ‡บๅŠ›ใ•ใ‚Œใพใ™ใ€‚ใ‚ฐใƒญใƒผใƒใƒซใฎใ‚จใƒฉใƒผใ‚’้–‹็™บ่€…่‡ช่บซใงๅ‡ฆ็†ใ™ใ‚‹ใซใฏใ€`app.error(fn)` ้–ขๆ•ฐใ‚’ไฝฟใฃใฆใ‚ฐใƒญใƒผใƒใƒซใฎใ‚จใƒฉใƒผใƒใƒณใƒ‰ใƒฉใƒผใ‚’ใ‚ขใƒ—ใƒชใซ่จญๅฎšใ—ใพใ™ใ€‚ -
    ```python @app.error 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/_advanced/ja_global_middleware.md b/docs/japanese/concepts/global-middleware.md similarity index 78% rename from docs/_advanced/ja_global_middleware.md rename to docs/japanese/concepts/global-middleware.md index 7f1e995d8..01ca417ac 100644 --- a/docs/_advanced/ja_global_middleware.md +++ b/docs/japanese/concepts/global-middleware.md @@ -1,18 +1,11 @@ ---- -title: ใ‚ฐใƒญใƒผใƒใƒซใƒŸใƒ‰ใƒซใ‚ฆใ‚งใ‚ข -lang: ja-jp -slug: global-middleware -order: 8 ---- +# ใ‚ฐใƒญใƒผใƒใƒซใƒŸใƒ‰ใƒซใ‚ฆใ‚งใ‚ข -
    ใ‚ฐใƒญใƒผใƒใƒซใƒŸใƒ‰ใƒซใ‚ฆใ‚งใ‚ขใฏใ€ใ™ในใฆใฎๅ—ไฟกใƒชใ‚ฏใ‚จใ‚นใƒˆใซๅฏพใ—ใฆใ€ใƒชใ‚นใƒŠใƒผใƒŸใƒ‰ใƒซใ‚ฆใ‚งใ‚ขใŒๅ‘ผใฐใ‚Œใ‚‹ๅ‰ใซๅฎŸ่กŒใ•ใ‚Œใ‚‹ใ‚‚ใฎใงใ™ใ€‚ใƒŸใƒ‰ใƒซใ‚ฆใ‚งใ‚ข้–ขๆ•ฐใ‚’ `app.use()` ใซๆธกใ™ใ“ใจใงใ€ใ‚ขใƒ—ใƒชใซใฏใ‚ฐใƒญใƒผใƒใƒซใƒŸใƒ‰ใƒซใ‚ฆใ‚งใ‚ขใ‚’ใ„ใใคใงใ‚‚่ฟฝๅŠ ใงใใพใ™ใ€‚ใƒŸใƒ‰ใƒซใ‚ฆใ‚งใ‚ข้–ขๆ•ฐใงๅ—ใ‘ๅ–ใ‚Œใ‚‹ๅผ•ๆ•ฐใฏใƒชใ‚นใƒŠใƒผ้–ขๆ•ฐใจๅŒใ˜ใ‚‚ใฎใซๅŠ ใˆใฆ`next()` ้–ขๆ•ฐใŒใ‚ใ‚Šใพใ™ใ€‚ ใ‚ฐใƒญใƒผใƒใƒซใƒŸใƒ‰ใƒซใ‚ฆใ‚งใ‚ขใงใ‚‚ใƒชใ‚นใƒŠใƒผใƒŸใƒ‰ใƒซใ‚ฆใ‚งใ‚ขใงใ‚‚ใ€ๆฌกใฎใƒŸใƒ‰ใƒซใ‚ฆใ‚งใ‚ขใซๅฎŸ่กŒใƒใ‚งใƒผใƒณใฎๅˆถๅพกใ‚’ใƒชใƒฌใƒผใ™ใ‚‹ใŸใ‚ใซใ€`next()` ใ‚’ๅ‘ผใณๅ‡บใ™ๅฟ…่ฆใŒใ‚ใ‚Šใพใ™ใ€‚ -
    -
    -ๆŒ‡ๅฎšๅฏ่ƒฝใชๅผ•ๆ•ฐใฎไธ€่ฆงใฏใƒขใ‚ธใƒฅใƒผใƒซใƒ‰ใ‚ญใƒฅใƒกใƒณใƒˆใ‚’ๅ‚่€ƒใซใ—ใฆใใ ใ•ใ„ใ€‚ +ๆŒ‡ๅฎšๅฏ่ƒฝใชๅผ•ๆ•ฐใฎไธ€่ฆงใฏ[ใƒขใ‚ธใƒฅใƒผใƒซใƒ‰ใ‚ญใƒฅใƒกใƒณใƒˆ](https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html)ใ‚’ๅ‚่€ƒใซใ—ใฆใใ ใ•ใ„ใ€‚ + ```python @app.use def auth_abc(client, context, logger, payload, next): @@ -33,5 +26,4 @@ def auth_abc(client, context, logger, payload, next): # ๆฌกใฎใƒŸใƒ‰ใƒซใ‚ฆใ‚งใ‚ขใซๅฎŸ่กŒๆจฉใ‚’ๆธกใ—ใพใ™ next() -``` -
    +``` \ No newline at end of file diff --git a/docs/_advanced/ja_lazy_listener.md b/docs/japanese/concepts/lazy-listeners.md similarity index 94% rename from docs/_advanced/ja_lazy_listener.md rename to docs/japanese/concepts/lazy-listeners.md index b472e0930..029f61d99 100644 --- a/docs/_advanced/ja_lazy_listener.md +++ b/docs/japanese/concepts/lazy-listeners.md @@ -1,11 +1,5 @@ ---- -title: Lazy ใƒชใ‚นใƒŠใƒผ๏ผˆFaaS๏ผ‰ -lang: ja-jp -slug: lazy-listeners -order: 10 ---- - -
    +# Lazy ใƒชใ‚นใƒŠใƒผ๏ผˆFaaS + Lazy ใƒชใ‚นใƒŠใƒผ้–ขๆ•ฐใฏใ€FaaS ็’ฐๅขƒใธใฎ Slack ใ‚ขใƒ—ใƒชใฎใƒ‡ใƒ—ใƒญใ‚คใ‚’ๅฎนๆ˜“ใซใ™ใ‚‹ๆฉŸ่ƒฝใงใ™ใ€‚ใ“ใฎๆฉŸ่ƒฝใฏ Bolt for Python ใงใฎใฟๅˆฉ็”จๅฏ่ƒฝใงใ€ไป–ใฎ Bolt ใƒ•ใƒฌใƒผใƒ ใƒฏใƒผใ‚ฏใงใ“ใฎๆฉŸ่ƒฝใซๅฏพๅฟœใ™ใ‚‹ใ“ใจใฏไบˆๅฎšใ—ใฆใ„ใพใ›ใ‚“ใ€‚ ้€šๅธธใ€ใ‚ขใ‚ฏใ‚ทใƒงใƒณ๏ผˆaction๏ผ‰ใ€ใ‚ณใƒžใƒณใƒ‰๏ผˆcommand๏ผ‰ใ€ใ‚ทใƒงใƒผใƒˆใ‚ซใƒƒใƒˆ๏ผˆshortcut๏ผ‰ใ€ใ‚ชใƒ—ใ‚ทใƒงใƒณ๏ผˆoptions๏ผ‰ใ€ใŠใ‚ˆใณใƒขใƒผใƒ€ใƒซใ‹ใ‚‰ใฎใƒ‡ใƒผใ‚ฟ้€ไฟก๏ผˆview_submission๏ผ‰ใ‚’ใƒใƒณใƒ‰ใƒซใ™ใ‚‹ใจใใ€ `ack()` ใ‚’ๅ‘ผใณๅ‡บใ—ใ€Slack ใ‹ใ‚‰ใฎใƒชใ‚ฏใ‚จใ‚นใƒˆใ‚’ 3 ็ง’ไปฅๅ†…ใซ็ขบ่ชใ™ใ‚‹ๅฟ…่ฆใŒใ‚ใ‚Šใพใ™ใ€‚`ack()` ใ‚’ๅ‘ผใณๅ‡บใ™ใจ Slack ใซ HTTP ใ‚นใƒ†ใƒผใ‚ฟใ‚นใŒ 200 OK ใฎๅฟœ็ญ”ใŒ่ฟ”ใ•ใ‚Œใพใ™ใ€‚ใ“ใ†ใ™ใ‚‹ใ“ใจใงใ€ใ‚ขใƒ—ใƒชใŒใƒชใ‚ฏใ‚จใ‚นใƒˆใฎๅฟœ็ญ”ใ‚’ๅ‡ฆ็†ไธญใงใ‚ใ‚‹ใ“ใจใ‚’ Slack ใซไผใˆใ‚‰ใ‚Œใพใ™ใ€‚้€šๅธธใงใ‚ใ‚Œใฐใ€ใ“ใฎ็ขบ่ชๅ‡ฆ็†ใ‚’ๅ‡ฆ็†้–ขๆ•ฐใฎๆœ€ๅˆใฎใ‚นใƒ†ใƒƒใƒ—ใจใ—ใฆ่กŒใ†ใ“ใจใ‚’ๆŽจๅฅจใ—ใฆใ„ใพใ™ใ€‚ @@ -15,7 +9,6 @@ Lazy ใƒชใ‚นใƒŠใƒผ้–ขๆ•ฐใฏใ€FaaS ็’ฐๅขƒใธใฎ Slack ใ‚ขใƒ—ใƒชใฎใƒ‡ใƒ—ใƒญใ‚คใ‚’ ๅ‡ฆ็†้–ขๆ•ฐใฎไธญใงๆ™‚้–“ใฎใ‹ใ‹ใ‚‹ๅ‡ฆ็†ใ‚’ๅฎŸ่กŒใงใใ‚‹ใ‚ˆใ†ใซใ™ใ‚‹ใŸใ‚ใซใ€็งใŸใกใฏ Lazy ใƒชใ‚นใƒŠใƒผใจใ„ใ†้–ขๆ•ฐใ‚’ๅฎŸ่กŒใ™ใ‚‹ไป•็ต„ใฟใ‚’ๅฐŽๅ…ฅใ—ใพใ—ใŸใ€‚Lazy ใƒชใ‚นใƒŠใƒผใฏใ€ใƒ‡ใ‚ณใƒฌใƒผใ‚ฟใƒผใจใ—ใฆๅ‹•ไฝœใ•ใ›ใ‚‹ใฎใงใฏใชใใ€ไปฅไธ‹ใฎ 2 ใคใฎใ‚ญใƒผใƒฏใƒผใƒ‰ๅผ•ๆ•ฐใ‚’ๅ—ใ‘ๅ–ใ‚Šใพใ™ใ€‚ * `ack: Callable`: 3 ็ง’ไปฅๅ†…ใงใฎ `ack()` ใƒกใ‚ฝใƒƒใƒ‰ใฎๅ‘ผใณๅ‡บใ—ใ‚’ๆ‹…ๅฝ“ใ—ใพใ™ใ€‚ * `lazy: List[Callable]` : ใƒชใ‚ฏใ‚จใ‚นใƒˆใซ้–ขใ™ใ‚‹ๆ™‚้–“ใฎใ‹ใ‹ใ‚‹ๅ‡ฆ็†ใฎใƒใƒณใƒ‰ใƒชใƒณใ‚ฐใ‚’ๆ‹…ๅฝ“ใ—ใพใ™ใ€‚Lazy ้–ขๆ•ฐใ‹ใ‚‰ใฏ `ack()` ใซใ‚ขใ‚ฏใ‚ปใ‚นใ™ใ‚‹ใ“ใจใฏใงใใพใ›ใ‚“ใ€‚ -
    ```python def respond_to_slack_within_3_seconds(body, ack): @@ -38,12 +31,8 @@ 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)ใซใฏใปใ‹ใซใ‚‚ใ‚ตใƒณใƒ—ใƒซใŒ็”จๆ„ใ•ใ‚Œใฆใ„ใพใ™ใ€‚ ```bash @@ -61,7 +50,6 @@ export SLACK_BOT_TOKEN=xoxb-*** echo 'slack_bolt' > requirements.txt lambda deploy --config-file config.yaml --requirements requirements.txt ``` -
    ```python from slack_bolt import App @@ -109,5 +97,4 @@ def handler(event, context): } ] } -``` -
    +``` \ No newline at end of file diff --git a/docs/_advanced/ja_listener_middleware.md b/docs/japanese/concepts/listener-middleware.md similarity index 67% rename from docs/_advanced/ja_listener_middleware.md rename to docs/japanese/concepts/listener-middleware.md index 7e67311b6..425ae4ea7 100644 --- a/docs/_advanced/ja_listener_middleware.md +++ b/docs/japanese/concepts/listener-middleware.md @@ -1,25 +1,16 @@ ---- -title: ใƒชใ‚นใƒŠใƒผใƒŸใƒ‰ใƒซใ‚ฆใ‚งใ‚ข -lang: ja-jp -slug: listener-middleware -order: 7 ---- +# ใƒชใ‚นใƒŠใƒผใƒŸใƒ‰ใƒซใ‚ฆใ‚งใ‚ข -
    ใƒชใ‚นใƒŠใƒผใƒŸใƒ‰ใƒซใ‚ฆใ‚งใ‚ขใฏใ€ใใ‚Œใ‚’ๆธกใ—ใŸใƒชใ‚นใƒŠใƒผใงใฎใฟๅฎŸ่กŒใ•ใ‚Œใ‚‹ใƒŸใƒ‰ใƒซใ‚ฆใ‚งใ‚ขใงใ™ใ€‚ใƒชใ‚นใƒŠใƒผใซใฏใ€`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]) @@ -28,14 +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']}") ``` -
    diff --git a/docs/_advanced/ja_logging.md b/docs/japanese/concepts/logging.md similarity index 92% rename from docs/_advanced/ja_logging.md rename to docs/japanese/concepts/logging.md index 91a3f4085..3afa46539 100644 --- a/docs/_advanced/ja_logging.md +++ b/docs/japanese/concepts/logging.md @@ -1,15 +1,8 @@ ---- -title: ใƒญใ‚ฎใƒณใ‚ฐ -lang: ja-jp -slug: logging -order: 4 ---- +# ใƒญใ‚ฎใƒณใ‚ฐ -
    ใƒ‡ใƒ•ใ‚ฉใƒซใƒˆใงใฏใ€ใ‚ขใƒ—ใƒชใ‹ใ‚‰ใฎใƒญใ‚ฐๆƒ…ๅ ฑใฏใ€ๆ—ขๅฎšใฎๅ‡บๅŠ›ๅ…ˆใซๅ‡บๅŠ›ใ•ใ‚Œใพใ™ใ€‚`logging` ใƒขใ‚ธใƒฅใƒผใƒซใ‚’ใ‚คใƒณใƒใƒผใƒˆใ™ใ‚Œใฐใ€`basicConfig()` ใฎ `level` ใƒ‘ใƒฉใƒกใƒผใ‚ฟใƒผใงrootใฎใƒญใ‚ฐใƒฌใƒ™ใƒซใ‚’ๅค‰ๆ›ดใ™ใ‚‹ใ“ใจใŒใงใใพใ™ใ€‚ๆŒ‡ๅฎšใงใใ‚‹ใƒญใ‚ฐใƒฌใƒ™ใƒซใฏใ€้‡่ฆๅบฆใฎไฝŽใ„ๆ–นใ‹ใ‚‰ `debug`ใ€`info`ใ€`warning`ใ€`error`ใ€ใŠใ‚ˆใณ `critical` ใงใ™ใ€‚ ใ‚ฐใƒญใƒผใƒใƒซใฎใ‚ณใƒณใƒ†ใ‚ญใ‚นใƒˆใจใฏๅˆฅใซใ€ๆŒ‡ๅฎšใฎใƒญใ‚ฐใƒฌใƒ™ใƒซใซๅฟœใ˜ใฆๅ˜ไธ€ใฎใƒกใƒƒใ‚ปใƒผใ‚ธใ‚’ใƒญใ‚ฐๅ‡บๅŠ›ใ™ใ‚‹ใ“ใจใ‚‚ใงใใพใ™ใ€‚Bolt ใงใฏ [Python ๆจ™ๆบ–ใฎ logging ใƒขใ‚ธใƒฅใƒผใƒซ](https://docs.python.org/3/library/logging.html)ใŒไฝฟใ‚ใ‚Œใฆใ„ใ‚‹ใŸใ‚ใ€ใ“ใฎใƒขใ‚ธใƒฅใƒผใƒซใŒๆŒใคใ™ในใฆใฎๆฉŸ่ƒฝใ‚’ๅˆฉ็”จใงใใพใ™ใ€‚ -
    ```python import logging @@ -25,4 +18,4 @@ def handle_mention(body, say, logger): # ใ‚ฐใƒญใƒผใƒใƒซใฎ logger ใŒใƒชใ‚นใƒŠใƒผใซๆธกใ•ใ‚Œใฆใ„ใพใ™ logger.debug(body) say(f"{user} mentioned your app") -``` +``` \ No newline at end of file diff --git a/docs/japanese/concepts/message-listening.md b/docs/japanese/concepts/message-listening.md new file mode 100644 index 000000000..dae729b51 --- /dev/null +++ b/docs/japanese/concepts/message-listening.md @@ -0,0 +1,28 @@ +# ใƒกใƒƒใ‚ปใƒผใ‚ธใฎใƒชใ‚นใƒ‹ใƒณใ‚ฐ + +[ใ‚ใชใŸใฎใ‚ขใƒ—ใƒชใŒใ‚ขใ‚ฏใ‚ปใ‚นๆจฉ้™ใ‚’ๆŒใค](/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:") +def say_hello(message, say): + user = message['user'] + say(f"Hi there, <@{user}>!") +``` + +## ๆญฃ่ฆ่กจ็พใƒ‘ใ‚ฟใƒผใƒณใฎๅˆฉ็”จ + +ๆ–‡ๅญ—ๅˆ—ใฎไปฃใ‚ใ‚Šใซ `re.compile()` ใƒกใ‚ฝใƒƒใƒ‰ใ‚’ไฝฟ็”จใ™ใ‚Œใฐใ€ใ‚ˆใ‚Š็ดฐใ‚„ใ‹ใชๆกไปถๆŒ‡ๅฎšใŒใงใใพใ™ใ€‚ + +```python +import re + +@app.message(re.compile("(hi|hello|hey)")) +def say_hello_regex(say, context): + # ๆญฃ่ฆ่กจ็พใฎใƒžใƒƒใƒ็ตๆžœใฏ context.matches ใซ่จญๅฎšใ•ใ‚Œใ‚‹ + greeting = context['matches'][0] + say(f"{greeting}, how are you?") +``` \ No newline at end of file diff --git a/docs/_basic/ja_sending_messages.md b/docs/japanese/concepts/message-sending.md similarity index 66% rename from docs/_basic/ja_sending_messages.md rename to docs/japanese/concepts/message-sending.md index afa30d8d1..ace67051b 100644 --- a/docs/_basic/ja_sending_messages.md +++ b/docs/japanese/concepts/message-sending.md @@ -1,39 +1,22 @@ ---- -title: ใƒกใƒƒใ‚ปใƒผใ‚ธใฎ้€ไฟก -lang: ja-jp -slug: message-sending -order: 2 ---- - -
    +# ใƒกใƒƒใ‚ปใƒผใ‚ธใฎ้€ไฟก ใƒชใ‚นใƒŠใƒผ้–ขๆ•ฐๅ†…ใงใฏใ€้–ข้€ฃใฅใ‘ใ‚‰ใ‚ŒใŸไผš่ฉฑ๏ผˆไพ‹๏ผšใƒชใ‚นใƒŠใƒผๅฎŸ่กŒใฎใƒˆใƒชใ‚ฌใƒผใจใชใฃใŸใ‚คใƒ™ใƒณใƒˆใพใŸใฏใ‚ขใ‚ฏใ‚ทใƒงใƒณใฎ็™บ็”Ÿๅ…ƒใฎไผš่ฉฑ๏ผ‰ใŒใ‚ใ‚‹ๅ ดๅˆใฏใ„ใคใงใ‚‚ `say()` ใ‚’ไฝฟ็”จใงใใพใ™ใ€‚`say()` ใซใฏๆ–‡ๅญ—ๅˆ—ใพใŸใฏ JSON ใƒšใ‚คใƒญใƒผใƒ‰ใ‚’ๆŒ‡ๅฎšใงใใพใ™ใ€‚ๆ–‡ๅญ—ๅˆ—ใฎๅ ดๅˆใ€้€ไฟกใงใใ‚‹ใฎใฏใƒ†ใ‚ญใ‚นใƒˆใƒ™ใƒผใ‚นใฎๅ˜็ด”ใชใƒกใƒƒใ‚ปใƒผใ‚ธใงใ™ใ€‚ใ‚ˆใ‚Š่ค‡้›‘ใชใƒกใƒƒใ‚ปใƒผใ‚ธใ‚’้€ไฟกใ™ใ‚‹ใซใฏ JSON ใƒšใ‚คใƒญใƒผใƒ‰ใ‚’ๆŒ‡ๅฎšใ—ใพใ™ใ€‚ๆŒ‡ๅฎšใ—ใŸใƒกใƒƒใ‚ปใƒผใ‚ธใฎใƒšใ‚คใƒญใƒผใƒ‰ใฏใ€้–ข้€ฃใฅใ‘ใ‚‰ใ‚ŒใŸไผš่ฉฑๅ†…ใฎใƒกใƒƒใ‚ปใƒผใ‚ธใจใ—ใฆ้€ไฟกใ•ใ‚Œใพใ™ใ€‚ -ใƒชใ‚นใƒŠใƒผ้–ขๆ•ฐใฎๅค–ใงใƒกใƒƒใ‚ปใƒผใ‚ธใ‚’้€ไฟกใ—ใŸใ„ๅ ดๅˆใ‚„ใ€ใ‚ˆใ‚Š้ซ˜ๅบฆใชๅ‡ฆ็†๏ผˆ็‰นๅฎšใฎใ‚จใƒฉใƒผใฎๅ‡ฆ็†ใชใฉ๏ผ‰ใ‚’ๅฎŸ่กŒใ—ใŸใ„ๅ ดๅˆใฏใ€[Bolt ใ‚คใƒณใ‚นใ‚ฟใƒณใ‚นใซใ‚ขใ‚ฟใƒƒใƒใ•ใ‚ŒใŸใ‚ฏใƒฉใ‚คใ‚ขใƒณใƒˆ](#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 ใƒ–ใƒญใƒƒใ‚ฏใ‚’้€ไฟก @@ -55,6 +38,4 @@ def show_datepicker(event, say): blocks=blocks, text="Pick a date for me to remind you" ) -``` - -
    +``` \ No newline at end of file diff --git a/docs/_basic/ja_opening_modals.md b/docs/japanese/concepts/opening-modals.md similarity index 61% rename from docs/_basic/ja_opening_modals.md rename to docs/japanese/concepts/opening-modals.md index 8a8333147..65342afb1 100644 --- a/docs/_basic/ja_opening_modals.md +++ b/docs/japanese/concepts/opening-modals.md @@ -1,22 +1,13 @@ ---- -title: ใƒขใƒผใƒ€ใƒซใฎ้–‹ๅง‹ -lang: ja-jp -slug: opening-modals -order: 10 ---- +# ใƒขใƒผใƒ€ใƒซใฎ้–‹ๅง‹ -
    - -ใƒขใƒผใƒ€ใƒซใฏใ€ใƒฆใƒผใ‚ถใƒผใ‹ใ‚‰ใฎใƒ‡ใƒผใ‚ฟใฎๅ…ฅๅŠ›ใ‚’ๅ—ใ‘ไป˜ใ‘ใŸใ‚Šใ€ๅ‹•็š„ใชๆƒ…ๅ ฑใ‚’่กจ็คบใ—ใŸใ‚Šใ™ใ‚‹ใŸใ‚ใฎใ‚คใƒณใ‚ฟใƒผใƒ•ใ‚งใ‚คใ‚นใงใ™ใ€‚็ต„ใฟ่พผใฟใฎ 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 # ใ‚ทใƒงใƒผใƒˆใ‚ซใƒƒใƒˆใฎๅ‘ผใณๅ‡บใ—ใ‚’ใƒชใƒƒใ‚นใƒณ @app.shortcut("open_modal") @@ -57,5 +48,4 @@ def open_modal(ack, body, client): ] } ) -``` -
    \ No newline at end of file +``` \ No newline at end of file diff --git a/docs/_basic/ja_listening_responding_options.md b/docs/japanese/concepts/select-menu-options.md similarity index 67% rename from docs/_basic/ja_listening_responding_options.md rename to docs/japanese/concepts/select-menu-options.md index 03f4a1e5b..4f3a5f357 100644 --- a/docs/_basic/ja_listening_responding_options.md +++ b/docs/japanese/concepts/select-menu-options.md @@ -1,25 +1,16 @@ ---- -title: ใ‚ชใƒ—ใ‚ทใƒงใƒณใฎใƒชใ‚นใƒ‹ใƒณใ‚ฐใจๅฟœ็ญ” -lang: ja-jp -slug: options -order: 14 ---- +# ใ‚ชใƒ—ใ‚ทใƒงใƒณใฎใƒชใ‚นใƒ‹ใƒณใ‚ฐใจๅฟœ็ญ” -
    -`options()` ใƒกใ‚ฝใƒƒใƒ‰ใฏใ€Slack ใ‹ใ‚‰ใฎใ‚ชใƒ—ใ‚ทใƒงใƒณ๏ผˆใ‚ปใƒฌใ‚ฏใƒˆใƒกใƒ‹ใƒฅใƒผๅ†…ใฎๅ‹•็š„ใช้ธๆŠž่‚ข๏ผ‰ใ‚’ใƒชใ‚ฏใ‚จใ‚นใƒˆใ™ใ‚‹ใƒšใ‚คใƒญใƒผใƒ‰ใ‚’ใƒชใƒƒใ‚นใƒณใ—ใพใ™ใ€‚ [`action()` ใจๅŒๆง˜ใซ](#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://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") @@ -39,4 +30,3 @@ def show_options(ack, payload): options = [o for o in options if keyword in o["text"]["text"]] ack(options=options) ``` -
    diff --git a/docs/_basic/ja_listening_responding_shortcuts.md b/docs/japanese/concepts/shortcuts.md similarity index 73% rename from docs/_basic/ja_listening_responding_shortcuts.md rename to docs/japanese/concepts/shortcuts.md index 282756049..39fb10ba8 100644 --- a/docs/_basic/ja_listening_responding_shortcuts.md +++ b/docs/japanese/concepts/shortcuts.md @@ -1,28 +1,18 @@ ---- -title: ใ‚ทใƒงใƒผใƒˆใ‚ซใƒƒใƒˆใฎใƒชใ‚นใƒ‹ใƒณใ‚ฐใจๅฟœ็ญ” -lang: ja-jp -slug: shortcuts -order: 8 ---- +# ใ‚ทใƒงใƒผใƒˆใ‚ซใƒƒใƒˆใฎใƒชใ‚นใƒ‹ใƒณใ‚ฐใจๅฟœ็ญ” -
    - -`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` ใŒๅซใพใ‚Œใพใ™ใ€‚ใ‚ขใƒ—ใƒชใฏใ“ใ‚Œใ‚’ไฝฟใฃใฆใ€ใƒฆใƒผใ‚ถใƒผใซใ‚„ใ‚ใ†ใจใ—ใฆใ„ใ‚‹ใ“ใจใ‚’็ขบ่ชใ™ใ‚‹ใŸใ‚ใฎ[ใƒขใƒผใƒ€ใƒซใ‚’้–‹ใ](#creating-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") @@ -42,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 ." } }, { @@ -58,19 +48,12 @@ def open_modal(ack, shortcut, client): } ) ``` -
    -
    - -

    ๅˆถ็ด„ไป˜ใใ‚ชใƒ–ใ‚ธใ‚งใ‚ฏใƒˆใ‚’ไฝฟ็”จใ—ใŸใ‚ทใƒงใƒผใƒˆใ‚ซใƒƒใƒˆใฎใƒชใ‚นใƒ‹ใƒณใ‚ฐ

    -
    +## ๅˆถ็ด„ไป˜ใใ‚ชใƒ–ใ‚ธใ‚งใ‚ฏใƒˆใ‚’ไฝฟ็”จใ—ใŸใ‚ทใƒงใƒผใƒˆใ‚ซใƒƒใƒˆใฎใƒชใ‚นใƒ‹ใƒณใ‚ฐ -
    ๅˆถ็ด„ไป˜ใใ‚ชใƒ–ใ‚ธใ‚งใ‚ฏใƒˆใ‚’ไฝฟใฃใฆ `callback_id` ใ‚„ `type` ใซใ‚ˆใ‚‹ใƒชใƒƒใ‚นใƒณใงใใพใ™ใ€‚ใ‚ชใƒ–ใ‚ธใ‚งใ‚ฏใƒˆๅ†…ใฎๅˆถ็ด„ใฏ `str` ๅž‹ใพใŸใฏ `re.Pattern` ใ‚ชใƒ–ใ‚ธใ‚งใ‚ฏใƒˆใ‚’ไฝฟ็”จใงใใพใ™ใ€‚ -
    ```python - # ใ“ใฎใƒชใ‚นใƒŠใƒผใŒๅ‘ผใณๅ‡บใ•ใ‚Œใ‚‹ใฎใฏใ€callback_id ใŒ 'open_modal' ใจไธ€่‡ดใ— # ใ‹ใค type ใŒ 'message_action' ใจไธ€่‡ดใ™ใ‚‹ใจใใฎใฟ @app.shortcut({"callback_id": "open_modal", "type": "message_action"}) @@ -89,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 ." } }, { @@ -104,6 +87,4 @@ def open_modal(ack, shortcut, client): ] } ) -``` - -
    +``` \ No newline at end of file diff --git a/docs/_basic/ja_socket_mode.md b/docs/japanese/concepts/socket-mode.md similarity index 80% rename from docs/_basic/ja_socket_mode.md rename to docs/japanese/concepts/socket-mode.md index 2a027882e..92922d2de 100644 --- a/docs/_basic/ja_socket_mode.md +++ b/docs/japanese/concepts/socket-mode.md @@ -1,12 +1,6 @@ ---- -title: ใ‚ฝใ‚ฑใƒƒใƒˆใƒขใƒผใƒ‰ใฎๅˆฉ็”จ -lang: ja-jp -slug: socket-mode -order: 16 ---- +# ใ‚ฝใ‚ฑใƒƒใƒˆใƒขใƒผใƒ‰ใฎๅˆฉ็”จ -
    -[ใ‚ฝใ‚ฑใƒƒใƒˆใƒขใƒผใƒ‰](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 ใซๆŽฅ็ถšใ—ใ€ใใฎใ‚ณใƒใ‚ฏใ‚ทใƒงใƒณ็ตŒ็”ฑใงใƒ‡ใƒผใ‚ฟใ‚’ๅ—ไฟกใ—ใพใ™ใ€‚ใ‚ฝใ‚ฑใƒƒใƒˆใƒขใƒผใƒ‰ใ‚’ไฝฟใ†ๅ‰ใซใ€ใ‚ขใƒ—ใƒชใฎ็ฎก็†็”ป้ขใงใ‚ฝใ‚ฑใƒƒใƒˆใƒขใƒผใƒ‰ใฎๆฉŸ่ƒฝใŒๆœ‰ๅŠนใซใชใฃใฆใ„ใ‚‹ใ“ใจใ‚’็ขบ่ชใ—ใฆใŠใ„ใฆใใ ใ•ใ„ใ€‚ @@ -21,8 +15,6 @@ order: 16 |[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 @@ -40,16 +32,11 @@ if __name__ == "__main__": handler.start() ``` -
    - -

    Async (asyncio) ใฎๅˆฉ็”จ

    -
    +## Async (asyncio) ใฎๅˆฉ็”จ -
    aiohttp ใฎใ‚ˆใ†ใช asyncio ใ‚’ใƒ™ใƒผใ‚นใจใ—ใŸใ‚ขใƒ€ใƒ—ใ‚ฟใƒผใ‚’ไฝฟใ†ๅ ดๅˆใ€ใ‚ขใƒ—ใƒชใ‚ฑใƒผใ‚ทใƒงใƒณๅ…จไฝ“ใŒ asyncio ใฎ async/await ใƒ—ใƒญใ‚ฐใƒฉใƒŸใƒณใ‚ฐใƒขใƒ‡ใƒซใงๅฎŸ่ฃ…ใ•ใ‚Œใฆใ„ใ‚‹ๅฟ…่ฆใŒใ‚ใ‚Šใพใ™ใ€‚`AsyncApp` ใ‚’ๅ‹•ไฝœใ•ใ›ใ‚‹ใŸใ‚ใซใฏ `AsyncSocketModeHandler` ใจใใฎ async ใชใƒŸใƒ‰ใƒซใ‚ฆใ‚งใ‚ขใ‚„ใƒชใ‚นใƒŠใƒผใ‚’ๅˆฉ็”จใ—ใพใ™ใ€‚ -`AsyncApp` ใฎไฝฟใ„ๆ–นใซใคใ„ใฆใฎ่ฉณ็ดฐใฏใ€[Async (asyncio) ใฎๅˆฉ็”จ](https://slack.dev/bolt-python/ja-jp/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 @@ -67,6 +54,4 @@ async def main(): if __name__ == "__main__": import asyncio asyncio.run(main()) -``` - -
    +``` \ No newline at end of file diff --git a/docs/_advanced/ja_token_rotation.md b/docs/japanese/concepts/token-rotation.md similarity index 64% rename from docs/_advanced/ja_token_rotation.md rename to docs/japanese/concepts/token-rotation.md index c8eb1cc56..25a0c735b 100644 --- a/docs/_advanced/ja_token_rotation.md +++ b/docs/japanese/concepts/token-rotation.md @@ -1,16 +1,9 @@ ---- -title: ใƒˆใƒผใ‚ฏใƒณใฎใƒญใƒผใƒ†ใƒผใ‚ทใƒงใƒณ -lang: ja-jp -slug: token-rotation -order: 6 ---- +# ใƒˆใƒผใ‚ฏใƒณใฎใƒญใƒผใƒ†ใƒผใ‚ทใƒงใƒณ -
    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 ๆฉŸ่ƒฝ](https://slack.dev/bolt-python/ja-jp/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)ใ‚’ๅ‚็…งใ—ใฆใใ ใ•ใ„ใ€‚ -
    +ใƒˆใƒผใ‚ฏใƒณใƒญใƒผใƒ†ใƒผใ‚ทใƒงใƒณใซ้–ขใ™ใ‚‹่ฉณ็ดฐใฏ [API ใƒ‰ใ‚ญใƒฅใƒกใƒณใƒˆ](/authentication/using-token-rotation)ใ‚’ๅ‚็…งใ—ใฆใใ ใ•ใ„ใ€‚ \ No newline at end of file diff --git a/docs/_basic/ja_updating_pushing_modals.md b/docs/japanese/concepts/updating-pushing-views.md similarity index 57% rename from docs/_basic/ja_updating_pushing_modals.md rename to docs/japanese/concepts/updating-pushing-views.md index cf5ea4b0a..2bbaf5ae5 100644 --- a/docs/_basic/ja_updating_pushing_modals.md +++ b/docs/japanese/concepts/updating-pushing-views.md @@ -1,26 +1,19 @@ ---- -title: ใƒขใƒผใƒ€ใƒซใฎๆ›ดๆ–ฐใจๅคš้‡่กจ็คบ -lang: ja-jp -slug: updating-pushing-views -order: 11 ---- +# ใƒขใƒผใƒ€ใƒซใฎๆ›ดๆ–ฐใจๅคš้‡่กจ็คบ -
    +ใƒขใƒผใƒ€ใƒซๅ†…ใงใฏใ€่ค‡ๆ•ฐใฎใƒขใƒผใƒ€ใƒซใ‚’ใ‚นใ‚ฟใƒƒใ‚ฏใฎใ‚ˆใ†ใซ้‡ใญใ‚‹ใ“ใจใŒใงใใพใ™ใ€‚[`views_open`](/reference/methods/views.open/) ใจใ„ใ† APIใ‚’ๅ‘ผใณๅ‡บใ™ใจใ€่ฆชใจใชใ‚‹ใจใชใ‚‹ใƒขใƒผใƒ€ใƒซใƒ“ใƒฅใƒผใŒ่ฟฝๅŠ ใ•ใ‚Œใพใ™ใ€‚ใ“ใฎๆœ€ๅˆใฎๅ‘ผใณๅ‡บใ—ใฎๅพŒใ€[`views_update`](/reference/methods/views.update/) ใ‚’ๅ‘ผใณๅ‡บใ™ใ“ใจใงใใฎใƒ“ใƒฅใƒผใ‚’ๆ›ดๆ–ฐใ™ใ‚‹ใ“ใจใŒใงใใพใ™ใ€‚ใพใŸใ€[`views_push`](/reference/methods/views.push) ใ‚’ๅ‘ผใณๅ‡บใ™ใจใ€่ฆชใฎใƒขใƒผใƒ€ใƒซใฎไธŠใซใ•ใ‚‰ใซๆ–ฐใ—ใ„ใƒขใƒผใƒ€ใƒซใƒ“ใƒฅใƒผใ‚’้‡ใญใ‚‹ใ“ใจใ‚‚ใงใใพใ™ใ€‚ -ใƒขใƒผใƒ€ใƒซๅ†…ใงใฏใ€่ค‡ๆ•ฐใฎใƒขใƒผใƒ€ใƒซใ‚’ใ‚นใ‚ฟใƒƒใ‚ฏใฎใ‚ˆใ†ใซ้‡ใญใ‚‹ใ“ใจใŒใงใใพใ™ใ€‚`views_open` ใจใ„ใ† APIใ‚’ๅ‘ผใณๅ‡บใ™ใจใ€่ฆชใจใชใ‚‹ใจใชใ‚‹ใƒขใƒผใƒ€ใƒซใƒ“ใƒฅใƒผใŒ่ฟฝๅŠ ใ•ใ‚Œใพใ™ใ€‚ใ“ใฎๆœ€ๅˆใฎๅ‘ผใณๅ‡บใ—ใฎๅพŒใ€`views_update` ใ‚’ๅ‘ผใณๅ‡บใ™ใ“ใจใงใใฎใƒ“ใƒฅใƒผใ‚’ๆ›ดๆ–ฐใ™ใ‚‹ใ“ใจใŒใงใใพใ™ใ€‚ใพใŸใ€`views_push` ใ‚’ๅ‘ผใณๅ‡บใ™ใจใ€่ฆชใฎใƒขใƒผใƒ€ใƒซใฎไธŠใซใ•ใ‚‰ใซๆ–ฐใ—ใ„ใƒขใƒผใƒ€ใƒซใƒ“ใƒฅใƒผใ‚’้‡ใญใ‚‹ใ“ใจใ‚‚ใงใใพใ™ใ€‚ +**`views_update`** -**`views_update`**
    ใƒขใƒผใƒ€ใƒซใฎๆ›ดๆ–ฐใฏใ€็ต„ใฟ่พผใฟใฎใ‚ฏใƒฉใ‚คใ‚ขใƒณใƒˆใง `views_update` API ใ‚’ๅ‘ผใณๅ‡บใ—ใพใ™ใ€‚ใ“ใฎ APIๅ‘ผใณๅ‡บใ—ใงใฏใ€ใƒ“ใƒฅใƒผใ‚’้–‹ใ„ใŸๆ™‚ใซ็”Ÿๆˆใ•ใ‚ŒใŸ `view_id` ใจใ€ๆ›ดๆ–ฐๅพŒใฎ `blocks` ใฎใƒชใ‚นใƒˆใ‚’ๅซใ‚€ๆ–ฐใ—ใ„ `view` ใ‚’ๆŒ‡ๅฎšใ—ใพใ™ใ€‚ๆ—ขๅญ˜ใฎใƒขใƒผใƒ€ใƒซใซๅซใพใ‚Œใ‚‹ใ‚จใƒฌใƒกใƒณใƒˆใ‚’ใƒฆใƒผใ‚ถใƒผใŒๆ“ไฝœใ—ใŸๆ™‚ใซใƒ“ใƒฅใƒผใ‚’ๆ›ดๆ–ฐใ™ใ‚‹ๅ ดๅˆใฏใ€ใƒชใ‚ฏใ‚จใ‚นใƒˆใฎ `body` ใซๅซใพใ‚Œใ‚‹ `view_id` ใŒๅˆฉ็”จใงใใพใ™ใ€‚ -**`views_push`**
    -ๆ—ขๅญ˜ใฎใƒขใƒผใƒ€ใƒซใฎไธŠใซๆ–ฐใ—ใ„ใƒขใƒผใƒ€ใƒซใ‚’ใ‚นใ‚ฟใƒƒใ‚ฏใฎใ‚ˆใ†ใซ่ฟฝๅŠ ใ™ใ‚‹ๅ ดๅˆใฏใ€็ต„ใฟ่พผใฟใฎใ‚ฏใƒฉใ‚คใ‚ขใƒณใƒˆใง `views_push` API ใ‚’ๅ‘ผใณๅ‡บใ—ใพใ™ใ€‚ใ“ใฎ API ๅ‘ผใณๅ‡บใ—ใงใฏใ€ๆœ‰ๅŠนใช `trigger_id` ใจๆ–ฐใ—ใ„ใƒ“ใƒฅใƒผใฎใƒšใ‚คใƒญใƒผใƒ‰ใ‚’ๆŒ‡ๅฎšใ—ใพใ™ใ€‚`views_push` ใฎๅผ•ๆ•ฐใฏ ใƒขใƒผใƒ€ใƒซใฎ้–‹ๅง‹ ใจๅŒใ˜ใงใ™ใ€‚ใƒขใƒผใƒ€ใƒซใ‚’้–‹ใ„ใŸๅพŒใ€ใ“ใฎใƒขใƒผใƒ€ใƒซใฎใ‚นใ‚ฟใƒƒใ‚ฏใซ่ฟฝๅŠ ใงใใ‚‹ใƒขใƒผใƒ€ใƒซใƒ“ใƒฅใƒผใฏ 2 ใคใพใงใงใ™ใ€‚ +**`views_push`** -ใƒขใƒผใƒ€ใƒซใฎๆ›ดๆ–ฐใจๅคš้‡่กจ็คบใซ้–ขใ™ใ‚‹่ฉณ็ดฐใฏใ€API ใƒ‰ใ‚ญใƒฅใƒกใƒณใƒˆใ‚’ๅ‚็…งใ—ใฆใใ ใ•ใ„ใ€‚ +ๆ—ขๅญ˜ใฎใƒขใƒผใƒ€ใƒซใฎไธŠใซๆ–ฐใ—ใ„ใƒขใƒผใƒ€ใƒซใ‚’ใ‚นใ‚ฟใƒƒใ‚ฏใฎใ‚ˆใ†ใซ่ฟฝๅŠ ใ™ใ‚‹ๅ ดๅˆใฏใ€็ต„ใฟ่พผใฟใฎใ‚ฏใƒฉใ‚คใ‚ขใƒณใƒˆใง `views_push` API ใ‚’ๅ‘ผใณๅ‡บใ—ใพใ™ใ€‚ใ“ใฎ API ๅ‘ผใณๅ‡บใ—ใงใฏใ€ๆœ‰ๅŠนใช `trigger_id` ใจๆ–ฐใ—ใ„[ใƒ“ใƒฅใƒผใฎใƒšใ‚คใƒญใƒผใƒ‰](/reference/interaction-payloads/view-interactions-payload/#view_submission)ใ‚’ๆŒ‡ๅฎšใ—ใพใ™ใ€‚`views_push` ใฎๅผ•ๆ•ฐใฏ [ใƒขใƒผใƒ€ใƒซใฎ้–‹ๅง‹](#creating-modals) ใจๅŒใ˜ใงใ™ใ€‚ใƒขใƒผใƒ€ใƒซใ‚’้–‹ใ„ใŸๅพŒใ€ใ“ใฎใƒขใƒผใƒ€ใƒซใฎใ‚นใ‚ฟใƒƒใ‚ฏใซ่ฟฝๅŠ ใงใใ‚‹ใƒขใƒผใƒ€ใƒซใƒ“ใƒฅใƒผใฏ 2 ใคใพใงใงใ™ใ€‚ -
    +ใƒขใƒผใƒ€ใƒซใฎๆ›ดๆ–ฐใจๅคš้‡่กจ็คบใซ้–ขใ™ใ‚‹่ฉณ็ดฐใฏใ€[API ใƒ‰ใ‚ญใƒฅใƒกใƒณใƒˆ](/surfaces/modals)ใ‚’ๅ‚็…งใ—ใฆใใ ใ•ใ„ใ€‚ + +ๆŒ‡ๅฎšๅฏ่ƒฝใชๅผ•ๆ•ฐใฎไธ€่ฆงใฏ[ใƒขใ‚ธใƒฅใƒผใƒซใƒ‰ใ‚ญใƒฅใƒกใƒณใƒˆ](https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html)ใ‚’ๅ‚่€ƒใซใ—ใฆใใ ใ•ใ„ใ€‚ -
    -ๆŒ‡ๅฎšๅฏ่ƒฝใชๅผ•ๆ•ฐใฎไธ€่ฆงใฏใƒขใ‚ธใƒฅใƒผใƒซใƒ‰ใ‚ญใƒฅใƒกใƒณใƒˆใ‚’ๅ‚่€ƒใซใ—ใฆใใ ใ•ใ„ใ€‚ ```python # ใƒขใƒผใƒ€ใƒซใซๅซใพใ‚Œใ‚‹ใ€`button_abc` ใจใ„ใ† action_id ใฎใƒœใ‚ฟใƒณใฎๅ‘ผใณๅ‡บใ—ใ‚’ใƒชใƒƒใ‚นใƒณ @app.action("button_abc") @@ -52,6 +45,4 @@ def update_modal(ack, body, client): ] } ) -``` -
    - +``` \ No newline at end of file diff --git a/docs/_basic/ja_listening_modals.md b/docs/japanese/concepts/view-submissions.md similarity index 67% rename from docs/_basic/ja_listening_modals.md rename to docs/japanese/concepts/view-submissions.md index d23ddcf89..f82922004 100644 --- a/docs/_basic/ja_listening_modals.md +++ b/docs/japanese/concepts/view-submissions.md @@ -1,13 +1,6 @@ ---- -title: ใƒขใƒผใƒ€ใƒซใฎ้€ไฟกใฎใƒชใ‚นใƒ‹ใƒณใ‚ฐ -lang: ja-jp -slug: view_submissions -order: 12 ---- +# ใƒขใƒผใƒ€ใƒซใฎ้€ไฟกใฎใƒชใ‚นใƒ‹ใƒณใ‚ฐ -
    - -ใƒขใƒผใƒ€ใƒซใฎใƒšใ‚คใƒญใƒผใƒ‰ใซ `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` ใซ็ดใฅใ‘ใ‚‹ๅฝขใงๅ…ฅๅŠ›ๅ€คใ‚’ไฟๆŒใ—ใฆใ„ใพใ™ใ€‚ @@ -21,11 +14,14 @@ order: 12 # ใƒขใƒผใƒ€ใƒซ้€ไฟกใงใฎใƒ“ใƒฅใƒผใฎๆ›ดๆ–ฐ @app.view("view_1") def handle_submission(ack, body): + # build_new_view() method ใฏใƒขใƒผใƒ€ใƒซใƒ“ใƒฅใƒผใ‚’่ฟ”ใ—ใพใ™ + # ใƒขใƒผใƒ€ใƒซใฎๆง‹็ฏ‰ใซใฏ Block Kit Builder ใ‚’่ฉฆใ—ใฆใฟใฆใใ ใ•ใ„๏ผš + # 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,10 +56,8 @@ 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 ใƒชใ‚ฏใ‚จใ‚นใƒˆใ‚’ๅ‡ฆ็† @app.view("view_1") @@ -98,5 +92,4 @@ def handle_submission(ack, body, client, view, logger): except e: logger.exception(f"Failed to post a message {e}") -``` -
    +``` \ No newline at end of file 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/japanese/getting-started.md b/docs/japanese/getting-started.md new file mode 100644 index 000000000..41e6ae5cd --- /dev/null +++ b/docs/japanese/getting-started.md @@ -0,0 +1,463 @@ +# Bolt ๅ…ฅ้–€ใ‚ฌใ‚คใƒ‰ + +ใ“ใฎใ‚ฌใ‚คใƒ‰ใงใฏใ€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** ใƒšใƒผใ‚ธใŒ่กจ็คบใ•ใ‚Œใพใ™ใ€‚ + +ใ“ใฎใƒšใƒผใ‚ธใงใฏใ€ใ‚ขใƒ—ใƒชใฎๆฆ‚่ฆใ‚„้‡่ฆใช่ช่จผๆƒ…ๅ ฑใ‚’็ขบ่ชใงใใพใ™ใ€‚ใ“ใ‚Œใ‚‰ใฎๆƒ…ๅ ฑใฏๅพŒใปใฉๅ‚็…งใ—ใพใ™ใ€‚ + +![Basic Information ใƒšใƒผใ‚ธ](/img/bolt-python/basic-information-page.png "Basic Information ใƒšใƒผใ‚ธ") + +ใฒใจ้€šใ‚Š็ขบ่ชใ—ใฆใ€ใ‚ขใƒ—ใƒชใฎใ‚ขใ‚คใ‚ณใƒณใจ่ชฌๆ˜Žใ‚’่ฟฝๅŠ ใ—ใŸใ‚‰ใ€ใ‚ขใƒ—ใƒชใฎใƒ—ใƒญใ‚ธใ‚งใ‚ฏใƒˆใฎๆง‹ๆˆ ๐Ÿ”ฉ ใ‚’ๅง‹ใ‚ใพใ—ใ‚‡ใ†ใ€‚ + +--- + +### ใƒˆใƒผใ‚ฏใƒณใจใ‚ขใƒ—ใƒชใฎใ‚คใƒณใ‚นใƒˆใƒผใƒซ {#tokens-and-installing-apps} +Slack ใ‚ขใƒ—ใƒชใงใฏใ€[Slack API ใธใฎใ‚ขใ‚ฏใ‚ปใ‚นใฎ็ฎก็†ใซ OAuth ใ‚’ไฝฟ็”จใ—ใพใ™](/authentication/installing-with-oauth)ใ€‚ใ‚ขใƒ—ใƒชใŒใ‚คใƒณใ‚นใƒˆใƒผใƒซใ•ใ‚Œใ‚‹ใจใ€ใƒˆใƒผใ‚ฏใƒณใŒ็™บ่กŒใ•ใ‚Œใพใ™ใ€‚ใ‚ขใƒ—ใƒชใฏใใฎใƒˆใƒผใ‚ฏใƒณใ‚’ไฝฟใฃใฆ API ใƒกใ‚ฝใƒƒใƒ‰ใ‚’ๅ‘ผใณๅ‡บใ™ใ“ใจใŒใงใใพใ™ใ€‚ + +Slack ใ‚ขใƒ—ใƒชใงไฝฟ็”จใงใใ‚‹ใƒˆใƒผใ‚ฏใƒณใซใฏใ€ใƒฆใƒผใ‚ถใƒผใƒˆใƒผใ‚ฏใƒณ๏ผˆ`xoxp`๏ผ‰ใจใƒœใƒƒใƒˆใƒˆใƒผใ‚ฏใƒณ๏ผˆ`xoxb`๏ผ‰ใ€ใ‚ขใƒ—ใƒชใƒฌใƒ™ใƒซใƒˆใƒผใ‚ฏใƒณ๏ผˆ`xapp`๏ผ‰ใฎ 3 ็จฎ้กžใŒใ‚ใ‚Šใพใ™ใ€‚ +- [ใƒฆใƒผใ‚ถใƒผใƒˆใƒผใ‚ฏใƒณ](/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`](/reference/scopes/chat.write) ใจใ„ใ†ใ‚นใ‚ณใƒผใƒ—ใฎใฟใ‚’่ฟฝๅŠ ใ—ใพใ™ใ€‚ใ“ใฎใ‚นใ‚ณใƒผใƒ—ใฏใ‚ขใƒ—ใƒชใŒๅ‚ๅŠ ใ—ใฆใ„ใ‚‹ใƒใƒฃใƒณใƒใƒซใซใƒกใƒƒใ‚ปใƒผใ‚ธใ‚’ๆŠ•็จฟใ™ใ‚‹ใ“ใจใ‚’่จฑๅฏใ—ใพใ™ใ€‚ + +3. OAuth & Permissions ใƒšใƒผใ‚ธใฎไธ€็•ชไธŠใพใงใ‚นใ‚ฏใƒญใƒผใƒซใ—ใ€ใ€Œ**Install App to Workspace**ใ€ใ‚’ใ‚ฏใƒชใƒƒใ‚ฏใ—ใพใ™ใ€‚Slack ใฎ OAuth ็ขบ่ช็”ป้ข ใŒ่กจ็คบใ•ใ‚Œใพใ™ใ€‚ใ“ใฎ็”ป้ขใง้–‹็™บ็”จใƒฏใƒผใ‚ฏใ‚นใƒšใƒผใ‚นใธใฎใ‚ขใƒ—ใƒชใฎใ‚คใƒณใ‚นใƒˆใƒผใƒซใ‚’ๆ‰ฟ่ชใ—ใพใ™ใ€‚ + +4. ใ‚คใƒณใ‚นใƒˆใƒผใƒซใ‚’ๆ‰ฟ่ชใ™ใ‚‹ใจ **OAuth & Permissions** ใƒšใƒผใ‚ธใŒ่กจ็คบใ•ใ‚Œใ€**Bot User OAuth Access Token** ใ‚’็ขบ่ชใงใใ‚‹ใงใ—ใ‚‡ใ†ใ€‚ + +![OAuth ใƒˆใƒผใ‚ฏใƒณ](/img/bolt-python/bot-token.png "ใƒœใƒƒใƒˆ็”จ OAuth ใƒˆใƒผใ‚ฏใƒณ") + +5. ๆฌกใซใ€Œ**Basic Informationใฎใƒšใƒผใ‚ธ**ใ€ใพใงๆˆปใ‚Šใ€ใ‚ขใƒ—ใƒชใƒฌใƒ™ใƒซใƒˆใƒผใ‚ฏใƒณใฎใ‚ปใ‚ฏใ‚ทใƒงใƒณใพใงไธ‹ใซใ‚นใ‚ฏใƒญใƒผใƒซใ—ใ€Œ**Generate Token and Scopes**ใ€ใ‚’ใ‚ฏใƒชใƒƒใ‚ฏใ—ใฆใ‚ขใƒ—ใƒชใƒฌใƒ™ใƒซใƒˆใƒผใ‚ฏใƒณใ‚’ไฝœๆˆใ—ใพใ™ใ€‚ใ“ใฎใƒˆใƒผใ‚ฏใƒณใซ `connections:write` ใฎใ‚นใ‚ณใƒผใƒ—ใ‚’ไป˜ไธŽใ—ใ€ไฝœๆˆใ•ใ‚ŒใŸ `xapp` ใƒˆใƒผใ‚ฏใƒณใ‚’ไฟๅญ˜ใ—ใพใ™ใ€‚ใ“ใ‚Œใ‚‰ใฎใƒˆใƒผใ‚ฏใƒณใฏๅพŒใปใฉๅˆฉ็”จใ—ใพใ™ใ€‚ + +6. ๅทฆใ‚ตใ‚คใƒ‰ใƒกใƒ‹ใƒฅใƒผใฎใ€Œ**Socket Mode**ใ€ใ‚’ๆœ‰ๅŠนใซใ—ใพใ™ใ€‚ + +:::tip[ใƒˆใƒผใ‚ฏใƒณใฏใƒ‘ใ‚นใƒฏใƒผใƒ‰ใจๅŒๆง˜ใซๅ–ใ‚Šๆ‰ฑใ„ใ€[ๅฎ‰ๅ…จใชๆ–นๆณ•ใงไฟ็ฎกใ—ใฆใใ ใ•ใ„](/security)ใ€‚ใ‚ขใƒ—ใƒชใฏใ“ใฎใƒˆใƒผใ‚ฏใƒณใ‚’ไฝฟใฃใฆ 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.7 ไปฅ้™](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. **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-<ใƒœใƒƒใƒˆใƒˆใƒผใ‚ฏใƒณ> +``` + +2. **Basic Information ใƒšใƒผใ‚ธใฎใ‚ขใƒ—ใƒชใƒฌใƒ™ใƒซใƒˆใƒผใ‚ฏใƒณ๏ผˆxapp๏ผ‰ใ‚’ใ‚ณใƒ”ใƒผ**ใ—ใฆใ€ๅˆฅใฎ็’ฐๅขƒๅค‰ๆ•ฐใซไฟๅญ˜ใ—ใพใ™ใ€‚ +```shell +export SLACK_APP_TOKEN=<ใ‚ขใƒ—ใƒชใƒฌใƒ™ใƒซใƒˆใƒผใ‚ฏใƒณ> +``` +:::warning[๐Ÿ”’ ๅ…จใฆใฎใƒˆใƒผใ‚ฏใƒณใฏๅฎ‰ๅ…จใซไฟ็ฎกใ—ใฆใใ ใ•ใ„ใ€‚] + +ๅฐ‘ใชใใจใ‚‚ใƒ‘ใƒ–ใƒชใƒƒใ‚ฏใชใƒใƒผใ‚ธใƒงใƒณ็ฎก็†ใซใƒใ‚งใƒƒใ‚ฏใ‚คใƒณใ™ใ‚‹ใ‚ˆใ†ใชใ“ใจใฏ้ฟใ‘ใ‚‹ในใใงใ—ใ‚‡ใ†ใ€‚ใพใŸใ€ไธŠใซใ‚ใฃใŸไพ‹ใฎใ‚ˆใ†ใซ็’ฐๅขƒๅค‰ๆ•ฐใ‚’ไป‹ใ—ใฆใ‚ขใ‚ฏใ‚ปใ‚นใ™ใ‚‹ใ‚ˆใ†ใซใ—ใฆใใ ใ•ใ„ใ€‚่ฉณ็ดฐใชๆƒ…ๅ ฑใฏ [ใ‚ขใƒ—ใƒชใฎใ‚ปใ‚ญใƒฅใƒชใƒ†ใ‚ฃใฎใƒ™ใ‚นใƒˆใƒ—ใƒฉใ‚ฏใƒ†ใ‚ฃใ‚น](/security)ใฎใƒ‰ใ‚ญใƒฅใƒกใƒณใƒˆใ‚’ๅ‚็…งใ—ใฆใใ ใ•ใ„ใ€‚ + +::: + +ๅฎŒไบ†ใ—ใŸใ‚‰ใ€ใ„ใ‚ˆใ„ใ‚ˆใ‚ขใƒ—ใƒชใ‚’ไฝœใฃใฆใ„ใใพใ—ใ‚‡ใ†ใ€‚ไปฅไธ‹ใฎใ‚ณใƒžใƒณใƒ‰ใ‚’ไฝฟใฃใฆใ€ไปฎๆƒณ็’ฐๅขƒใซ 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")) + +# ใ‚ขใƒ—ใƒชใ‚’่ตทๅ‹•ใ—ใพใ™ +if __name__ == "__main__": + SocketModeHandler(app, os.environ["SLACK_APP_TOKEN"]).start() +``` + +ใ“ใฎใ‚ˆใ†ใซใƒˆใƒผใ‚ฏใƒณใ•ใˆใ‚ใ‚Œใฐใ€ๆœ€ๅˆใฎ Bolt ใ‚ขใƒ—ใƒชใ‚’ไฝœๆˆใ™ใ‚‹ใ“ใจใŒใงใใพใ™ใ€‚ใ€Œ`app.py`ใ€ใจใ„ใ†ๅๅ‰ใงใƒ•ใ‚กใ‚คใƒซใ‚’ไฟๅญ˜ใ—ใฆใ€ใ‚ณใƒžใƒณใƒ‰ใƒฉใ‚คใƒณใงไปฅไธ‹ใ‚’ๅฎŸ่กŒใ—ใพใ™ใ€‚ + +```script +python3 app.py +``` + +ใ‚ขใƒ—ใƒชใŒ่ตทๅ‹•ใ—ใ€ๅฎŸ่กŒไธญใงใ‚ใ‚‹ใ“ใจใŒ่กจ็คบใ•ใ‚Œใ‚‹ใฏใšใงใ™ ๐ŸŽ‰ + +--- + +### ใ‚คใƒ™ใƒณใƒˆใ‚’่จญๅฎšใ™ใ‚‹ {#setting-up-events} +ใ‚ขใƒ—ใƒชใฏใƒฏใƒผใ‚ฏใ‚นใƒšใƒผใ‚นๅ†…ใฎไป–ใฎใƒกใƒณใƒใƒผใจๅŒใ˜ใ‚ˆใ†ใซๆŒฏใ‚‹่ˆžใ„ใ€ใƒกใƒƒใ‚ปใƒผใ‚ธใ‚’ๆŠ•็จฟใ—ใŸใ‚Šใ€็ตตๆ–‡ๅญ—ใƒชใ‚ขใ‚ฏใ‚ทใƒงใƒณใ‚’่ฟฝๅŠ ใ—ใŸใ‚Šใ€ใ‚คใƒ™ใƒณใƒˆใ‚’ใƒชใƒƒใ‚นใƒณใ—ใฆ่ฟ”็ญ”ใ—ใŸใ‚Šใงใใพใ™ใ€‚ + +Slack ใƒฏใƒผใ‚ฏใ‚นใƒšใƒผใ‚นใง็™บ็”Ÿใ™ใ‚‹ใ‚คใƒ™ใƒณใƒˆ๏ผˆใƒกใƒƒใ‚ปใƒผใ‚ธใŒๆŠ•็จฟใ•ใ‚ŒใŸใจใใ‚„ใ€ใƒกใƒƒใ‚ปใƒผใ‚ธใซๅฏพใ™ใ‚‹ใƒชใ‚ขใ‚ฏใ‚ทใƒงใƒณใŒใคใ‘ใ‚‰ใ‚ŒใŸใจใใชใฉ๏ผ‰ใ‚’ใƒชใƒƒใ‚นใƒณใ™ใ‚‹ใซใฏใ€[Events API ใ‚’ไฝฟใฃใฆ็‰นๅฎšใฎ็จฎ้กžใฎใ‚คใƒ™ใƒณใƒˆใ‚’ใ‚ตใƒ–ใ‚นใ‚ฏใƒฉใ‚คใƒ–ใ—ใพใ™](/apis/events-api/)ใ€‚ + +ใ“ใฎใƒใƒฅใƒผใƒˆใƒชใ‚ขใƒซใฎๅบ็›คใงใ‚ฝใ‚ฑใƒƒใƒˆใƒขใƒผใƒ‰ใ‚’ๆœ‰ๅŠนใซใ—ใพใ—ใŸใ€‚ใ‚ฝใ‚ฑใƒƒใƒˆใƒขใƒผใƒ‰ใ‚’ไฝฟใ†ใ“ใจใงใ€ใ‚ขใƒ—ใƒชใŒๅ…ฌ้–‹ใ•ใ‚ŒใŸ HTTP ใ‚จใƒณใƒ‰ใƒใ‚คใƒณใƒˆใ‚’ๅ…ฌ้–‹ใ›ใšใซ Events API ใ‚„ใ‚คใƒณใ‚ฟใƒฉใ‚ฏใƒ†ใ‚ฃใƒ–ใ‚ณใƒณใƒใƒผใƒใƒณใƒˆใ‚’ๅˆฉ็”จใงใใ‚‹ใ‚ˆใ†ใซใชใ‚Šใพใ™ใ€‚ใ“ใฎใ“ใจใฏใ€้–‹็™บๆ™‚ใ‚„ใƒ•ใ‚กใ‚คใƒคใƒผใ‚ฆใ‚ฉใƒผใƒซใฎ่ฃใ‹ใ‚‰ใฎใƒชใ‚ฏใ‚จใ‚นใƒˆใ‚’ๅ—ใ‘ใ‚‹้š›ใซไพฟๅˆฉใงใ™ใ€‚HTTP ใงใฎๆ–นๅผใฏใ€ใƒ›ใ‚นใƒ†ใ‚ฃใƒณใ‚ฐ็’ฐๅขƒใซใƒ‡ใƒ—ใƒญใ‚คใ™ใ‚‹ใ‚ขใƒ—ใƒชใ‚„ Slack App Directory ใง้…ๅธƒใ•ใ‚Œใ‚‹ใ‚ขใƒ—ใƒชใฎ้–‹็™บใƒป้‹็”จใซ้ฉใ—ใฆใ„ใพใ™ใ€‚ + +ใใ‚Œใงใฏใ€ใ“ใฎใ‚ขใƒ—ใƒชใŒใฉใฎใ‚คใƒ™ใƒณใƒˆใ‚’ใƒชใƒƒใ‚นใƒณใ—ใŸใ„ใ‹ใ‚’ Slack ใซไผใˆใพใ—ใ‚‡ใ†ใ€‚ + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + + + + +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`](/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**ใ€ใƒœใ‚ฟใƒณใ‚’ใ‚ฏใƒชใƒƒใ‚ฏใ—ใพใ™ใ€‚ + +--- + +### ใƒกใƒƒใ‚ปใƒผใ‚ธใ‚’ใƒชใƒƒใ‚นใƒณใ—ใฆๅฟœ็ญ”ใ™ใ‚‹ {#listening-and-responding-to-a-message} +ใ‚ขใƒ—ใƒชใซใƒญใ‚ธใƒƒใ‚ฏใ‚’็ต„ใฟ่พผใ‚€ๆบ–ๅ‚™ใŒๆ•ดใ„ใพใ—ใŸใ€‚ใพใšใฏ `message()` ใƒกใ‚ฝใƒƒใƒ‰ใ‚’ไฝฟ็”จใ—ใฆใ€ใƒกใƒƒใ‚ปใƒผใ‚ธใฎใƒชใ‚นใƒŠใƒผใ‚’ใ‚ขใ‚ฟใƒƒใƒใ—ใพใ—ใ‚‡ใ†ใ€‚ + +ๆฌกใฎไพ‹ใงใฏใ€ใ‚ขใƒ—ใƒชใŒๅ‚ๅŠ ใ™ใ‚‹ใƒใƒฃใƒณใƒใƒซใจใƒ€ใ‚คใƒฌใ‚ฏใƒˆใƒกใƒƒใ‚ปใƒผใ‚ธใซๆŠ•็จฟใ•ใ‚Œใ‚‹ใ™ในใฆใฎใƒกใƒƒใ‚ปใƒผใ‚ธใ‚’ใƒชใƒƒใ‚นใƒณใ—ใ€ใ€Œใ“ใ‚“ใซใกใฏใ€ใจใ„ใ†ใƒกใƒƒใ‚ปใƒผใ‚ธใซๅฟœ็ญ”ใ‚’่ฟ”ใ—ใพใ™ใ€‚ + + + + +```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")) + +# 'ใ“ใ‚“ใซใกใฏ' ใ‚’ๅซใ‚€ใƒกใƒƒใ‚ปใƒผใ‚ธใ‚’ใƒชใƒƒใ‚นใƒณใ—ใพใ™ +# ๆŒ‡ๅฎšๅฏ่ƒฝใชใƒชใ‚นใƒŠใƒผใฎใƒกใ‚ฝใƒƒใƒ‰ๅผ•ๆ•ฐใฎไธ€่ฆงใฏไปฅไธ‹ใฎใƒขใ‚ธใƒฅใƒผใƒซใƒ‰ใ‚ญใƒฅใƒกใƒณใƒˆใ‚’ๅ‚่€ƒใซใ—ใฆใใ ใ•ใ„๏ผš +# https://docs.slack.dev/tools/bolt-python/reference/kwargs_injection/args.html +@app.message("ใ“ใ‚“ใซใกใฏ") +def message_hello(message, say): + # ใ‚คใƒ™ใƒณใƒˆใŒใƒˆใƒชใ‚ฌใƒผใ•ใ‚ŒใŸใƒใƒฃใƒณใƒใƒซใธ say() ใงใƒกใƒƒใ‚ปใƒผใ‚ธใ‚’้€ไฟกใ—ใพใ™ + say(f"ใ“ใ‚“ใซใกใฏใ€<@{message['user']}> ใ•ใ‚“๏ผ") + +if __name__ == "__main__": + # ใ‚ขใƒ—ใƒชใ‚’่ตทๅ‹•ใ—ใฆใ€ใ‚ฝใ‚ฑใƒƒใƒˆใƒขใƒผใƒ‰ใง Slack ใซๆŽฅ็ถšใ—ใพใ™ + 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))) +``` + + + + +ใ‚ขใƒ—ใƒชใ‚’ๅ†่ตทๅ‹•ใ—ใฆใ€ใƒœใƒƒใƒˆใƒฆใƒผใ‚ถใƒผใŒๅ‚ๅŠ ใ—ใฆใ„ใ‚‹ใƒใƒฃใƒณใƒใƒซใƒปใƒ€ใ‚คใƒฌใ‚ฏใƒˆใƒกใƒƒใ‚ปใƒผใ‚ธใซใ€Œใ“ใ‚“ใซใกใฏใ€ใจใ„ใ†ใƒกใƒƒใ‚ปใƒผใ‚ธใ‚’ๆŠ•็จฟใ™ใ‚‹ใจใ€ใ‚ขใƒ—ใƒชใŒ่ฟ”็ญ”ใ‚’่ฟ”ใ™ใงใ—ใ‚‡ใ†ใ€‚ + +ใ“ใ‚Œใฏใ”ใๅŸบๆœฌ็š„ใชใ‚ณใƒผใƒ‰ไพ‹ใงใ™ใŒใ€ๆœ€็ต‚็š„ใซใ‚„ใ‚ŠใŸใ„ใ“ใจใ‚’ๅฎŸ็พใ™ใ‚‹ใŸใ‚ใซใ‚ขใƒ—ใƒชใ‚’ใ‚ซใ‚นใ‚ฟใƒžใ‚คใ‚บใ—ใฆใ„ใๅœŸๅฐใจใ—ใฆๅˆฉ็”จใงใใพใ™ใ€‚ๆฌกใฏใ€ใ‚ทใƒณใƒ—ใƒซใชใƒ†ใ‚ญใ‚นใƒˆใฎ่ฟ”็ญ”ใ‚’้€ไฟกใ™ใ‚‹ไปฃใ‚ใ‚Šใซใƒกใƒƒใ‚ปใƒผใ‚ธๅ†…ใซใƒœใ‚ฟใƒณใ‚’่กจ็คบใ™ใ‚‹ใจใ„ใ†ใ€ใ‚‚ใ†ๅฐ‘ใ—ใ‚คใƒณใ‚ฟใƒฉใ‚ฏใƒ†ใ‚ฃใƒ–ใชๅ‹•ไฝœใ‚’่ฉฆใ—ใฆใฟใพใ—ใ‚‡ใ†ใ€‚ + +--- + +### ใ‚ขใ‚ฏใ‚ทใƒงใƒณใ‚’้€ไฟกใ—ใฆๅฟœ็ญ”ใ™ใ‚‹ {#sending-and-responding-to-actions} + +ใ‚คใƒณใ‚ฟใƒฉใ‚ฏใƒ†ใ‚ฃใƒ–ๆฉŸ่ƒฝใ‚’ๆœ‰ๅŠนใซใ™ใ‚‹ใจใ€ใƒœใ‚ฟใƒณใ€้ธๆŠžใƒกใƒ‹ใƒฅใƒผใ€ๆ—ฅไป˜ใƒ”ใƒƒใ‚ซใƒผใ€ใƒขใƒผใƒ€ใƒซใ€ใ‚ทใƒงใƒผใƒˆใ‚ซใƒƒใƒˆใชใฉใฎๆฉŸ่ƒฝใŒๅˆฉ็”จใงใใ‚‹ใ‚ˆใ†ใซใชใ‚Šใพใ™ใ€‚ใ‚ขใƒ—ใƒช่จญๅฎšใƒšใƒผใ‚ธใฎใ€Œ**Interactivity & Shortcuts**ใ€ใซใ‚ขใ‚ฏใ‚ปใ‚นใ—ใฆใใ ใ•ใ„ใ€‚ + + + + +ใ‚ฝใ‚ฑใƒƒใƒˆ ใƒขใƒผใƒ‰ใ‚’ใ‚ชใƒณใซใ™ใ‚‹ใจใ€ๅŸบๆœฌ็š„ใชๅฏพ่ฉฑๆฉŸ่ƒฝใŒใƒ‡ใƒ•ใ‚ฉใƒซใƒˆใงๆœ‰ๅŠนใซใชใ‚‹ใŸใ‚ใ€ใใ‚ŒไปฅไธŠใฎๆ“ไฝœใฏๅฟ…่ฆใ‚ใ‚Šใพใ›ใ‚“ใ€‚ + + + + +ใ‚คใƒ™ใƒณใƒˆใจๅŒๆง˜ใซใ€Slack ใŒใ‚ขใ‚ฏใ‚ทใƒงใƒณ (*ใƒฆใƒผใ‚ถใƒผใŒใƒœใ‚ฟใƒณใ‚’ใ‚ฏใƒชใƒƒใ‚ฏใ—ใŸ* ใชใฉ) ใ‚’้€ไฟกใ™ใ‚‹ใซใฏใ€URL ใ‚’ๆŒ‡ๅฎšใ™ใ‚‹ๅฟ…่ฆใŒใ‚ใ‚Šใพใ™ใ€‚ใ‚ขใƒ—ใƒชๆง‹ๆˆใƒšใƒผใ‚ธใซๆˆปใ‚Šใ€ๅทฆๅดใซใ‚ใ‚‹ **ๅฏพ่ฉฑๆ€งใจใ‚ทใƒงใƒผใƒˆใ‚ซใƒƒใƒˆ** ใ‚’ใ‚ฏใƒชใƒƒใ‚ฏใ—ใพใ™ใ€‚ๅˆฅใฎ **ใƒชใ‚ฏใ‚จใ‚นใƒˆ URL** ใƒœใƒƒใ‚ฏใ‚นใŒใ‚ใ‚‹ใ“ใจใŒใ‚ใ‹ใ‚Šใพใ™ใ€‚ + +:::tip + +ใ‚ฝใ‚ฑใƒƒใƒˆใƒขใƒผใƒ‰ใ‚’ๆœ‰ๅŠนใซใ—ใฆใ„ใ‚‹ใจใใ€ใƒ‡ใƒ•ใ‚ฉใƒซใƒˆใงๅŸบๆœฌ็š„ใชใ‚คใƒณใ‚ฟใƒฉใ‚ฏใƒ†ใ‚ฃใƒ–ๆฉŸ่ƒฝใŒๆœ‰ๅŠนใซใชใฃใฆใ„ใ‚‹ใ“ใจใซๆฐ—ใฅใใงใ—ใ‚‡ใ†ใ€‚่ฟฝๅŠ ใฎใ‚ขใ‚ฏใ‚ทใƒงใƒณใฏไธ่ฆใงใ™ใ€‚ใ‚‚ใ— HTTP ใ‚’ไฝฟใฃใฆใ„ใ‚‹ๅ ดๅˆใ€Slack ใ‹ใ‚‰ใฎใ‚คใƒ™ใƒณใƒˆ้€ไฟกๅ…ˆใงใ‚ใ‚‹ Request URL ใ‚’่จญๅฎšใ™ใ‚‹ๅฟ…่ฆใŒใ‚ใ‚Šใพใ™ใ€‚ + +::: + + + + +ใ‚คใƒณใ‚ฟใƒฉใ‚ฏใƒ†ใ‚ฃใƒ“ใƒ†ใ‚ฃใŒๆœ‰ๅŠนๅŒ–ใ•ใ‚Œใฆใ„ใ‚Œใฐใ€ใ‚ทใƒงใƒผใƒˆใ‚ซใƒƒใƒˆใ€ใƒขใƒผใƒ€ใƒซใ€ใ‚คใƒณใ‚ฟใƒฉใ‚ฏใƒ†ใ‚ฃใƒ–ใ‚ณใƒณใƒใƒผใƒใƒณใƒˆ (ไพ‹๏ผšใƒœใ‚ฟใƒณใ€้ธๆŠžใƒกใƒ‹ใƒฅใƒผใ€ๆ—ฅไป˜ใƒ”ใƒƒใ‚ซใƒผ) ใจใฎใ‚คใƒณใ‚ฟใƒฉใ‚ฏใ‚ทใƒงใƒณใฏใ‚คใƒ™ใƒณใƒˆใจใ—ใฆใ‚ใชใŸใฎใ‚ขใƒ—ใƒชใซ้€ไฟกใ•ใ‚Œใพใ™ใ€‚ + +ใใ‚Œใงใฏใ€ใ‚ขใƒ—ใƒชใฎใ‚ณใƒผใƒ‰ใซๆˆปใ‚Šใ€ใ“ใ‚Œใ‚‰ใฎใ‚คใƒ™ใƒณใƒˆใ‚’ๅ‡ฆ็†ใ™ใ‚‹็‚บใฎใƒญใ‚ธใƒƒใ‚ฏใ‚’่ฟฝๅŠ ใ—ใพใ—ใ‚‡ใ†ใ€‚ +- ใพใšใ€ใ‚คใƒณใ‚ฟใƒฉใ‚ฏใƒ†ใ‚ฃใƒ–ใ‚ณใƒณใƒใƒผใƒใƒณใƒˆ๏ผˆใ“ใ“ใงใฏใƒœใ‚ฟใƒณ๏ผ‰ใ‚’ๅซใ‚“ใ ใƒกใƒƒใ‚ปใƒผใ‚ธใ‚’ใ‚ขใƒ—ใƒชใ‹ใ‚‰้€ไฟกใ—ใพใ™ใ€‚ +- ๆฌกใซใ€ใƒฆใƒผใ‚ถใƒผใ‹ใ‚‰่ฟ”ใ•ใ‚Œใ‚‹ใƒœใ‚ฟใƒณใ‚ฏใƒชใƒƒใ‚ฏใฎใ‚ขใ‚ฏใ‚ทใƒงใƒณใ‚’ใƒชใƒƒใ‚นใƒณใ—ใ€ใใ‚Œใซๅฟœ็ญ”ใ—ใพใ™ใ€‚ + +ไปฅไธ‹ใฎใ‚ณใƒผใƒ‰ใฎๅพŒใฎ้ƒจๅˆ†ใ‚’็ทจ้›†ใ—ใ€ๆ–‡ๅญ—ๅˆ—ใ ใ‘ใฎใƒกใƒƒใ‚ปใƒผใ‚ธใฎไปฃใ‚ใ‚Šใซใ€ใƒœใ‚ฟใƒณใ‚’ๅซใ‚“ใ ใƒกใƒƒใ‚ปใƒผใ‚ธใ‚’้€ไฟกใ™ใ‚‹ใ‚ˆใ†ใซใ—ใฆใฟใพใ™ใ€‚ + + + + +```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")) + +# 'ใ“ใ‚“ใซใกใฏ' ใ‚’ๅซใ‚€ใƒกใƒƒใ‚ปใƒผใ‚ธใ‚’ใƒชใƒƒใ‚นใƒณใ—ใพใ™ +@app.message("ใ“ใ‚“ใซใกใฏ") +def message_hello(message, say): + # ใ‚คใƒ™ใƒณใƒˆใŒใƒˆใƒชใ‚ฌใƒผใ•ใ‚ŒใŸใƒใƒฃใƒณใƒใƒซใธ say() ใงใƒกใƒƒใ‚ปใƒผใ‚ธใ‚’้€ไฟกใ—ใพใ™ + say( + blocks=[ + { + "type": "section", + "text": {"type": "mrkdwn", "text": f"ใ“ใ‚“ใซใกใฏใ€<@{message['user']}> ใ•ใ‚“๏ผ"}, + "accessory": { + "type": "button", + "text": {"type": "plain_text", "text": "ใ‚ฏใƒชใƒƒใ‚ฏใ—ใฆใใ ใ•ใ„"}, + "action_id": "button_click" + } + } + ], + text=f"ใ“ใ‚“ใซใกใฏใ€<@{message['user']}> ใ•ใ‚“๏ผ", + ) + +if __name__ == "__main__": + # ใ‚ขใƒ—ใƒชใ‚’่ตทๅ‹•ใ—ใฆใ€ใ‚ฝใ‚ฑใƒƒใƒˆใƒขใƒผใƒ‰ใง Slack ใซๆŽฅ็ถšใ—ใพใ™ + 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[[Block Kit Builder](https://app.slack.com/block-kit-builder) ใ‚’ไฝฟ็”จใ™ใ‚‹ใจใ€ใ‚คใƒณใ‚ฟใƒฉใ‚ฏใƒ†ใ‚ฃใƒ–ใชใƒกใƒƒใ‚ปใƒผใ‚ธใฎใƒ—ใƒญใƒˆใ‚ฟใ‚คใƒ—ใ‚’็ฐกๅ˜ใซไฝœๆˆใงใใพใ™ใ€‚] + +่‡ชๅˆ†่‡ช่บซใ‚„ใƒใƒผใƒ ใƒกใƒณใƒใƒผใŒใƒกใƒƒใ‚ปใƒผใ‚ธใฎใƒขใƒƒใ‚ฏใ‚ขใƒƒใƒ—ใ‚’ไฝœๆˆใ—ใ€็”Ÿๆˆใ•ใ‚Œใ‚‹ JSON ใ‚’ใ‚ขใƒ—ใƒชใซ็›ดๆŽฅ่ฒผใ‚Šใคใ‘ใ‚‹ใ“ใจใŒใงใใพใ™ใ€‚ + +::: + +ใ‚ขใƒ—ใƒชใ‚’ๅ†่ตทๅ‹•ใ—ใ€ใ‚ขใƒ—ใƒชใŒๅ‚ๅŠ ใ—ใฆใ„ใ‚‹ใƒใƒฃใƒณใƒใƒซใงใ€Œใ“ใ‚“ใซใกใฏใ€ใจๅ…ฅๅŠ›ใ™ใ‚‹ใจใ€ใƒœใ‚ฟใƒณไป˜ใใฎใƒกใƒƒใ‚ปใƒผใ‚ธใŒ่กจ็คบใ•ใ‚Œใ‚‹ใ‚ˆใ†ใซใชใ‚Šใพใ—ใŸใ€‚ใŸใ ใ—ใ€ใƒœใ‚ฟใƒณใ‚’ใ‚ฏใƒชใƒƒใ‚ฏใ—ใฆใ‚‚ใ€*ใพใ * ไฝ•ใ‚‚่ตทใ“ใ‚Šใพใ›ใ‚“ใ€‚ + +ใƒใƒณใƒ‰ใƒฉใƒผใ‚’่ฟฝๅŠ ใ—ใฆใ€ใƒœใ‚ฟใƒณใŒใ‚ฏใƒชใƒƒใ‚ฏใ•ใ‚ŒใŸใจใใซใƒ•ใ‚ฉใƒญใƒผใ‚ขใƒƒใƒ—ใƒกใƒƒใ‚ปใƒผใ‚ธใ‚’้€ไฟกใ™ใ‚‹ใ‚ˆใ†ใซใ—ใฆใฟใพใ—ใ‚‡ใ†ใ€‚ + + + + +```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")) + +# 'ใ“ใ‚“ใซใกใฏ' ใ‚’ๅซใ‚€ใƒกใƒƒใ‚ปใƒผใ‚ธใ‚’ใƒชใƒƒใ‚นใƒณใ—ใพใ™ +@app.message("ใ“ใ‚“ใซใกใฏ") +def message_hello(message, say): + # ใ‚คใƒ™ใƒณใƒˆใŒใƒˆใƒชใ‚ฌใƒผใ•ใ‚ŒใŸใƒใƒฃใƒณใƒใƒซใธ say() ใงใƒกใƒƒใ‚ปใƒผใ‚ธใ‚’้€ไฟกใ—ใพใ™ + say( + blocks=[ + { + "type": "section", + "text": {"type": "mrkdwn", "text": f"ใ“ใ‚“ใซใกใฏใ€<@{message['user']}> ใ•ใ‚“๏ผ"}, + "accessory": { + "type": "button", + "text": {"type": "plain_text", "text": "ใ‚ฏใƒชใƒƒใ‚ฏใ—ใฆใใ ใ•ใ„"}, + "action_id": "button_click" + } + } + ], + text=f"ใ“ใ‚“ใซใกใฏใ€<@{message['user']}> ใ•ใ‚“๏ผ", + ) + +@app.action("button_click") +def action_button_click(body, ack, say): + # ใ‚ขใ‚ฏใ‚ทใƒงใƒณใ‚’็ขบ่ชใ—ใŸใ“ใจใ‚’ๅณๆ™‚ใงๅฟœ็ญ”ใ—ใพใ™ + ack() + # ใƒใƒฃใƒณใƒใƒซใซใƒกใƒƒใ‚ปใƒผใ‚ธใ‚’ๆŠ•็จฟใ—ใพใ™ + say(f"<@{body['user']['id']}> ใ•ใ‚“ใŒใƒœใ‚ฟใƒณใ‚’ใ‚ฏใƒชใƒƒใ‚ฏใ—ใพใ—ใŸ๏ผ") + +if __name__ == "__main__": + # ใ‚ขใƒ—ใƒชใ‚’่ตทๅ‹•ใ—ใฆใ€ใ‚ฝใ‚ฑใƒƒใƒˆใƒขใƒผใƒ‰ใง Slack ใซๆŽฅ็ถšใ—ใพใ™ + 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 ใ‚ขใƒ—ใƒชใ‚’ไฝœใ‚‹ๆ–นๆณ•ใซใคใ„ใฆ่ชฟในใฆใฟใฆใใ ใ•ใ„ใ€‚ๅ‚่€ƒใซใชใ‚Šใใ†ใชใƒชใ‚ฝใƒผใ‚นใ‚’ใ„ใใคใ‹ใ”็ดนไป‹ใ—ใพใ™ใ€‚ + +* ๅŸบๆœฌ็š„ใชๆฆ‚ๅฟตใซใคใ„ใฆ่ชญใ‚“ใงใฟใฆใใ ใ•ใ„ใ€‚Bolt ใ‚ขใƒ—ใƒชใŒใ‚ขใ‚ฏใ‚ปใ‚นใงใใ‚‹ใ•ใพใ–ใพใƒกใ‚ฝใƒƒใƒ‰ใ‚„ๆฉŸ่ƒฝใซใคใ„ใฆ็Ÿฅใ‚‹ใ“ใจใŒใงใใพใ™ใ€‚ +* [`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/jp.md b/docs/jp.md deleted file mode 100644 index 387668f76..000000000 --- a/docs/jp.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -permalink: ja-jp/concepts -redirect_from: - - /jp - - /ja-jp -layout: default -lang: ja-jp ---- diff --git a/docs/reference/adapter/aiohttp/index.html b/docs/reference/adapter/aiohttp/index.html new file mode 100644 index 000000000..7d7ceedbe --- /dev/null +++ b/docs/reference/adapter/aiohttp/index.html @@ -0,0 +1,128 @@ + + + + + + +slack_bolt.adapter.aiohttp API documentation + + + + + + + + + + + +
    +
    +
    +

    Module slack_bolt.adapter.aiohttp

    +
    +
    +
    +
    +
    +
    +
    +
    +

    Functions

    +
    +
    +async def to_aiohttp_response(bolt_resp:ย BoltResponse) โ€‘>ย aiohttp.web_response.Response +
    +
    +
    + +Expand source code + +
    async def to_aiohttp_response(bolt_resp: BoltResponse) -> web.Response:
    +    content_type = bolt_resp.headers.pop(
    +        "content-type",
    +        ["application/json" if bolt_resp.body.startswith("{") else "text/plain"],
    +    )[0]
    +    content_type = re.sub(r";\s*charset=utf-8", "", content_type)
    +    resp = web.Response(
    +        status=bolt_resp.status,
    +        body=bolt_resp.body,
    +        headers=bolt_resp.first_headers_without_set_cookie(),
    +        content_type=content_type,
    +    )
    +    for cookie in bolt_resp.cookies():
    +        for name, c in cookie.items():
    +            resp.set_cookie(
    +                name=name,
    +                value=c.value,
    +                max_age=c.get("max-age"),
    +                expires=c.get("expires"),
    +                path=c.get("path"),  # type: ignore[arg-type]
    +                domain=c.get("domain"),
    +                secure=True,
    +                httponly=True,
    +            )
    +    return resp
    +
    +
    +
    +
    +async def to_bolt_request(request:ย aiohttp.web_request.Request) โ€‘>ย AsyncBoltRequest +
    +
    +
    + +Expand source code + +
    async def to_bolt_request(request: web.Request) -> AsyncBoltRequest:
    +    return AsyncBoltRequest(
    +        body=await request.text(),
    +        query=request.query_string,
    +        headers=request.headers,  # type: ignore[arg-type]
    +    )
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    + + + diff --git a/docs/reference/adapter/asgi/aiohttp/index.html b/docs/reference/adapter/asgi/aiohttp/index.html new file mode 100644 index 000000000..a6aa7c92d --- /dev/null +++ b/docs/reference/adapter/asgi/aiohttp/index.html @@ -0,0 +1,164 @@ + + + + + + +slack_bolt.adapter.asgi.aiohttp API documentation + + + + + + + + + + + +
    +
    +
    +

    Module slack_bolt.adapter.asgi.aiohttp

    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    Classes

    +
    +
    +class AsyncSlackRequestHandler +(app:ย AsyncApp,
    path:ย strย =ย '/slack/events')
    +
    +
    +
    + +Expand source code + +
    class AsyncSlackRequestHandler(SlackRequestHandler):
    +    app: AsyncApp
    +
    +    def __init__(self, app: AsyncApp, path: str = "/slack/events"):
    +        """Setup Bolt as an ASGI web framework, this will make your application compatible with ASGI web servers.
    +        This can be used for production deployment.
    +
    +        With the default settings, `http://localhost:3000/slack/events`
    +        Run Bolt with [uvicron](https://www.uvicorn.org/)
    +
    +            # Python
    +            app = AsyncApp()
    +            api = SlackRequestHandler(app)
    +
    +            # bash
    +            export SLACK_SIGNING_SECRET=***
    +            export SLACK_BOT_TOKEN=xoxb-***
    +            uvicorn app:api --port 3000 --log-level debug
    +
    +        Args:
    +            app: Your bolt application
    +            path: The path to handle request from Slack (Default: `/slack/events`)
    +        """
    +        self.path = path
    +        self.app = app
    +
    +    async def dispatch(self, request: AsgiHttpRequest) -> BoltResponse:
    +        return await self.app.async_dispatch(
    +            AsyncBoltRequest(body=await request.get_raw_body(), query=request.query_string, headers=request.get_headers())
    +        )
    +
    +    async def handle_installation(self, request: AsgiHttpRequest) -> BoltResponse:
    +        return await self.app.oauth_flow.handle_installation(  # type: ignore[union-attr]
    +            AsyncBoltRequest(body=await request.get_raw_body(), query=request.query_string, headers=request.get_headers())
    +        )
    +
    +    async def handle_callback(self, request: AsgiHttpRequest) -> BoltResponse:
    +        return await self.app.oauth_flow.handle_callback(  # type: ignore[union-attr]
    +            AsyncBoltRequest(body=await request.get_raw_body(), query=request.query_string, headers=request.get_headers())
    +        )
    +
    +

    Setup Bolt as an ASGI web framework, this will make your application compatible with ASGI web servers. +This can be used for production deployment.

    +

    With the default settings, http://localhost:3000/slack/events +Run Bolt with uvicron

    +
    # Python
    +app = AsyncApp()
    +api = SlackRequestHandler(app)
    +
    +# bash
    +export SLACK_SIGNING_SECRET=***
    +export SLACK_BOT_TOKEN=xoxb-***
    +uvicorn app:api --port 3000 --log-level debug
    +
    +

    Args

    +
    +
    app
    +
    Your bolt application
    +
    path
    +
    The path to handle request from Slack (Default: /slack/events)
    +
    +

    Ancestors

    + +

    Inherited members

    + +
    +
    +
    +
    + +
    + + + diff --git a/docs/api-docs/slack_bolt/adapter/asgi/async_handler.html b/docs/reference/adapter/asgi/async_handler.html similarity index 53% rename from docs/api-docs/slack_bolt/adapter/asgi/async_handler.html rename to docs/reference/adapter/asgi/async_handler.html index b93e472f9..23433ffce 100644 --- a/docs/api-docs/slack_bolt/adapter/asgi/async_handler.html +++ b/docs/reference/adapter/asgi/async_handler.html @@ -2,18 +2,32 @@ - - + + slack_bolt.adapter.asgi.async_handler API documentation - - - - - - + + + + + + - - + +
    @@ -22,14 +36,6 @@

    Module slack_bolt.adapter.asgi.async_handler

    -
    - -Expand source code - -
    from .aiohttp import AsyncSlackRequestHandler
    -
    -__all__ = ["AsyncSlackRequestHandler"]
    -
    @@ -42,29 +48,9 @@

    Classes

    class AsyncSlackRequestHandler -(app:ย AsyncApp, path:ย strย =ย '/slack/events') +(app:ย AsyncApp,
    path:ย strย =ย '/slack/events')
    -

    Setup Bolt as an ASGI web framework, this will make your application compatible with ASGI web servers. -This can be used for production deployment.

    -

    With the default settings, http://localhost:3000/slack/events -Run Bolt with uvicron

    -
    # Python
    -app = AsyncApp()
    -api = SlackRequestHandler(app)
    -
    -# bash
    -export SLACK_SIGNING_SECRET=***
    -export SLACK_BOT_TOKEN=xoxb-***
    -uvicorn app:api --port 3000 --log-level debug
    -
    -

    Args

    -
    -
    app
    -
    Your bolt application
    -
    path
    -
    The path to handle request from Slack (Default: /slack/events)
    -
    Expand source code @@ -101,36 +87,49 @@

    Args

    ) async def handle_installation(self, request: AsgiHttpRequest) -> BoltResponse: - oauth_flow: AsyncOAuthFlow = self.app.oauth_flow - return await oauth_flow.handle_installation( + return await self.app.oauth_flow.handle_installation( # type: ignore[union-attr] AsyncBoltRequest(body=await request.get_raw_body(), query=request.query_string, headers=request.get_headers()) ) async def handle_callback(self, request: AsgiHttpRequest) -> BoltResponse: - oauth_flow: AsyncOAuthFlow = self.app.oauth_flow - return await oauth_flow.handle_callback( + return await self.app.oauth_flow.handle_callback( # type: ignore[union-attr] AsyncBoltRequest(body=await request.get_raw_body(), query=request.query_string, headers=request.get_headers()) )
    +

    Setup Bolt as an ASGI web framework, this will make your application compatible with ASGI web servers. +This can be used for production deployment.

    +

    With the default settings, http://localhost:3000/slack/events +Run Bolt with uvicron

    +
    # Python
    +app = AsyncApp()
    +api = SlackRequestHandler(app)
    +
    +# bash
    +export SLACK_SIGNING_SECRET=***
    +export SLACK_BOT_TOKEN=xoxb-***
    +uvicorn app:api --port 3000 --log-level debug
    +
    +

    Args

    +
    +
    app
    +
    Your bolt application
    +
    path
    +
    The path to handle request from Slack (Default: /slack/events)
    +

    Ancestors

    -

    Class variables

    -
    -
    var app :ย AsyncApp
    -
    -
    -
    -

    Inherited members

    @@ -139,7 +138,6 @@

    Inherited members

    @@ -206,16 +102,15 @@

    Classes

    class ChaliceSlackRequestHandler -(app:ย App, chalice:ย chalice.app.Chalice, lambda_client:ย Optional[botocore.client.BaseClient]ย =ย None) +(app:ย App,
    chalice:ย chalice.app.Chalice,
    lambda_client:ย botocore.client.BaseClientย |ย Noneย =ย None)
    -
    Expand source code
    class ChaliceSlackRequestHandler:
    -    def __init__(self, app: App, chalice: Chalice, lambda_client: Optional[BaseClient] = None):  # type: ignore
    +    def __init__(self, app: App, chalice: Chalice, lambda_client: Optional[BaseClient] = None):
             self.app = app
             self.chalice = chalice
             self.logger = get_bolt_app_logger(app.name, ChaliceSlackRequestHandler, app.logger)
    @@ -226,7 +121,7 @@ 

    Classes

    LocalLambdaClient, ) - lambda_client = LocalLambdaClient(self.chalice, None) + lambda_client = LocalLambdaClient(self.chalice, None) # type: ignore[arg-type] except ImportError: logging.info("Failed to load LocalLambdaClient for CLI mode.") pass @@ -247,7 +142,7 @@

    Classes

    root.removeHandler(handler) def handle(self, request: Request): - body: str = request.raw_body.decode("utf-8") if request.raw_body else "" + body: str = request.raw_body.decode("utf-8") if request.raw_body else "" # type: ignore[union-attr] self.logger.debug(f"Incoming request: {request.to_dict()}, body: {body}") method = request.method @@ -269,7 +164,7 @@

    Classes

    bolt_resp = oauth_flow.handle_installation(bolt_req) return to_chalice_response(bolt_resp) elif method == "POST": - bolt_req: BoltRequest = to_bolt_request(request, body) + bolt_req = to_bolt_request(request, body) # https://docs.aws.amazon.com/lambda/latest/dg/python-context.html aws_lambda_function_name = self.chalice.lambda_context.function_name bolt_req.context["aws_lambda_function_name"] = aws_lambda_function_name @@ -278,13 +173,14 @@

    Classes

    aws_response = to_chalice_response(bolt_resp) return aws_response elif method == "NONE": - bolt_req: BoltRequest = to_bolt_request(request, body) + bolt_req = to_bolt_request(request, body) bolt_resp = self.app.dispatch(bolt_req) aws_response = to_chalice_response(bolt_resp) return aws_response return not_found()
    +

    Static methods

    @@ -292,18 +188,6 @@

    Static methods

    -
    - -Expand source code - -
    @classmethod
    -def clear_all_log_handlers(cls):
    -    # https://stackoverflow.com/questions/37703609/using-python-logging-with-aws-lambda
    -    root = logging.getLogger()
    -    if root.handlers:
    -        for handler in root.handlers:
    -            root.removeHandler(handler)
    -

    Methods

    @@ -312,13 +196,12 @@

    Methods

    def handle(self, request:ย chalice.app.Request)
    -
    Expand source code
    def handle(self, request: Request):
    -    body: str = request.raw_body.decode("utf-8") if request.raw_body else ""
    +    body: str = request.raw_body.decode("utf-8") if request.raw_body else ""  # type: ignore[union-attr]
         self.logger.debug(f"Incoming request: {request.to_dict()}, body: {body}")
     
         method = request.method
    @@ -340,7 +223,7 @@ 

    Methods

    bolt_resp = oauth_flow.handle_installation(bolt_req) return to_chalice_response(bolt_resp) elif method == "POST": - bolt_req: BoltRequest = to_bolt_request(request, body) + bolt_req = to_bolt_request(request, body) # https://docs.aws.amazon.com/lambda/latest/dg/python-context.html aws_lambda_function_name = self.chalice.lambda_context.function_name bolt_req.context["aws_lambda_function_name"] = aws_lambda_function_name @@ -349,13 +232,14 @@

    Methods

    aws_response = to_chalice_response(bolt_resp) return aws_response elif method == "NONE": - bolt_req: BoltRequest = to_bolt_request(request, body) + bolt_req = to_bolt_request(request, body) bolt_resp = self.app.dispatch(bolt_req) aws_response = to_chalice_response(bolt_resp) return aws_response return not_found()
    +
    @@ -363,7 +247,6 @@

    Methods