diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 000000000..d6547ce30 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,2 @@ +# change black settings +108955b601e768fd56696be903fc8b471c73ebf7 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/01_question.md b/.github/ISSUE_TEMPLATE/01_question.md index 628fb4160..66971561d 100644 --- a/.github/ISSUE_TEMPLATE/01_question.md +++ b/.github/ISSUE_TEMPLATE/01_question.md @@ -1,5 +1,5 @@ --- -name: Question +name: SDK Question about: Submit a question about this SDK title: (Set a clear title describing your question) labels: "untriaged" @@ -30,7 +30,7 @@ sw_vers && uname -v # or `ver` #### Steps to reproduce: -(Share the commands to run, source code, and project settings (e.g., setup.py)) +(Share the commands to run, source code, and project settings (e.g., pyproject.toml)) 1. 2. diff --git a/.github/ISSUE_TEMPLATE/02_enhancement.md b/.github/ISSUE_TEMPLATE/02_enhancement.md index c40600ccf..33c349f68 100644 --- a/.github/ISSUE_TEMPLATE/02_enhancement.md +++ b/.github/ISSUE_TEMPLATE/02_enhancement.md @@ -1,5 +1,5 @@ --- -name: Enhancement / Feature Request +name: SDK Enhancement / Feature Request about: Submit an enhancement/feature request title: (Set a clear title describing your idea) labels: "untriaged" diff --git a/.github/ISSUE_TEMPLATE/03_document.md b/.github/ISSUE_TEMPLATE/03_document.md index a07f7a441..822bc2ee4 100644 --- a/.github/ISSUE_TEMPLATE/03_document.md +++ b/.github/ISSUE_TEMPLATE/03_document.md @@ -1,5 +1,5 @@ --- -name: Document +name: SDK Document about: Submit an issue on documents title: (Set a clear title describing your idea) labels: "untriaged" @@ -10,7 +10,7 @@ assignees: "" ### The page URLs -- https://slack.dev/python-slack-sdk/ +- https://docs.slack.dev/tools/python-slack-sdk/ ### Requirements diff --git a/.github/ISSUE_TEMPLATE/04_bug.md b/.github/ISSUE_TEMPLATE/04_bug.md index 89fa4bcbe..fd1fc5718 100644 --- a/.github/ISSUE_TEMPLATE/04_bug.md +++ b/.github/ISSUE_TEMPLATE/04_bug.md @@ -1,5 +1,5 @@ --- -name: Bug +name: SDK Bug about: Report the SDK bug title: (Set a clear title describing the issue) labels: "untriaged" @@ -30,7 +30,7 @@ sw_vers && uname -v # or `ver` #### Steps to reproduce: -(Share the commands to run, source code, and project settings (e.g., setup.py)) +(Share the commands to run, source code, and project settings (e.g., pyproject.toml)) 1. 2. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..1925abae7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,7 @@ +blank_issues_enabled: false +contact_links: + - name: Slack Platform Customer Support + url: https://my.slack.com/help/requests/new + about: | + This issue tracker is a place to track bugs, feature requests, and questions on this SDK side. + If you have a general question on how to use the Slack platform, please get in touch with our customer support agents first via either /feedback in your Slack workspace or the help page link here. diff --git a/.github/contributing.md b/.github/contributing.md index 78832d300..f15e0c46a 100644 --- a/.github/contributing.md +++ b/.github/contributing.md @@ -38,7 +38,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/python-slack-sdk). +- [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 (see the [Maintainer's Guide](./maintainers_guide.md) for details on how to run the tests). - [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. diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..ac86badc1 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,16 @@ +# 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: + - dependency-name: "black" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" diff --git a/.github/issue_template.md b/.github/issue_template.md index 4c61970db..33b393830 100644 --- a/.github/issue_template.md +++ b/.github/issue_template.md @@ -22,7 +22,7 @@ sw_vers && uname -v # or `ver` #### Steps to reproduce: -(Share the commands to run, source code, and project settings (e.g., setup.py)) +(Share the commands to run, source code, and project settings (e.g., pyproject.toml)) 1. 2. diff --git a/.github/maintainers_guide.md b/.github/maintainers_guide.md index 6ed88343a..ccda1607f 100644 --- a/.github/maintainers_guide.md +++ b/.github/maintainers_guide.md @@ -10,20 +10,20 @@ 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 ``` You can hook `pyenv` into your shell automatically by running `pyenv init` and following the instructions. Install necessary Python runtimes for development/testing. It is not necessary to install all the various Python versions we test in [continuous integration on -GitHub Actions](https://github.com/slackapi/python-slack-sdk/blob/main/.github/workflows/ci-build.yml), +GitHub Actions](https://github.com/slackapi/python-slack-sdk/blob/main/.github/workflows/tests.yml), but make sure you are running at least one version that we execute our tests in locally so that you can run the tests yourself. -```bash +```sh $ pyenv install -l | grep -v "-e[conda|stackless|pypy]" $ pyenv install 3.9.6 # select the latest patch version @@ -40,9 +40,9 @@ $ pyenv rehash Then, you can create a new [Virtual Environment](https://docs.python.org/3/tutorial/venv.html) specific to the Python version you just installed by running: -``` -$ python -m venv env_3.9.6 -$ source env_3.9.6/bin/activate +```sh +python -m venv env_3.9.6 +source env_3.9.6/bin/activate ``` At this point you have a clean, Python-version-specific environment "activated" for @@ -53,95 +53,199 @@ do so after you are done working in this project. To come back to development work for this project again in the future, `cd` into this project directory and run `source env_3.9.6/bin/activate` again. -The last step is to install this project's dependencies; to do so, check out [how +The last step is to install this project's dependencies and run all unit tests; to do so, you can run + +```sh +./scripts/run_validation.sh +``` + +Also check out [how we configure GitHub Actions to install dependencies for this project for use in -our continuous integration](https://github.com/slackapi/python-slack-sdk/blob/main/.github/workflows/ci-build.yml#L26-L30). You can also run `./scripts/run_validation.sh` to install the dependencies and run the unit tests in one command! +our continuous integration](https://github.com/slackapi/python-slack-sdk/blob/v3.17.0/.github/workflows/ci-build.yml#L28-L32). ## Tasks -### Testing (Unit Tests) +### Formatting + +This project uses code formatting tools to maintain consistent style. You can format the codebase by running: + +```sh +./scripts/format.sh +``` + +### Testing + +#### Unit Tests + +When you make changes to this SDK, please write unit tests verifying if the changes work as you expected. You can easily run all the tests and formatting/linter with the below scripts. + +Run all the unit tests, code linter, and code analyzer: + +```sh +./scripts/run_validation.sh +``` + +Run all the unit tests (no linter nor code analyzer): -When you make changes to this SDK, please write unit tests verifying if the changes work as you expected. You can easily run all the tests by running the command. The `validate` command runs Flake8 (static code analyzer), Black (code formatter), and unit tests in the `tests` directory for you. +```sh +./scripts/run_unit_tests.sh +``` -```bash -python setup.py validate # run all +Run a specific unit test: -# run a single test -python setup.py validate \ - --test-target tests/web/test_web_client.py +```sh +./scripts/run_unit_tests.sh tests/web/test_web_client.py ``` You can rely on GitHub Actions builds for running the tests on a variety of Python runtimes. -### Testing (Integration Tests with Real Slack APIs) +#### Integration Tests with Real Slack APIs This project also has integration tests that verify the SDK works with the Slack API platform. As a preparation, you need to set [the required env variables](https://github.com/slackapi/python-slack-sdk/blob/main/integration_tests/env_variable_names.py) properly. You don't need to setup all of them if you just want to run some of the tests. Commonly, `SLACK_SDK_TEST_BOT_TOKEN` and `SLACK_SDK_TEST_USER_TOKEN` are used for running `WebClient` tests. -```bash -python setup.py integration_tests # run all +Run all integration tests: -# run a single test -python setup.py integration_tests \ - --test-target integration_tests/web/test_web_client.py +```sh +./scripts/run_integration_tests.sh ``` -### Generating Documentation - -The documentation is generated from the source and templates in the `docs-src` directory. The generated documentation -gets committed to the repo in `docs` and also published to a GitHub Pages website. +Run a specific integration test: -You can generate the documentation by running `./scripts/docs.sh`. +```sh +./scripts/run_integration_tests.sh integration_tests/web/test_async_web_client.py +``` -### Releasing +#### Develop Locally -1. Create the commit for the release: +If you want to test the package locally you can. -- Bump the version number in adherence to [Semantic Versioning](http://semver.org/) in `slack_sdk/version.py`. -- Build the docs with `./scripts/docs.sh`. -- Create a branch for the release with `git checkout -b v2.5.0` -- Make a commit that includes the new version number: `git commit -m 'version 2.5.0'`. -- Open a PR and merge after receiving at least one approval from other maintainers. -- Create a git tag for the release. For example `git tag v2.5.0`. -- Push the tag up to github with `git push origin --tags` +1. Build the package locally + - 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_sdk-1.2.3-py2.py3-none-any.whl` was created + - From anywhere on your machine you can install this package to a project with + ```sh + pip install /dist/slack_sdk-1.2.3-py2.py3-none-any.whl + ``` + - It is also possible to include `slack_sdk @ file:////dist/slack_sdk-1.2.3-py2.py3-none-any.whl` in a [requirements.txt](https://pip.pypa.io/en/stable/user_guide/#requirements-files) file -2. Distribute the release +### Generating Documentation -- Use the latest stable Python runtime - - `python -m venv env` - - `python setup.py upload` -- Create a GitHub Release. You will select the commit with updated version number (e.g. `version 2.5.0`) to associate with the tag, and name the tag after this version (e.g. `v2.5.0`). This will also serve as a Changelog for the project. Add a description of changes to the Release. Mention Issue and PR #'s and @-mention contributors. +See [`/docs/README`](https://github.com/slackapi/python-slack-sdk/blob/main/docs/README.md) for information on editing documentation pages. -```markdown -Refer to [v{version} milestone](https://github.com/slackapi/python-slack-sdk/milestone/{TODO}?closed=1) to know the complete list of the issues resolved by this release. +The API reference is generated from a script. You can generate and preview the **API _reference_ documents for `slack_sdk` package modules** by running: -**Updates** +```sh +./scripts/generate_api_docs.sh +``` -1. [WebClient] #111 Make an awesome change - Thanks @SlackHQ -1. [RTMClient] #222 Make an awesome change - Thanks @SlackAPI +### Releasing -**All Changes** +#### test.pypi.org deployment -https://github.com/slackapi/python-slack-sdk/compare/{the previous release version tag}...{the release version tag} -``` +[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. -3. (Slack Internal) Communicate the release internally +You can deploy this project to TestPyPI using GitHub Actions. -- Include a link to the GitHub release +To deploy using GitHub Actions: -4. Make announcements +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 -- #slack-api in dev4slack.slack.com -- #lang-python in community.slack.com +Alternatively, you can deploy from your local machine with: -5. (Slack Internal) Tweet by @SlackAPI +```sh +./scripts/deploy_to_test_pypi.sh +``` -- Not necessary for patch updates, might be needed for minor updates, definitely needed for major updates. Include a link to the GitHub release +#### 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](#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_sdk/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/python-slack-sdk/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_sdk/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/python-slack-sdk/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 + +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](#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_sdk/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/python-slack-sdk/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_sdk/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/python-slack-sdk/actions/workflows/pypi-release.yml). You will need to approve the deployment! +4. After a few minutes, the corresponding version will be available on . +5. Close the current GitHub Milestone and create one for the next patch version. +6. (Slack Internal) Communicate the release internally + - Include a link to the GitHub release +7. (Slack Internal) Tweet by @SlackAPI + - Not necessary for patch updates, might be needed for minor updates, + definitely needed for major updates. Include a link to the GitHub release ## Workflow ### Versioning and Tags -This project uses semantic versioning, expressed through the numbering scheme of +This project uses [Semantic Versioning](http://semver.org/), expressed through the numbering scheme of [PEP-0440](https://www.python.org/dev/peps/pep-0440/). ### Branches diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index f56b5fdee..ae9cb65ab 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,8 +1,12 @@ ## Summary -(Describe the goal of this PR. Mention any related issue numbers) + -### Category (place an `x` in each of the `[ ]`) +### Testing + + + +### Category - [ ] **slack_sdk.web.WebClient (sync/async)** (Web API client) - [ ] **slack_sdk.webhook.WebhookClient (sync/async)** (Incoming Webhook, response_url sender) @@ -13,12 +17,11 @@ - [ ] **slack_sdk.scim** (SCIM API client) - [ ] **slack_sdk.audit_logs** (Audit Logs API client) - [ ] **slack_sdk.rtm_v2** (RTM client) -- [ ] `/docs-src` (Documents, have you run `./scripts/docs.sh`?) -- [ ] `/docs-src-v2` (Documents, have you run `./scripts/docs-v2.sh`?) +- [ ] `/docs` (Documents) - [ ] `/tutorial` (PythOnBoardingBot tutorial) - [ ] `tests`/`integration_tests` (Automated tests for this library) -## Requirements (place an `x` in each `[ ]`) +## Requirements - [ ] I've read and understood the [Contributing Guidelines](https://github.com/slackapi/python-slack-sdk/blob/main/.github/contributing.md) and have done my best effort to follow them. - [ ] I've read and agree to the [Code of Conduct](https://slackhq.github.io/code-of-conduct). 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 index d648eb858..c0fd021f0 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -1,38 +1,134 @@ -# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions -name: CI Build +name: Python CI on: push: - branches: [ main ] + branches: + - main pull_request: + schedule: + - cron: "0 0 * * *" + workflow_dispatch: + +env: + LATEST_SUPPORTED_PY: "3.14" jobs: - build: - runs-on: ubuntu-20.04 - timeout-minutes: 10 + lint: + name: Lint + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + contents: read + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + persist-credentials: false + - name: Set up Python ${{ env.LATEST_SUPPORTED_PY }} + uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + persist-credentials: false + - name: Set up Python ${{ env.LATEST_SUPPORTED_PY }} + uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 + with: + python-version: ${{ env.LATEST_SUPPORTED_PY }} + - name: Run mypy verification + run: ./scripts/run_mypy.sh + + unittest: + name: Unit tests + runs-on: ubuntu-22.04 + timeout-minutes: 15 strategy: + fail-fast: false matrix: - python-version: ['3.6', '3.7', '3.8', '3.9'] + python-version: + - "3.14" + - "3.13" + - "3.12" + - "3.11" + - "3.10" + - "3.9" + - "3.8" + - "3.7" + - "pypy3.10" + - "pypy3.11" + permissions: + contents: read env: - PYTHON_SLACK_SDK_MOCK_SERVER_MODE: 'threading' - #CI_UNSTABLE_TESTS_SKIP_ENABLED: '1' + CI_LARGE_SOCKET_MODE_PAYLOAD_TESTING_DISABLED: "1" + FORCE_COLOR: "1" + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + persist-credentials: false + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 + with: + python-version: ${{ matrix.python-version }} + cache: pip + - name: Install dependencies + run: | + pip install -U pip + pip install -r requirements/testing.txt + pip install -r requirements/optional.txt + - name: Run tests + run: | + PYTHONPATH=$PWD:$PYTHONPATH pytest --cov-report=xml --cov=slack_sdk/ --junitxml=reports/test_report.xml tests/ + - name: Run tests for SQLAlchemy v1.4 (backward-compatibility) + run: | + # Install v1.4 for testing + pip install "SQLAlchemy>=1.4,<2" + PYTHONPATH=$PWD:$PYTHONPATH pytest tests/slack_sdk/oauth/installation_store/test_sqlalchemy.py + PYTHONPATH=$PWD:$PYTHONPATH pytest tests/slack_sdk/oauth/state_store/test_sqlalchemy.py + - name: Upload test results to Codecov + if: ${{ !cancelled() }} + uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2 + with: + directory: ./reports/ + fail_ci_if_error: true + flags: ${{ matrix.python-version }} + report_type: test_results + token: ${{ secrets.CODECOV_TOKEN }} + verbose: true + - name: Upload test coverage to Codecov (only with latest supported version) + if: startsWith(matrix.python-version, env.LATEST_SUPPORTED_PY) + uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2 + with: + fail_ci_if_error: true + # Run validation generates the coverage file + files: ./coverage.xml + 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: - - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - pip install -U pip wheel - pip install -e ".[testing]" - pip install -e ".[optional]" - - name: Run validation - run: | - python setup.py validate - - name: Run codecov (only 3.9) - run: | - python_version=`python -V` - if [ ${python_version:7:3} == "3.9" ]; then - codecov -e ${python_version:7} - fi + - name: Send notifications of failing tests + uses: slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a # v2.1.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/pypi-release.yml b/.github/workflows/pypi-release.yml new file mode 100644 index 000000000..89a18c827 --- /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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + with: + ref: ${{ github.event.release.tag_name || github.ref }} + persist-credentials: false + + - name: Set up Python + uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 + with: + python-version: "3.x" + + - name: Build release distributions + run: | + scripts/build_pypi_package.sh + + - name: Persist dist folder + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.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@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 + 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@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 + 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/triage-issues.yml b/.github/workflows/triage-issues.yml new file mode 100644 index 000000000..cf13d3afc --- /dev/null +++ b/.github/workflows/triage-issues.yml @@ -0,0 +1,32 @@ +# This workflow uses the following github action to automate +# management of stale issues and prs in this repo: +# https://github.com/marketplace/actions/close-stale-issues + +name: Close stale issues and PRs + +on: + workflow_dispatch: + schedule: + - cron: "0 0 * * 1" + +jobs: + stale: + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + steps: + - uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10.1.1 + with: + days-before-issue-stale: 30 + days-before-issue-close: 10 + days-before-pr-stale: -1 + days-before-pr-close: -1 + stale-issue-label: auto-triage-stale + stale-issue-message: ๐Ÿ‘‹ It looks like this issue has been open for 30 days with no activity. We'll mark this as stale for now, and wait 10 days for an update or for further comment before closing this issue out. If you think this issue needs to be prioritized, please comment to get the thread going again! Maintainers also review issues marked as stale on a regular basis and comment or adjust status if the issue needs to be reprioritized. + close-issue-message: As this issue has been inactive for more than one month, we will be closing it. Thank you to all the participants! If you would like to raise a related issue, please create a new issue which includes your specific details and references this issue number. + exempt-issue-labels: auto-triage-skip + exempt-all-milestones: true + remove-stale-when-updated: true + enable-statistics: true + operations-per-run: 60 diff --git a/.gitignore b/.gitignore index 68dd7ab37..5f316a341 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,6 @@ # general things to ignore build/ dist/ -docs/_sources/ -docs/.doctrees -docs-v2/_sources/ -docs-v2/.doctrees .eggs/ *.egg-info/ *.egg @@ -23,6 +19,7 @@ venv*/ .coverage* cov_* coverage.xml +reports/ # due to using tox and pytest .tox diff --git a/README.md b/README.md old mode 100755 new mode 100644 index 0921919eb..2d0638e7d --- a/README.md +++ b/README.md @@ -1,34 +1,46 @@ -# Python Slack SDK +

Python Slack SDK

+ +

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

The Slack platform offers several APIs to build apps. Each Slack API delivers part of the capabilities from the platform, so that you can pick just those that fit for your needs. This SDK offers a corresponding package for each of Slackโ€™s APIs. They are small and powerful when used independently, and work seamlessly when used together, too. -**Comprehensive documentation on using the Slack Python can be found at [https://slack.dev/python-slack-sdk/](https://slack.dev/python-slack-sdk/)** +**Comprehensive documentation on using the Slack Python can be found at [https://docs.slack.dev/tools/python-slack-sdk/](https://docs.slack.dev/tools/python-slack-sdk/)** -[![pypi package][pypi-image]][pypi-url] -[![Build Status][build-image]][build-url] -[![Python Version][python-version]][pypi-url] -[![codecov][codecov-image]][codecov-url] -[![contact][contact-image]][contact-url] +--- Whether you're building a custom app for your team, or integrating a third party service into your Slack workflows, Slack Developer Kit for Python allows you to leverage the flexibility of Python to get your project up and running as quickly as possible. The **Python Slack SDK** allows interaction with: -- `slack_sdk.web`: for calling the Slack Web API methods ([API Docs site][api-methods]) -- `slack_sdk.webhook`: for utilizing the Incoming Webhooks and `response_url`s in payloads -- `slack_sdk.signature`: for verifying incoming requests from the Slack API server -- `slack_sdk.socket_mode`: for receiving and sending messages over [Socket Mode](https://api.slack.com/socket-mode) connections -- `slack_sdk.audit_logs`: for utilizing [Audit Logs APIs](https://api.slack.com/admins/audit-logs) -- `slack_sdk.scim`: for utilizing [SCIM APIs](https://api.slack.com/admins/scim) -- `slack_sdk.oauth`: for implementing the Slack OAuth flow -- `slack_sdk.models`: for constructing UI components using easy-to-use builders +- `slack_sdk.web`: for calling the [Web API methods][api-methods] +- `slack_sdk.webhook`: for utilizing the [Incoming Webhooks](https://docs.slack.dev/messaging/sending-messages-using-incoming-webhooks/) and [`response_url`s in payloads](https://docs.slack.dev/interactivity/handling-user-interaction/#message_responses) +- `slack_sdk.signature`: for [verifying incoming requests from the Slack API server](https://docs.slack.dev/authentication/verifying-requests-from-slack/) +- `slack_sdk.socket_mode`: for receiving and sending messages over [Socket Mode](https://docs.slack.dev/apis/events-api/using-socket-mode/) connections +- `slack_sdk.audit_logs`: for utilizing [Audit Logs APIs](https://docs.slack.dev/admins/audit-logs-api/) +- `slack_sdk.scim`: for utilizing [SCIM APIs](https://docs.slack.dev/admins/scim-api/) +- `slack_sdk.oauth`: for implementing the [Slack OAuth flow](https://docs.slack.dev/authentication/installing-with-oauth/) +- `slack_sdk.models`: for constructing [Block Kit](https://docs.slack.dev/block-kit/) UI components using easy-to-use builders - `slack_sdk.rtm`: for utilizing the [RTM API][rtm-docs] -If you want to use our [Events API][events-docs] and Interactivity features, please check the [Bolt for Python][bolt-python] library. Details on the Tokens and Authentication can be found in our [Auth Guide](https://slack.dev/python-slack-sdk/installation/). +If you want to use our [Events API][events-docs] and Interactivity features, please check the [Bolt for Python][bolt-python] library. Details on the Tokens and Authentication can be found in our [Auth Guide](https://docs.slack.dev/tools/python-slack-sdk/installation/). ## slackclient is in maintenance mode -Are you looking for [slackclient](https://pypi.org/project/slackclient/)? The website is live [here](https://slack.dev/python-slackclient/) just like before. However, the slackclient project is in maintenance mode now and this [`slack_sdk`](https://pypi.org/project/slack-sdk/) is the successor. If you have time to make a migration to slack_sdk v3, please follow [our migration guide](https://slack.dev/python-slack-sdk/v3-migration/) to ensure your app continues working after updating. +Are you looking for [slackclient](https://pypi.org/project/slackclient/)? The slackclient project is in maintenance mode now and this [`slack_sdk`](https://pypi.org/project/slack-sdk/) is the successor. If you have time to make a migration to slack_sdk v3, please follow [our migration guide](https://docs.slack.dev/tools/python-slack-sdk/v3-migration/) to ensure your app continues working after updating. ## Table of contents @@ -54,7 +66,7 @@ Are you looking for [slackclient](https://pypi.org/project/slackclient/)? The we --- -This library requires Python 3.6 and above. If you require Python 2, please use our [SlackClient - v1.x][slackclientv1]. If you're unsure how to check what version of Python you're on, you can check it using the following: +This library requires Python 3.7 and above. If you're unsure how to check what version of Python you're on, you can check it using the following: > **Note:** You may need to use `python3` before your commands to ensure you use the correct Python path. e.g. `python3 --version` @@ -78,15 +90,15 @@ $ pip install slack_sdk --- -We've created this [tutorial](/tutorial) to build a basic Slack app in less than 10 minutes. It requires some general programming knowledge, and Python basics. It focuses on the interacting with Slack's Web and RTM API. Use it to give you an idea of how to use this SDK. +We've created this [tutorial](https://github.com/slackapi/python-slack-sdk/tree/main/tutorial) to build a basic Slack app in less than 10 minutes. It requires some general programming knowledge, and Python basics. It focuses on the interacting with the Slack Web API and RTM API. Use it to give you an idea of how to use this SDK. -**[Read the tutorial to get started!](/tutorial)** +**[Read the tutorial to get started!](https://github.com/slackapi/python-slack-sdk/tree/main/tutorial)** ### Basic Usage of the Web Client --- -Slack provide a Web API that gives you the ability to build applications that interact with Slack in a variety of ways. This Development Kit is a module based wrapper that makes interaction with that API easier. We have a basic example here with some of the more common uses but a full list of the available methods are available [here][api-methods]. More detailed examples can be found in [our guide](https://slack.dev/python-slack-sdk/web/). +Slack provide a Web API that gives you the ability to build applications that interact with Slack in a variety of ways. This Development Kit is a module based wrapper that makes interaction with that API easier. We have a basic example here with some of the more common uses but a full list of the available methods are available [here][api-methods]. More detailed examples can be found in [our guide](https://docs.slack.dev/tools/python-slack-sdk/web/). #### Sending a message to Slack @@ -107,13 +119,16 @@ except SlackApiError as e: assert e.response["ok"] is False assert e.response["error"] # str like 'invalid_auth', 'channel_not_found' print(f"Got an error: {e.response['error']}") + # Also receive a corresponding status_code + assert isinstance(e.response.status_code, int) + print(f"Received a response status_code: {e.response.status_code}") ``` Here we also ensure that the response back from Slack is a successful one and that the message is the one we sent by using the `assert` statement. #### Uploading files to Slack -We've changed the process for uploading files to Slack to be much easier and straight forward. You can now just include a path to the file directly in the API call and upload it that way. You can find the details on this api call [here][files.upload] +We've changed the process for uploading files to Slack to be much easier and straight forward. You can now just include a path to the file directly in the API call and upload it that way. ```python import os @@ -124,7 +139,7 @@ client = WebClient(token=os.environ['SLACK_BOT_TOKEN']) try: filepath="./tmp.txt" - response = client.files_upload(channels='#random', file=filepath) + response = client.files_upload_v2(channel='C0123456789', file=filepath) assert response["file"] # the uploaded file except SlackApiError as e: # You will get a SlackApiError if "ok" is False @@ -133,6 +148,8 @@ except SlackApiError as e: print(f"Got an error: {e.response['error']}") ``` +More details on the `files_upload_v2` method can be found [here][files_upload_v2]. + ### Async usage `AsyncWebClient` in this SDK requires [AIOHttp][aiohttp] under the hood for asynchronous requests. @@ -250,7 +267,7 @@ print(response) If you're migrating from slackclient v2.x of slack_sdk to v3.x, Please follow our migration guide to ensure your app continues working after updating. -**[Check out the Migration Guide here!](https://slack.dev/python-slack-sdk/v3-migration/)** +**[Check out the Migration Guide here!](https://docs.slack.dev/tools/python-slack-sdk/v3-migration/)** ### Migrating from v1 @@ -275,23 +292,14 @@ helpful and collaborative way. -[pypi-image]: https://badge.fury.io/py/slack-sdk.svg -[pypi-url]: https://pypi.org/project/slack-sdk/ -[python-version]: https://img.shields.io/pypi/pyversions/slack-sdk.svg -[build-image]: https://github.com/slackapi/python-slack-sdk/workflows/CI%20Build/badge.svg -[build-url]: https://github.com/slackapi/python-slack-sdk/actions?query=workflow%3A%22CI+Build%22 -[codecov-image]: https://codecov.io/gh/slackapi/python-slack-sdk/branch/main/graph/badge.svg -[codecov-url]: https://codecov.io/gh/slackapi/python-slack-sdk -[contact-image]: https://img.shields.io/badge/contact-support-green.svg -[contact-url]: https://slack.com/support [slackclientv1]: https://github.com/slackapi/python-slackclient/tree/v1 -[api-methods]: https://api.slack.com/methods -[rtm-docs]: https://api.slack.com/rtm -[events-docs]: https://api.slack.com/events-api +[api-methods]: https://docs.slack.dev/reference/methods +[rtm-docs]: https://docs.slack.dev/legacy/legacy-rtm-api/ +[events-docs]: https://docs.slack.dev/apis/events-api/ [bolt-python]: https://github.com/slackapi/bolt-python [pypi]: https://pypi.org/ [gh-issues]: https://github.com/slackapi/python-slack-sdk/issues -[slack-community]: http://slackcommunity.com/ -[files.upload]: https://api.slack.com/methods/files.upload +[slack-community]: https://slackcommunity.com/ +[files_upload_v2]: https://github.com/slackapi/python-slack-sdk/releases/tag/v3.19.0 [aiohttp]: https://aiohttp.readthedocs.io/ -[urllib]: https://docs.python.org/3/library/urllib.request.html + diff --git a/docs-src-v2/.gitignore b/docs-src-v2/.gitignore deleted file mode 100644 index e35d8850c..000000000 --- a/docs-src-v2/.gitignore +++ /dev/null @@ -1 +0,0 @@ -_build diff --git a/docs-src-v2/Makefile b/docs-src-v2/Makefile deleted file mode 100644 index ecbc9e80a..000000000 --- a/docs-src-v2/Makefile +++ /dev/null @@ -1,225 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = ../docs/ - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . -# the i18n builder cannot share the environment and doctrees with the others -I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . - -.PHONY: help -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " applehelp to make an Apple Help Book" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " epub3 to make an epub3" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " texinfo to make Texinfo files" - @echo " info to make Texinfo files and run them through makeinfo" - @echo " gettext to make PO message catalogs" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " xml to make Docutils-native XML files" - @echo " pseudoxml to make pseudoxml-XML files for display purposes" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - @echo " coverage to run coverage check of the documentation (if enabled)" - @echo " dummy to check syntax errors of document sources" - -.PHONY: clean -clean: - rm -rf $(BUILDDIR)/* - -.PHONY: html -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -.PHONY: dirhtml -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -.PHONY: singlehtml -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -.PHONY: pickle -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -.PHONY: json -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -.PHONY: htmlhelp -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -.PHONY: qthelp -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/python-slackclient.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/python-slackclient.qhc" - -.PHONY: applehelp -applehelp: - $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp - @echo - @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." - @echo "N.B. You won't be able to view it unless you put it in" \ - "~/Library/Documentation/Help or install it in your application" \ - "bundle." - -.PHONY: devhelp -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/python-slackclient" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/python-slackclient" - @echo "# devhelp" - -.PHONY: epub -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub." - -.PHONY: epub3 -epub3: - $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 - @echo - @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." - -.PHONY: latex -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." - -.PHONY: latexpdf -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through pdflatex..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -.PHONY: latexpdfja -latexpdfja: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through platex and dvipdfmx..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -.PHONY: text -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -.PHONY: man -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -.PHONY: texinfo -texinfo: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo - @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." - @echo "Run \`make' in that directory to run these through makeinfo" \ - "(use \`make info' here to do that automatically)." - -.PHONY: info -info: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo "Running Texinfo files through makeinfo..." - make -C $(BUILDDIR)/texinfo info - @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." - -.PHONY: gettext -gettext: - $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale - @echo - @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." - -.PHONY: changes -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -.PHONY: linkcheck -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -.PHONY: doctest -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." - -.PHONY: coverage -coverage: - $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage - @echo "Testing of coverage in the sources finished, look at the " \ - "results in $(BUILDDIR)/coverage/python.txt." - -.PHONY: xml -xml: - $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml - @echo - @echo "Build finished. The XML files are in $(BUILDDIR)/xml." - -.PHONY: pseudoxml -pseudoxml: - $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml - @echo - @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." - -.PHONY: dummy -dummy: - $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy - @echo - @echo "Build finished. Dummy builder generates no files." diff --git a/docs-src-v2/_themes/slack/conf.py b/docs-src-v2/_themes/slack/conf.py deleted file mode 100644 index d80caa9d7..000000000 --- a/docs-src-v2/_themes/slack/conf.py +++ /dev/null @@ -1,342 +0,0 @@ -# -*- coding: utf-8 -*- -# -# python-slackclient documentation build configuration file, created by -# sphinx-quickstart on Mon Jun 27 17:36:09 2016. -# -# This file is execfile()d with the current directory set to its -# containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# -import os -import sys -sys.path.insert(0, os.path.abspath('../')) - -# -- General configuration ------------------------------------------------ - -# If your documentation needs a minimal Sphinx version, state it here. -# -# needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.coverage', -] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['../../_templates'] - -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -# -# source_suffix = ['.rst', '.md'] -source_suffix = '.rst' - -# The encoding of source files. -# -# source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'slackclient (Legacy Python Slack SDK)' -copyright = u'2015โ€“ Slack Technologies, LLC and contributors' -author = u'Slack Technologies, LLC and contributors' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = u'1.0' -# The full version, including alpha/beta/rc tags. -release = u'1.0.1' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -# -# today = '' -# -# Else, today_fmt is used as the format for a strftime call. -# -# today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This patterns also effect to html_static_path and html_extra_path -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] - -# The reST default role (used for this markup: `text`) to use for all -# documents. -# -# default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -# -# add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -# -# add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -# -# show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'emacs' - -# A list of ignored prefixes for module index sorting. -# modindex_common_prefix = [] - -# If true, keep warnings as "system message" paragraphs in the built documents. -# keep_warnings = False - -# If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = False - - -# -- Options for HTML output ---------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = "slack" -html_theme_path = ["../../_themes", ] - -highlight_language = "python" - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# -# html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -# html_theme_path = [] - -# The name for this set of Sphinx documents. -# " v documentation" by default. -# -# html_title = u'python-slackclient v1.0.1' - -# A shorter title for the navigation bar. Default is the same as html_title. -# -# html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -# -# html_logo = None - -# The name of an image file (relative to this directory) to use as a favicon of -# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -# -# html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['static'] - -html_context = { - 'css_files': ['static/pygments.css'], -} - -# Add any extra paths that contain custom files (such as robots.txt or -# .htaccess) here, relative to this directory. These files are copied -# directly to the root of the documentation. -# -# html_extra_path = [] - -# If not None, a 'Last updated on:' timestamp is inserted at every page -# bottom, using the given strftime format. -# The empty string is equivalent to '%b %d, %Y'. -# -# html_last_updated_fmt = None - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -# -# html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -# -# html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -# -# html_additional_pages = {} - -# If false, no module index is generated. -# -# html_domain_indices = True - -# If false, no index is generated. -# -# html_use_index = True - -# If true, the index is split into individual pages for each letter. -# -# html_split_index = False - -# If true, links to the reST sources are added to the pages. -# -# html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -# -# html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -# -# html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -# -# html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -# html_file_suffix = None - -# Language to be used for generating the HTML full-text search index. -# Sphinx supports the following languages: -# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' -# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh' -# -# html_search_language = 'en' - -# A dictionary with options for the search language support, empty by default. -# 'ja' uses this config value. -# 'zh' user can custom change `jieba` dictionary path. -# -# html_search_options = {'type': 'default'} - -# The name of a javascript file (relative to the configuration directory) that -# implements a search results scorer. If empty, the default will be used. -# -# html_search_scorer = 'scorer.js' - -# Output file base name for HTML help builder. -htmlhelp_basename = 'python-slackclientdoc' - -# -- Options for LaTeX output --------------------------------------------- - -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # - # 'papersize': 'letterpaper', - - # The font size ('10pt', '11pt' or '12pt'). - # - # 'pointsize': '10pt', - - # Additional stuff for the LaTeX preamble. - # - # 'preamble': '', - - # Latex figure (float) alignment - # - # 'figure_align': 'htbp', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - (master_doc, 'python-slackclient.tex', u'python-slackclient Documentation', - u'Ryan Huber, Jeff Ammons', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -# -# latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -# -# latex_use_parts = False - -# If true, show page references after internal links. -# -# latex_show_pagerefs = False - -# If true, show URL addresses after external links. -# -# latex_show_urls = False - -# Documents to append as an appendix to all manuals. -# -# latex_appendices = [] - -# If false, no module index is generated. -# -# latex_domain_indices = True - - -# -- Options for manual page output --------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'python-slackclient', u'python-slackclient Documentation', - [author], 1) -] - -# If true, show URL addresses after external links. -# -# man_show_urls = False - - -# -- Options for Texinfo output ------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - (master_doc, 'python-slackclient', u'python-slackclient Documentation', - author, 'python-slackclient', 'A basic client for Slack.com, which can optionally connect to the Slack Real Time Messaging (RTM) API.', - 'Miscellaneous'), -] - -# Documents to append as an appendix to all manuals. -# -# texinfo_appendices = [] - -# If false, no module index is generated. -# -# texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -# -# texinfo_show_urls = 'footnote' - -# If true, do not generate a @detailmenu in the "Top" node's menu. -# -# texinfo_no_detailmenu = False diff --git a/docs-src-v2/_themes/slack/layout.html b/docs-src-v2/_themes/slack/layout.html deleted file mode 100644 index d4e2df15b..000000000 --- a/docs-src-v2/_themes/slack/layout.html +++ /dev/null @@ -1,169 +0,0 @@ - - - - - {{ - metatags - }} - - {%- block htmltitle %} - {{ title|striptags|e + " — "|safe + project|e }} - {%- endblock %} {%- macro css() %} - - - - - - {%- endmacro %} - - - - - {{ - css() - }} - {%- block linktags %} - - - {%- endblock %} - - - - - - - - {%- block header %} -
- - - - - - - {{ project }} - - -
- {% endblock %} - -
-
- - - - - -
- -
- {%- block body %} - {{ body }} - {% endblock %} -
-
-
- -
-
- -
-

- ยฉ 2015- Slack Technologies, LLC and contributors -

-
- - - - - diff --git a/docs-src-v2/_themes/slack/localtoc.html b/docs-src-v2/_themes/slack/localtoc.html deleted file mode 100644 index e0ffc3f4f..000000000 --- a/docs-src-v2/_themes/slack/localtoc.html +++ /dev/null @@ -1,4 +0,0 @@ -
Table of Contents?
-
    - {{ toc }} -
\ No newline at end of file diff --git a/docs-src-v2/_themes/slack/relations.html b/docs-src-v2/_themes/slack/relations.html deleted file mode 100644 index e3faec04a..000000000 --- a/docs-src-v2/_themes/slack/relations.html +++ /dev/null @@ -1,19 +0,0 @@ -{# - basic/relations.html - ~~~~~~~~~~~~~~~~~~~~ - - Sphinx sidebar template: relation links. - - :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. - :license: BSD, see LICENSE for details. -#} -{%- if prev %} -

{{ _('Previous topic') }}

-

{{ prev.title }}

-{%- endif %} -{%- if next %} -

{{ _('Next topic') }}

-

{{ next.title }}

-{%- endif %} diff --git a/docs-src-v2/_themes/slack/sidebar.html b/docs-src-v2/_themes/slack/sidebar.html deleted file mode 100644 index 2ca9f37d1..000000000 --- a/docs-src-v2/_themes/slack/sidebar.html +++ /dev/null @@ -1,15 +0,0 @@ -{{ toctree(maxdepth=-1, collapse=False,includehidden=True) }} - - diff --git a/docs-src-v2/_themes/slack/static/default.css_t b/docs-src-v2/_themes/slack/static/default.css_t deleted file mode 100644 index b7221ce92..000000000 --- a/docs-src-v2/_themes/slack/static/default.css_t +++ /dev/null @@ -1,79 +0,0 @@ -a.headerlink { - display: none !important; -} - -h2 { - margin-top: -120px; - padding-top: 120px; -} - -.section-title { - font-size: 2rem; - line-height: 2.5rem; - letter-spacing: -1px; - font-weight: 700; - margin: 0 0 1rem; -} - -nav#api_nav .toctree-l1 { - margin-bottom: 1.5rem; -} - -nav#api_nav #api_sections ul { - list-style: none; - margin: 0; - padding: 0; -} - -nav#api_nav #api_sections ul li.toctree-l1>a { - color: #1264a3; - letter-spacing: 0; - font-size: .8rem; - font-weight: 800; - text-transform: uppercase; - border: none; - padding: 0; -} - -nav#api_nav #api_sections ul li.toctree-l2 { - margin: 0; - padding: 0; -} - -nav#api_nav #api_sections ul li.toctree-l2 a { - color: #1d1c1d; - text-transform: none; - font-weight: inherit; - padding: 0; - display: block; - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - font-size: 15px!important; - line-height:15px; - padding: 4px 8px; - border: 1px solid transparent; - border-radius: 4px; -} - -nav#api_nav #api_sections ul li.toctree-l2 a:hover { - cursor: pointer; - text-decoration: none; - background-color:#e8f5fa; - border-color:#dcf0fb; -} - -nav#api_nav #footer #footer_nav { - font-size: .9375rem; -} - -nav#api_nav #footer #footer_nav a { - border: none; - padding: 0; - color: #616061; -} - -nav#api_nav #footer #footer_nav a:hover { - text-decoration: none; - color: #1c1c1c; -} diff --git a/docs-src-v2/_themes/slack/static/docs.css_t b/docs-src-v2/_themes/slack/static/docs.css_t deleted file mode 100644 index 7f360ac66..000000000 --- a/docs-src-v2/_themes/slack/static/docs.css_t +++ /dev/null @@ -1,34 +0,0 @@ -/* Updates body font */ -body { - font-family: Slack-Lato,appleLogo,sans-serif; -} - -/* Replaces old sidebar styled links */ -.sidebar_menu h5 { - font-size: 0.8rem; - font-weight: 800; - margin-bottom: 3px; -} - -/* Aligns footer navigation to the left of the sidebar */ -.footer_nav { - margin: 0 !important; -} - -/* Styles the signature all nice and pretty <3 */ -#footer_signature { - color:#e01e5a; - font-size:.9rem; - margin-top: 10px; -} - -/* Fixes link hover state */ -a:hover { - text-decoration: underline; -} - -/* Makes footer consistent */ -footer { - background-color: transparent; - border: 0; -} \ No newline at end of file diff --git a/docs-src-v2/_themes/slack/static/pygments.css_t b/docs-src-v2/_themes/slack/static/pygments.css_t deleted file mode 100644 index a94b170ff..000000000 --- a/docs-src-v2/_themes/slack/static/pygments.css_t +++ /dev/null @@ -1,60 +0,0 @@ -.highlight .hll { background-color: #ffffcc } -.highlight { background: #ffffff; } -.highlight .c { color: #999988; font-style: italic } /* Comment */ -.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ -.highlight .k { font-weight: bold } /* Keyword */ -.highlight .o { font-weight: bold } /* Operator */ -.highlight .cm { color: #999988; font-style: italic } /* Comment.Multiline */ -.highlight .cp { color: #999999; font-weight: bold } /* 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 { 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 { font-weight: bold } /* Keyword.Constant */ -.highlight .kd { font-weight: bold } /* Keyword.Declaration */ -.highlight .kn { font-weight: bold } /* Keyword.Namespace */ -.highlight .kp { font-weight: bold } /* Keyword.Pseudo */ -.highlight .kr { font-weight: bold } /* Keyword.Reserved */ -.highlight .kt { color: #445588; font-weight: bold } /* Keyword.Type */ -.highlight .m { color: #009999 } /* Literal.Number */ -.highlight .s { color: #bb8844 } /* Literal.String */ -.highlight .na { color: #008080 } /* Name.Attribute */ -.highlight .nb { color: #999999 } /* Name.Builtin */ -.highlight .nc { color: #445588; font-weight: bold } /* Name.Class */ -.highlight .no { color: #008080 } /* Name.Constant */ -.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 .nn { color: #555555 } /* Name.Namespace */ -.highlight .nt { color: #000080 } /* Name.Tag */ -.highlight .nv { color: #008080 } /* Name.Variable */ -.highlight .ow { 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: #bb8844 } /* Literal.String.Backtick */ -.highlight .sc { color: #bb8844 } /* Literal.String.Char */ -.highlight .sd { color: #bb8844 } /* Literal.String.Doc */ -.highlight .s2 { color: #bb8844 } /* Literal.String.Double */ -.highlight .se { color: #bb8844 } /* Literal.String.Escape */ -.highlight .sh { color: #bb8844 } /* Literal.String.Heredoc */ -.highlight .si { color: #bb8844 } /* Literal.String.Interpol */ -.highlight .sx { color: #bb8844 } /* Literal.String.Other */ -.highlight .sr { color: #808000 } /* Literal.String.Regex */ -.highlight .s1 { color: #bb8844 } /* Literal.String.Single */ -.highlight .ss { color: #bb8844 } /* 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 */ diff --git a/docs-src-v2/_themes/slack/theme.conf b/docs-src-v2/_themes/slack/theme.conf deleted file mode 100644 index b13de3cbb..000000000 --- a/docs-src-v2/_themes/slack/theme.conf +++ /dev/null @@ -1,6 +0,0 @@ -[theme] -inherit = default -stylesheet = default.css -show_sphinx = False - -[options] diff --git a/docs-src-v2/about.rst b/docs-src-v2/about.rst deleted file mode 100644 index 17fad157f..000000000 --- a/docs-src-v2/about.rst +++ /dev/null @@ -1,18 +0,0 @@ -============================================== -About -============================================== - -|product_name| -************** - - -Access the Slack Platform from your Python app. |product_name| lets you build on the Slack Web APIs pythonically. - -|product_name| is proudly maintained with ๐Ÿ’– by the Slack Developer Tools team - -- `License`_ -- `Code of Conduct`_ -- `Contributing`_ -- `Contributor License Agreement`_ - -.. include:: metadata.rst \ No newline at end of file diff --git a/docs-src-v2/auth.rst b/docs-src-v2/auth.rst deleted file mode 100644 index 6448bac29..000000000 --- a/docs-src-v2/auth.rst +++ /dev/null @@ -1,136 +0,0 @@ -============================================== -Tokens & Installation -============================================== -.. _handling-tokens: - -Keeping tokens safe -------------------- - -The OAuth token you use to call the Slack API has access to the data on the workspace where it is installed. Depending on the scopes granted to the token, it potentially has the ability to read and write data. Treat these tokens just as you would a password -- don't publish them, don't check them into source code, don't share them with others. - -๐ŸšซAvoid this: - -.. code-block:: python - - token = 'xoxb-111-222-xxxxx' - -We recommend you pass tokens in as environment variables, or persist them in a database that is accessed at runtime. You can add a token to the environment by starting your app as: - -.. code-block:: python - - SLACK_BOT_TOKEN="xoxb-111-222-xxxxx" python myapp.py - -Then retrieve the key with: - -.. code-block:: python - - import os - SLACK_BOT_TOKEN = os.environ["SLACK_BOT_TOKEN"] - -For additional information, please see our `Safely Storing Credentials `_ page. - -Single Workspace Install ---------------------------------------- -If you're building an application for a single Slack workspace, there's no need to build out the entire OAuth flow. - -Once you've setup your features, click on the **Install App to Team** button found on the **Install App** page. -If you add new permission scopes or Slack app features after an app has been installed, you must reinstall the app to -your workspace for changes to take effect. - -For additional information, see the `Installing Apps `_ of our `Building Slack apps `_ page. - -Multiple Workspace Install -------------------------------------------------------- -If you intend for an app to be installed on multiple Slack workspaces, you will need to handle this installation via the industry-standard OAuth protocol. You can read more about `how Slack handles Oauth `_. - -(The OAuth exchange is facilitated via HTTP and requires a webserver; in this example, we'll use `Flask `_.) - -To configure your app for OAuth, you'll need a client ID, a client secret, and a set of one or more scopes that will be applied to the token once it is granted. The client ID and client secret are available from your `app's configuration page `_. The scopes are determined by the functionality of the app -- every method you wish to access has a corresponding scope and your app will need to request that scope in order to be able to access the method. Review Slack's `full list of OAuth scopes `_. - -.. code-block:: python - - import os - from slack import WebClient - from flask import Flask, request - - client_id = os.environ["SLACK_CLIENT_ID"] - client_secret = os.environ["SLACK_CLIENT_SECRET"] - oauth_scope = os.environ["SLACK_SCOPES"] - - app = Flask(__name__) - -**The OAuth initiation link** - -To begin the OAuth flow that will install your app on a workspace, you'll need to provide the user with a link to Slack's OAuth page. This can be a simple link to ``https://slack.com/oauth/v2/authorize`` with ``scope`` and ``client_id`` query parameters, or you can use our pre-built `Add to Slack button `_ to do all the work for you. - -This link directs the user to Slack's OAuth acceptance page, where the user will review and accept or refuse the permissions your app is requesting as defined by the scope(s). - -.. code-block:: python - - @app.route("/slack/install", methods=["GET"]) - def pre_install(): - state = "randomly-generated-one-time-value" - return '' \ - 'Add to Slack' - -**The OAuth completion page** - -Once the user has agreed to the permissions you've requested, Slack will redirect the user to your auth completion page, which includes a ``code`` query string param. You'll use the ``code`` param to call the ``oauth.v2.access`` `endpoint `_ that will finally grant you the token. - -.. code-block:: python - - @app.route("/slack/oauth_redirect", methods=["GET"]) - def post_install(): - # Verify the "state" parameter - - # Retrieve the auth code from the request params - code_param = request.args['code'] - - # An empty string is a valid token for this request - client = WebClient() - - # Request the auth tokens from Slack - response = client.oauth_v2_access( - client_id=client_id, - client_secret=client_secret, - code=code_param - ) - -A successful request to ``oauth.v2.access`` will yield a JSON payload with at least one token, a bot token that begins with ``xoxb``. - -.. code-block:: python - - @app.route("/slack/oauth_redirect", methods=["GET"]) - def post_install(): - # Verify the "state" parameter - - # Retrieve the auth code from the request params - code_param = request.args['code'] - - # An empty string is a valid token for this request - client = WebClient() - - # Request the auth tokens from Slack - response = client.oauth_v2_access( - client_id=client_id, - client_secret=client_secret, - code=code_param - ) - print(response) - - # Save the bot token to an environmental variable or to your data store - # for later use - os.environ["SLACK_BOT_TOKEN"] = response['access_token'] - - # Don't forget to let the user know that OAuth has succeeded! - return "Installation is completed!" - - if __name__ == "__main__": - app.run("localhost", 3000) - -Once your user has completed the OAuth flow, you'll be able to use the provided tokens to call any of Slack's API methods that require an access token. - -See the `Basic Usage <./basic_usage.html>`_ section of this documentation for usage examples. - -.. include:: metadata.rst diff --git a/docs-src-v2/basic_usage.rst b/docs-src-v2/basic_usage.rst deleted file mode 100644 index 163405b0a..000000000 --- a/docs-src-v2/basic_usage.rst +++ /dev/null @@ -1,493 +0,0 @@ -.. _web-api-examples: - -============================================== -Basic Usage -============================================== - -The Slack Web API allows you to build applications that interact with Slack in more complex ways than the integrations we provide out of the box. - -Access Slack's API methods requires an OAuth token -- see the `Tokens & Authentication `_ section for more on how Slack uses OAuth tokens as well as best practices. - -`Each of these API methods `_ is fully documented on our developer site at api.slack.com - -Sending a message ------------------------ -One of the primary uses of Slack is posting messages to a channel using the channel ID or as a DM to another person using their user ID. This method will handle either a channel ID or a user ID passed to the ``channel`` parameter. - -.. code-block:: python - - import logging - logging.basicConfig(level=logging.DEBUG) - - import os - from slack import WebClient - from slack.errors import SlackApiError - - slack_token = os.environ["SLACK_API_TOKEN"] - client = WebClient(token=slack_token) - - try: - response = client.chat_postMessage( - channel="C0XXXXXX", - text="Hello from your app! :tada:" - ) - except SlackApiError as e: - # You will get a SlackApiError if "ok" is False - assert e.response["error"] # str like 'invalid_auth', 'channel_not_found' - -Sending an ephemeral message, which is only visible to an assigned user in a specified channel, is nearly the same -as sending a regular message, but with an additional ``user`` parameter. - -.. code-block:: python - - import os - from slack import WebClient - - slack_token = os.environ["SLACK_API_TOKEN"] - client = WebClient(token=slack_token) - - response = client.chat_postEphemeral( - channel="C0XXXXXX", - text="Hello silently from your app! :tada:", - user="U0XXXXXXX" - ) - -See `chat.postEphemeral `_ for more info. - --------- - -Formatting with Block Kit ------------------------------- -Messages posted from apps can contain more than just text, though. They can include full user interfaces composed of `blocks `_. - -The chat.postMessage method takes an optional blocks argument that allows you to customize the layout of a message. Blocks specified in a single object literal, so just add additional keys for any optional argument. - -To send a message to a channel, use the channel's ID. For IMs, use the user's ID. - -.. code-block:: python - - client.chat_postMessage( - channel="C0XXXXXX", - blocks=[ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "Danny Torrence left the following review for your property:" - } - }, - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": " \n :star: \n Doors had too many axe holes, guest in room " + - "237 was far too rowdy, whole place felt stuck in the 1920s." - }, - "accessory": { - "type": "image", - "image_url": "https://images.pexels.com/photos/750319/pexels-photo-750319.jpeg", - "alt_text": "Haunted hotel image" - } - }, - { - "type": "section", - "fields": [ - { - "type": "mrkdwn", - "text": "*Average Rating*\n1.0" - } - ] - } - ] - ) - -**Note:** You can use the `Block Kit Builder `_ to prototype your message's look and feel. - --------- - -Threading Messages ------------------- -Threaded messages are a way of grouping messages together to provide greater context. You can reply to a thread or start a new threaded conversation by simply passing the original message's ``ts`` ID in the ``thread_ts`` attribute when posting a message. If you're replying to a threaded message, you'll pass the `thread_ts` ID of the message you're replying to. - -A channel or DM conversation is a nearly linear timeline of messages exchanged between people, bots, and apps. When one of these messages is replied to, it becomes the parent of a thread. By default, threaded replies do not appear directly in the channel, instead relegated to a kind of forked timeline descending from the parent message. - -.. code-block:: python - - response = client.chat_postMessage( - channel="C0XXXXXX", - thread_ts="1476746830.000003", - text="Hello from your app! :tada:" - ) - -By default, ``reply_broadcast`` is set to ``False``. To indicate your reply is germane to all members of a channel, and therefore a notification of the reply should be posted in-channel, set the ``reply_broadcast`` to ``True``. - -.. code-block:: python - - response = client.chat_postMessage( - channel="C0XXXXXX", - thread_ts="1476746830.000003", - text="Hello from your app! :tada:", - reply_broadcast=True - ) - -**Note:** While threaded messages may contain attachments and message buttons, when your reply is broadcast to the -channel, it'll actually be a reference to your reply, not the reply itself. -So, when appearing in the channel, it won't contain any attachments or message buttons. Also note that updates and -deletion of threaded replies works the same as regular messages. - -See the `Threading messages together `_ -article for more information. - --------- - -Updating a message ----------------------------------- -Let's say you have a bot which posts the status of a request. When that request changes, you'll want to update the message to reflect it's state. - -.. code-block:: python - - response = client.chat_update( - channel="C0XXXXXX", - ts="1476746830.000003", - text="updates from your app! :tada:" - ) - -See `chat.update `_ for formatting options and some special considerations when calling this with a bot user. - --------- - -Deleting a message -------------------- -Sometimes you need to delete things. - -.. code-block:: python - - response = client.chat_delete( - channel="C0XXXXXX", - ts="1476745373.000002" - ) - -See `chat.delete `_ for more info. - --------- - -Opening a modal ----------------------------------- -Modals allow you to collect data from users and display dynamic information in a focused surface. - -Modals use the same blocks that compose messages with the addition of an `input` block. - -.. code-block:: python - - # This module is available since v2.6 - from slack.signature import SignatureVerifier - signature_verifier = SignatureVerifier(os.environ["SLACK_SIGNING_SECRET"]) - - from flask import Flask, request, make_response - app = Flask(__name__) - - @app.route("/slack/events", methods=["POST"]) - def slack_app(): - if not signature_verifier.is_valid_request(request.get_data(), request.headers): - return make_response("invalid request", 403) - - if "payload" in request.form: - payload = json.loads(request.form["payload"]) - - if payload["type"] == "shortcut" \ - and payload["callback_id"] == "open-modal-shortcut": - # Open a new modal by a global shortcut - try: - api_response = client.views_open( - trigger_id=payload["trigger_id"], - view={ - "type": "modal", - "callback_id": "modal-id", - "title": { - "type": "plain_text", - "text": "Awesome Modal" - }, - "submit": { - "type": "plain_text", - "text": "Submit" - }, - "close": { - "type": "plain_text", - "text": "Cancel" - }, - "blocks": [ - { - "type": "input", - "block_id": "b-id", - "label": { - "type": "plain_text", - "text": "Input label", - }, - "element": { - "action_id": "a-id", - "type": "plain_text_input", - } - } - ] - } - ) - return make_response("", 200) - except SlackApiError as e: - code = e.response["error"] - return make_response(f"Failed to open a modal due to {code}", 200) - - if payload["type"] == "view_submission" \ - and payload["view"]["callback_id"] == "modal-id": - # Handle a data submission request from the modal - submitted_data = payload["view"]["state"]["values"] - print(submitted_data) # {'b-id': {'a-id': {'type': 'plain_text_input', 'value': 'your input'}}} - return make_response("", 200) - - return make_response("", 404) - - if __name__ == "__main__": - # export SLACK_SIGNING_SECRET=*** - # export SLACK_API_TOKEN=xoxb-*** - # export FLASK_ENV=development - # python3 app.py - app.run("localhost", 3000) - -See `views.open `_ more details and additional parameters. - -Also, to run the above example, the following `Slack app configurations `_ are required. - -* Enable **Interactivity** with a valid Request URL: ``https://{your-public-domain}/slack/events`` -* Add a global shortcut with the Callback ID: ``open-modal-shortcut`` - --------- - -Updating and pushing modals ------------------------------- -You can dynamically update a view inside of a modal by calling `views.update` and passing the view ID returned in the previous `views.open` call. - -.. code-block:: python - - private_metadata = "any str data you want to store" - response = client.views_update( - view_id=payload["view"]["id"], - hash=payload["view"]["hash"], - view={ - "type": "modal", - "callback_id": "modal-id", - "private_metadata": private_metadata, - "title": { - "type": "plain_text", - "text": "Awesome Modal" - }, - "submit": { - "type": "plain_text", - "text": "Submit" - }, - "close": { - "type": "plain_text", - "text": "Cancel" - }, - "blocks": [ - { - "type": "input", - "block_id": "b-id", - "label": { - "type": "plain_text", - "text": "Input label", - }, - "element": { - "action_id": "a-id", - "type": "plain_text_input", - } - } - ] - } - ) - -See `views.update `_ for more info. - -If you want to push a new view onto the modal instead of updating an existing view, reference the `views.push `_ documentation. - --------- - -Emoji reactions ---------------------------------------- -You can quickly respond to any message on Slack with an emoji reaction. Reactions can be used for any purpose: voting, checking off to-do items, showing excitement -โ€” or just for fun. - -This method adds a reaction (emoji) to an item (``file``, ``file comment``, ``channel message``, ``group message``, or ``direct message``). One of file, file_comment, or the combination of channel and timestamp must be specified. - -.. code-block:: python - - response = client.reactions_add( - channel="C0XXXXXXX", - name="thumbsup", - timestamp="1234567890.123456" - ) - -Removing an emoji reaction is basically the same format, but you'll use ``reactions.remove`` instead of ``reactions.add`` - -.. code-block:: python - - response = client.reactions_remove( - channel="C0XXXXXXX", - name="thumbsup", - timestamp="1234567890.123456" - ) - - -See `reactions.add `_ and `reactions.remove `_ for more info. - --------- - -Listing public channels ---------------------------- -At some point, you'll want to find out what channels are available to your app. This is how you get that list. - -.. code-block:: python - - response = client.conversations_list(types="public_channel") - -Archived channels are included by default. You can exclude them by passing ``exclude_archived=1`` to your request. - -.. code-block:: python - - response = client.conversations_list(exclude_archived=1) - -See `conversations.list `_ for more info. - --------- - -Getting a channel's info -------------------------- -Once you have the ID for a specific channel, you can fetch information about that channel. - -.. code-block:: python - - response = client.conversations_info(channel="C0XXXXXXX") - -See `conversations.info `_ for more info. - --------- - -Joining a channel ------------------- -Channels are the social hub of most Slack teams. Here's how you hop into one: - -.. code-block:: python - - response = client.conversations_join(channel="C0XXXXXXY") - -If you are already in the channel, the response is slightly different. -``already_in_channel`` will be true, and a limited ``channel`` object will be returned. Bot users cannot join a channel on their own, they need to be invited by another user. - -See `conversations.join `_ for more info. - --------- - -Leaving a channel ------------------- -Maybe you've finished up all the business you had in a channel, or maybe you -joined one by accident. This is how you leave a channel. - -.. code-block:: python - - response = client.conversations_leave(channel="C0XXXXXXX") - -See `conversations.leave `_ for more info. - --------- - -Listing team members --------------------- - -.. code-block:: python - - response = client.users_list() - users = response["members"] - user_ids = list(map(lambda u: u["id"], users)) - -See `users.list `_ for more info. - - --------- - -Uploading files ---------------- - -.. code-block:: python - - response = client.files_upload( - channels="C3UKJTQAC", - file="files.pdf", - title="Test upload" - ) - -See `files.upload `_ for more info. - --------- - -Calling any API methods --------------------------- - -This library covers all the public endpoints as the methods in ``WebClient``. That said, you may see a bit delay of the library release. When you're in a hurry, you can directly use ``api_call`` method as below. - -.. code-block:: python - - import os - from slack import WebClient - - client = WebClient(token=os.environ['SLACK_API_TOKEN']) - response = client.api_call( - api_method='chat.postMessage', - json={'channel': '#random','text': "Hello world!"} - ) - assert response["message"]["text"] == "Hello world!" - - --------- - -Web API Rate Limits --------------------- -When posting messages to a channel, Slack allows applications to send no more than one message per channel per second. We allow bursts over that limit for short periods. However, if your app continues to exceed the limit over a longer period of time it will be rate limited. Different API methods have other rate limits -- be sure to `check the limits `_ and test that your application has a graceful fallback if it should hit those limits. - -If you go over these limits, Slack will start returning a HTTP 429 Too Many Requests error, a JSON object containing the number of calls you have been making, and a Retry-After header containing the number of seconds until you can retry. - -Here's a very basic example of how one might deal with rate limited requests. - -.. code-block:: python - - import os - import time - from slack import WebClient - from slack.errors import SlackApiError - - client = WebClient(token=os.environ["SLACK_API_TOKEN"]) - - # Simple wrapper for sending a Slack message - def send_slack_message(channel, message): - return client.chat_postMessage( - channel=channel, - text=message - ) - - # Make the API call and save results to `response` - channel = "#random" - message = "Hello, from Python!" - # Do until being rate limited - while True: - try: - response = send_slack_message(channel, message) - except SlackApiError as e: - if e.response.status_code == 429: - # The `Retry-After` header will tell you how long to wait before retrying - delay = int(e.response.headers['Retry-After']) - print(f"Rate limited. Retrying in {delay} seconds") - time.sleep(delay) - response = send_slack_message(channel, message) - else: - # other errors - raise e - -See the documentation on `Rate Limiting `_ for more info. - -.. include:: metadata.rst diff --git a/docs-src-v2/changelog.rst b/docs-src-v2/changelog.rst deleted file mode 100644 index 19c504c53..000000000 --- a/docs-src-v2/changelog.rst +++ /dev/null @@ -1,451 +0,0 @@ -============================================== -Changelog -============================================== - -v3.0.0 (2020-11-09) -------------------- - -This is the first stable version of `slack_sdk `_ v3. The remarkable updates in this major version are: - -* Newly added OAuth flow support -* Better Async/sync separation for ``WebClient`` and ``WebhookClient`` -* Renamed packages (from ``slack`` to ``slack_sdk``) with deprecation warnings - -Refer to `v3.0.0 milestone `_ and `the website `_ for details. If you're a slackclient user, the migration guide for `slackclient` v2.x users is available at https://slack.dev/python-slack-sdk/v3-migration/ - -v2.9.3 (2020-10-20) -------------------- - -Refer to `v2.9.3 milestone `_ to know the complete list of the issues resolved by this release. - -**Updates** - -1. [Block Kit] #851 #852 Set default_type for HeaderBlock text - Thanks @fwump38 -2. [Block Kit] #853 #854 Enable to use input blocks in Home tab views - Thanks @fwump38 -3. [RTMClient] #857 #846 RTMClient does not pass timeout value to WebClient - Thanks @Luden @seratch - -v2.9.2 (2020-10-09) -------------------- - -Refer to `v2.9.2 milestone `_ to know the complete list of the issues resolved by this release. - -**Updates** - -1. [Block Kit] #841 Dispatch Action in Input blocks - Thanks @seratch -2. [WebClient] #838 Add apps.event.authorizations.list and other APIs - Thanks @seratch -3. [WebClient][WebhookClient] #829 Improve error body parser to handle no charset responses - Thanks @adamchainz @seratch -4. [Block Kit] #824 Correct text field validation in Header blocks - Thanks @seratch - -v2.9.1 (2020-09-23) -------------------- - -Refer to `v2.9.1 milestone `_ to know the complete list of the issues resolved by this release. - -**Updates** - -1. [WebClient][WebhookClient] #820 #821 #822 The proxy option in WebClient/WebhookClient no longer works - Thanks @seratch - -v2.9.0 (2020-09-17) -------------------- - -Refer to `v2.9.0 milestone `_ to know the complete list of the issues resolved by this release. - -**Updates** - -1. [WebClient] #811 Add workflows.* API support - Thanks @misscoded -2. [WebClient] #810 #809 Only set default filename in files_upload if file is an instance of str - Thanks @csaska - -v2.8.2 (2020-09-04) -------------------- - -Refer to `v2.8.2 milestone `_ to know the complete list of the issues resolved by this release. - -**Updates** - -1. [WebClient] #795 #794 Add admin.conversations.* API methods in WebClient/AsyncWebClient - Thanks @ruberVulpes -2. [WebClient] #796 Fix a link to the Static options documentation - Thanks @Jamim - -v2.8.1 (2020-08-28) -------------------- - -Refer to `v2.8.1 milestone `_ to know the complete list of the issues resolved by this release. - -**Updates** - -1. [WebClient] #778 #779 Adding support for View objects for views.push/update/publish - Thanks @ruberVulpes -2. [WebClient] #786 Fix admin.conversations.restrictAccess.* methods to match documentation - Thanks @ruberVulpes - -v2.8.0 (2020-08-06) -------------------- - -Refer to `v2.8.0 milestone `_ to know the complete list of the issues resolved by this release. - -**New Features** - -1. [WebClient] #765 #766 Introduce AsyncWebClient/AsyncWebhookClient providing coroutines - Thanks @seratch -2. [Block Kit] #767 #768 Add "header" block support - Thanks @mwbrooks - -**Updates** - -1. [WebClient] #738 Add HTTP_PROXY, HTTPS_PROXY env variable support in async WebClient - Thanks @iamtofr @seratch -2. [WebClient] #769 #773 Enable User-Agent to have additional info part - Thanks @seratch -3. [WebClient] #770 #771 Fix a bug where ``files.upload``'s file param doesn't accept bytes data - Thanks @seratch - -v2.7.3 (2020-07-20) -------------------- - -Refer to `v2.7.3 milestone `_ to know the complete list of the issues resolved by this release. - -**Updates** - -1. [WebClient] #754 Fix #729 Add admin.conversations.restrictAccess.*, conversations.mark API - Thanks @ruberVulpes @kian2attari -2. [WebClient] #758 Fix #757 Add admin.usergroups.addTeams, calls.participants.remove API - Thanks @seratch -3. [WebClient] #727 Fix #645 Unclosed client session - Thanks @NoAnyLove @jourdanrodrigues -4. [WebClient] #745 Fix #744 a validation logic bug in DatePickerElement - Thanks @dzudi941 -5. [WebClient] #752 Fix #733 Better error handling when getting TimeoutError in RTMClient#start() - Thanks @liorblob @seratch -6. [WebClient] #751 Fix #718 by handling unexpected response body format - Thanks @jeffbuswell @seratch - -v2.7.2 (2020-06-23) -------------------- - -Refer to `v2.7.2 milestone `_ to know the complete list of the issues resolved by this release. - -**Updates** - -1. [WebClient] Fix #728 by adding bytearray support in files_upload (sync mode) - Thanks @sofya-salmanova @seratch -2. [WebClient] #726 Fix InputBlock.hint validation failure - Thanks @jourdanrodrigues -3. [WebClient] #723 Correct the default value of InputBlock.label, hint - Thanks @jourdanrodrigues - -v2.7.1 (2020-06-04) -------------------- - -This release includes the fixes for regression bugs in `WebClient` since v2.6.0. Refer to `v2.7.1 milestone `_ to know the complete list of the issues resolved by this release. - -**Updates** - -1. [WebClient] #716 #712 Support timeout in sync sync web clients - Thanks @DanialErfanian @seratch -2. [WebClient] #713 Support custom SSL context in sync sync web clients - Thanks @austinbutler -3. [WebClient] #715 #714 Support proxy in sync sync web clients - Thanks @austinbutler @seratch - -v2.7.0 (2020-06-02) -------------------- - -Refer to `v2.7.0 milestone `_ to know the complete list of the issues resolved by this release. - -**New Features** - -1. [WebhookClient] #707 #270 #531 Add `WebhookClient` for Incoming Webhooks & response_url - Thanks @seratch @chubz @Ambro17 - -**Updates** - -1. [WebClient] #704 #695 Add `calls_*` methods to `WebClient` and `CallBlock` in Block Kit classes - Thanks @seratch -2. [WebClient] #710 #536 Allow Tokens to be specified per request - Thanks @seratch -3. [WebClient] #709 #708 Add default_to_current_conversation in conversations_select elements - Thanks @seratch - -v2.6.2 (2020-05-28) -------------------- - -Refer to `v2.6.2 milestone `_ to know the complete details of this release. - -**Updates** - -1. [WebClient] #705 WebClient's paginated API calls may fail with no params - Thanks @seratch - -v2.6.1 (2020-05-24) -------------------- - -This patch release is a quick fix for #701, a major issue that affected RTMClient users in v2.6.0. The malfunction was introduced by #667 trying to address #558 #619. Those issues were reopened and will be resolved by another approach. Refer to `v2.6.1 milestone `_ to know the complete list of the issues resolved by this release. - -**Updates** - -1. [RTMClient] #701 RTMClient drops some messages when they come in rapid succession - Thanks @pbrackin @seratch - -v2.6.0 (2020-05-21) -------------------- - -Refer to `v2.6.0 milestone `_ to know the complete list of the issues resolved by this release. - -**New Features** - -1. [Block Kit] #659 Add complete supports for Block Kit components and fixed a few existing bugs as well (#500 #519 #623 #632 #635 #639 #676 #699) - Thanks @seratch @diurnalist @ruberVulpes @jeremyschulman @e271828- @RodneyU215 -2. [Signature] #686 Add slack.signature.SignatureVerifier for request verification - Thanks @seratch -3. [WebClient] #682 Add missing Grid admin APIs (`admin.usergroups.*`, `admin.users.*`, `admin.apps.*`) - Thanks @stevengill @seratch - -**Updates** - -1. [WebClient][RTMClient] Fixed a bunch of the currency issues this SDK had (#429 #463 #492 #497 #530 #569 #605 #613 #626 #630 #631 #633 #669) - Thanks @seratch @aaguilartablada @aoberoi @stevengill @marshallino16 -2. [WebClient] #681 #560 Enable using bool values for request parameters - Thanks @roman-kachanovsky @seratch -3. [WebClient] #661 #678 Improve handling of required "ids" parameters (e.g., channel_ids, users) - Thanks @seratch -4. [WebClient] #680 Add non-conversation API deprecation warnings - Thanks @seratch -5. [WebClient] #671 #670 Enable passing None values for request parameters (they used to result in errors) - Thanks @yuji38kwmt @seratch -6. [WebClient] #673 Fix #672 files.upload fails with a filepath containing multi byte chars - Thanks @yuji38kwmt @seratch -7. [WebClient] #656 Fix #594 preview_image for files.remote.add API method is not properly supported - Thanks @Eothred @seratch -8. [Maintenance] #618 Add py.typed file to package distribution - Thanks @JKillian -9. [WebClient] #599 Strip token string parameters of whitespace - Thanks @TheFrozenFire -10. [WebClient] #692 Fix superfluous_charset warnings since v2.4.0 - Thanks @seratch -11. [WebClient] #652 Update oauth_v2_access to include redirect_uri (as optional) - Thanks @tomasreimers - -v2.5.0 (2019-12-09) -------------------- -**New Features** - -1. [WebClient] Adding new oauth.v2.access Web API method. #577 - -v2.4.0 (2019-11-27) -------------------- -**New Features** - -1. [WebClient] Adding new admin.* Web API methods. #571 - -**Updates** -1. [WebClient] We're no longer validating token types for Web API methods. Improves compatibility with granular bot permissions. #568 (Thanks @Smotko) -2. [WebClient] Correcting typos in descriptions #554 (Thanks @phamk) -3. [WebClient] Fixed 'iteracting' typo in library file headers #564 (Thanks @acabey) -4. [Message Builders] Remove value from LinkButtonElement #563 (Thanks @pedroma) - -v2.3.1 (2019-10-29) -------------------- -**Updates** - -1. [WebClient] Fixing a regression that causes the client to close sessions prematurely. #544 (Thanks @fatih-acar!) -2. [WebClient] Adding required missing `view` param to views.update Web API method. #542 - -v2.3.0 (2019-10-22) -------------------- -**New Features** - -1. [WebClient] Adding new views.publish Web API method. #540 - -**Updates** - -1. [WebClient] Some server responses don't return json. Correcting initial assumption. #540 -2. [Maintenance] Add `py.typed` to mark the library to support type hinting #524s - -v2.2.1 (2019-10-08) -------------------- -**Updates** - -1. [Docs] Fix Indentation of Code Snippets in README.md #525 (Thanks @abhishekjiitr) -2. [WebClient] Fix Web Client custom iterator #521 (Thanks @smaeda-ks) -3. [WebClient] Oauth previously failed to pass along credentials properly. This is fixed now. #527 -4. [WebClient] When a SlackApiError occurs we're now passing the entire SlackResponse into the exception. #527 - -v2.2.0 (2019-09-25) -------------------- -**New Features** - -1. [WebClient] Adding new admin and remote files API methods. #501 -2. [WebClient] Adding new view API methods. #517 - -**Updates** - -1. [Message Builders] Update BlockAttachment to not send invalid JSON due to fields attribute #473 (Thanks @paul-griffith) -2. [Docs] Add RTM section for docs v2 #477 (Thanks @shanedewael) -3. [Docs] Fix typo; recieved -> received #478 (Thanks @joakimnordling) -4. [Docs] Fix block kit link & update docs #484 (Thanks @clavin) -5. [RTMClient] Return callback from `RTMClient.run_on` #490 (Thanks @clavin) -6. [Docs] Fix link to Auth Guide in readme #498 (Thanks @asherf) -7. [Docs] Fix missing word and typo #512 (Thanks @marks) -8. [Message Builders] bugfix for value length in button elements #514 (Thanks @avanderm) -9. [Docs] Fixes formatting #515 (Thanks @vpetersson) -10. [Docs] Improve a code snippet on README #516 (Thanks @seratch) -11. [WebClient] Fixed an OAuth Headers bug and made the `token` param optional. #517 - -v2.1.0 (2019-07-01) -------------------- -**New Features** - -1. Type-hinted helper classes for building messages in v2 #400 (Thanks @paul-griffith) - -**Breaking Changes** - -1. [RTMClient] Converted the `RTMClient#typing()` function to async #446 - -**Updates** - -1. [RTMClient] Handle case in which aiohttp closes the websocket due to lack of ping responses. #453 (Thanks @flyte) -2. Modify package identifier in user agent to match v1.x identifier #418 (Thanks @aoberoi) -3. [WebClient] Fixed typo in Scheduled message #428 & #435 (Thanks @splinterific) -4. Transform install_requires of 'aiodns' into extras_require. #440 (Thanks @staticdev) - -**Thank you!!** -To everyone who's opened, commented or reacted to an issue; this project is better because of you! -Thank you for helping the Slack community! - -v2.0.0 (2019-04-29) -------------------- -`Original RFC `_ - -`v2 PR `_ - -**New Features** - -1. Client Decomposition: Weโ€™ve split the client into two. - - a. WebClient: A HTTP client focused on Slack's Web API. - b. RTMClient: A websocket client focused on Slack's RTM API. - -2. RTMClient: Completely redesigned, this client allows you to link your application's callbacks to corresponding Slack events. -3. WebClient: The WebClient now provides built-in methods for Slack's Web API. These methods act as helpers enabling you to focus less on how the request is constructed. Here are a few things that this provides: - - a. Basic information about each method through the docstring. - b. Easy File Uploads: You can now pass in the location of a file and the library will handle opening and retrieving the file object to be transmitted. - c. Token type validation: This gives you better error messaging when you're attempting to consume an api method that your token doesn't have access to. - d. Constructs requests using Slack's preferred HTTP methods and content-types. - -**Breaking Changes:** -If you're migrating from v1.x of slackclient to v2.x, Please follow our migration guide to ensure your app continues working after updating. - -`Check out the Migration Guide here! `_ - -**Thank you!** -This release would not have been possible without the support of our community. Thank you to everyone whoโ€™s contributed to this release. - - -v1.3.1 (2019-02-28) -------------------- - -- Lock websocket-client version to < 0.55.0: temp fix for #385 - - -v1.3.0 (2018-09-11) -------------------- - -## New Features -- Adds support for short lived tokens and automatic token refresh #347 (Thanks @roach!) - -## Other -- update RTM rate limiting comment and error message #308 (Thanks @benoitlavigne!) -- Use logging instead of traceback #309 (Thanks @harlowja!) -- Remove Python 3.3 from test environments #346 (Thanks @roach!) -- Enforced linting when using VSCode. #347 (Thanks @roach!) - - -v1.2.1 (2018-03-26) -------------------- - -- Added rate limit handling for rtm connections (thanks @jayalane!) - - -v1.2.0 (2018-03-20) -------------------- - -- You can now tell the RTM client to automatically reconnect by passing `auto_reconnect=True` - -v1.1.3 (2018-03-01) -------------------- - -- Fixed another API param encoding bug. It encodes things properly now. - -v1.1.2 (2018-01-31) -------------------- - -- Fixed an encoding issue which was encoding some Web API params incorrectly (sorry) - -v1.1.1 (2018-01-30) -------------------- - - - Adds HTTP response headers to `api_call` responses to expose things like rate limit info - - Moves `token` into auth header rather than request params - -v1.1.0 (2017-11-21) -------------------- - - - Aadds new SlackClientError and ResponseParseError types to describe errors - thanks @aoberoi! - - Fix Build Error (#245) - thanks @stasfilin! - - include email as user property (#173) - thanks @acaire! - - Add http reply into slack login and slack connection error (#216) - thanks @harlowja! - - Removed unused exception class (#233) - - Fix rtm_send_message bug (#225) - thanks @kt5356! - - Allow use of custom parameters on rtm_connect() (#210) - thanks @kamushadenes! - - Fix link to rtm.connect docs (#223) - @sampart! - -v1.0.9 (2017-08-31) -------------------- - - - Fixed rtm_send_message ID bug introduced in 1.0.8 - -v1.0.8 (2017-08-31) -------------------- - - - Added rtm.connect support - -v1.0.7 (2017-08-02) -------------------- - - - Fixes an issue where connecting over RTM to large teams may result in โ€œWebsocket URL expiredโ€ errors - - A load of packaging improvements - -v1.0.6 (2017-06-12) -------------------- - - - Added proxy support (thanks @timfeirg!) - - Tidied up docs (thanks @schlueter!) - - Added tox settings for Python 3 testing (thanks @cclauss!) - -v1.0.5 (2017-01-23) -------------------- - - - Allow RTM Channel.send_message to reply to a thread - - Index users by ID instead of Name (non-breaking change) - - Added timeout to api calls. - - Fixed a typo about token access in auth.rst, thanks @kelvintaywl! - - Added Message Threads to the docs - -v1.0.4 (2016-12-15) -------------------- - - - fixed the ability to search for a user by ID - -v1.0.3 (2016-12-13) -------------------- - - - fixed an issue causing RTM connections to fail for large teams - -v1.0.2 (2016-09-22) -------------------- - - - removed unused ping counter - - fixed contributor guidelines links - - updated documentation - - Fix bug preventing API calls requiring a file ID - - Removes files from api_calls before JSON encoding, so the request is properly formatted - -v1.0.1 (2016-03-25) -------------------- - - - fix for __eq__ comparison in channels using '#' in channel name - - added copyright info to the LICENSE file - -v1.0.0 (2016-02-28) -------------------- - - - the ``api_call`` function now returns a decoded JSON object, rather than a JSON encoded string - - some ``api_call`` calls now call actions on the parent server object: - - ``im.open`` - - ``mpim.open``, ``groups.create``, ``groups.createChild`` - - ``channels.create``, `channels.join`` - -v0.18.0 (2016-02-21) --------------------- - - - Moves to use semver for versioning - - Adds support for private groups and MPDMs - - Switches to use requests instead of urllib - - Gets Travis CI integration working - - Fixes some formatting issues so the code will work for python 2.6 - - Cleans up some unused imports, some PEP-8 fixes and a couple bad default args fixes - -v0.17.0 (2016-02-15) --------------------- - - - Fixes the server so that it doesn't add duplicate users or channels to its internal lists, https://github.com/slackapi/python-slackclient/commit/0cb4bcd6e887b428e27e8059b6278b86ee661aaa - - README updates: - - Updates the URLs pointing to Slack docs for configuring authentication, https://github.com/slackapi/python-slackclient/commit/7d01515cebc80918a29100b0e4793790eb83e7b9 - - s/channnels/channels, https://github.com/slackapi/python-slackclient/commit/d45285d2f1025899dcd65e259624ee73771f94bb - - Adds users to the local cache when they join the team, https://github.com/slackapi/python-slackclient/commit/f7bb8889580cc34471ba1ddc05afc34d1a5efa23 - - Fixes urllib py 2/3 compatibility, https://github.com/slackapi/python-slackclient/commit/1046cc2375a85a22e94573e2aad954ba7287c886 - - - -.. include:: metadata.rst \ No newline at end of file diff --git a/docs-src-v2/conf.py b/docs-src-v2/conf.py deleted file mode 100644 index 241164b38..000000000 --- a/docs-src-v2/conf.py +++ /dev/null @@ -1,337 +0,0 @@ -# -*- coding: utf-8 -*- -# -# python-slackclient documentation build configuration file, created by -# sphinx-quickstart on Mon Jun 27 17:36:09 2016. -# -# This file is execfile()d with the current directory set to its -# containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# -import os -import sys -sys.path.insert(0, os.path.abspath('../')) - -# -- General configuration ------------------------------------------------ - -# If your documentation needs a minimal Sphinx version, state it here. -# -# needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.coverage', -] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -# -# source_suffix = ['.rst', '.md'] -source_suffix = '.rst' - -# The encoding of source files. -# -# source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'python-slackclient' -copyright = u'2016, Ryan Huber, Jeff Ammons' -author = u'Ryan Huber, Jeff Ammons' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = u'1.0' -# The full version, including alpha/beta/rc tags. -release = u'1.0.1' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -# -# today = '' -# -# Else, today_fmt is used as the format for a strftime call. -# -# today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This patterns also effect to html_static_path and html_extra_path -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] - -# The reST default role (used for this markup: `text`) to use for all -# documents. -# -# default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -# -# add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -# -# add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -# -# show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -# modindex_common_prefix = [] - -# If true, keep warnings as "system message" paragraphs in the built documents. -# keep_warnings = False - -# If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = False - - -# -- Options for HTML output ---------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -# Use the slack theme -html_theme_path = ["_themes"] -html_theme = "slack" - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# -# html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -# html_theme_path = [] - -# The name for this set of Sphinx documents. -# " v documentation" by default. -# -# html_title = u'python-slackclient v1.0.1' - -# A shorter title for the navigation bar. Default is the same as html_title. -# -# html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -# -# html_logo = None - -# The name of an image file (relative to this directory) to use as a favicon of -# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -# -# html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -# html_static_path = ['_static'] - -# Add any extra paths that contain custom files (such as robots.txt or -# .htaccess) here, relative to this directory. These files are copied -# directly to the root of the documentation. -# -# html_extra_path = [] - -# If not None, a 'Last updated on:' timestamp is inserted at every page -# bottom, using the given strftime format. -# The empty string is equivalent to '%b %d, %Y'. -# -# html_last_updated_fmt = None - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -# -# html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -# -# html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -# -# html_additional_pages = {} - -# If false, no module index is generated. -# -# html_domain_indices = True - -# If false, no index is generated. -# -# html_use_index = True - -# If true, the index is split into individual pages for each letter. -# -# html_split_index = False - -# If true, links to the reST sources are added to the pages. -# -# html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -# -# html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -# -# html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -# -# html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -# html_file_suffix = None - -# Language to be used for generating the HTML full-text search index. -# Sphinx supports the following languages: -# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' -# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh' -# -# html_search_language = 'en' - -# A dictionary with options for the search language support, empty by default. -# 'ja' uses this config value. -# 'zh' user can custom change `jieba` dictionary path. -# -# html_search_options = {'type': 'default'} - -# The name of a javascript file (relative to the configuration directory) that -# implements a search results scorer. If empty, the default will be used. -# -# html_search_scorer = 'scorer.js' - -# Output file base name for HTML help builder. -htmlhelp_basename = 'python-slackclientdoc' - -# -- Options for LaTeX output --------------------------------------------- - -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # - # 'papersize': 'letterpaper', - - # The font size ('10pt', '11pt' or '12pt'). - # - # 'pointsize': '10pt', - - # Additional stuff for the LaTeX preamble. - # - # 'preamble': '', - - # Latex figure (float) alignment - # - # 'figure_align': 'htbp', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - (master_doc, 'python-slackclient.tex', u'python-slackclient Documentation', - u'Ryan Huber, Jeff Ammons', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -# -# latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -# -# latex_use_parts = False - -# If true, show page references after internal links. -# -# latex_show_pagerefs = False - -# If true, show URL addresses after external links. -# -# latex_show_urls = False - -# Documents to append as an appendix to all manuals. -# -# latex_appendices = [] - -# If false, no module index is generated. -# -# latex_domain_indices = True - - -# -- Options for manual page output --------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'python-slackclient', u'python-slackclient Documentation', - [author], 1) -] - -# If true, show URL addresses after external links. -# -# man_show_urls = False - - -# -- Options for Texinfo output ------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - (master_doc, 'python-slackclient', u'python-slackclient Documentation', - author, 'python-slackclient', 'A basic client for Slack.com, which can optionally connect to the Slack Real Time Messaging (RTM) API.', - 'Miscellaneous'), -] - -# Documents to append as an appendix to all manuals. -# -# texinfo_appendices = [] - -# If false, no module index is generated. -# -# texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -# -# texinfo_show_urls = 'footnote' - -# If true, do not generate a @detailmenu in the "Top" node's menu. -# -# texinfo_no_detailmenu = False diff --git a/docs-src-v2/conversations.rst b/docs-src-v2/conversations.rst deleted file mode 100644 index 9c67fff0e..000000000 --- a/docs-src-v2/conversations.rst +++ /dev/null @@ -1,140 +0,0 @@ -.. _conversations_api: - -============================================== -Conversations API -============================================== -The Slack Conversations API provides your app with a unified interface to work with all the channel-like things encountered in Slack; public channels, private channels, direct messages, group direct messages, and our newest channel type, Shared Channels. - - -See `Conversations API `_ docs for more info. - --------- - -Direct messages ---------------------------------------------------------- -The ``conversations_open`` method opens either a 1:1 direct message with a single user or a a multi-person direct message, depending on the number of users supplied to the ``users`` parameter. - -*For public or private channels, use the ``conversations_create`` method.* - -Provide a ``users`` parameter as an array with 1 to 8 user IDs to open or resume a conversation. Providing only 1 ID will create a direct message. Providing more will create a new multi-party DM or resume an existing conversation. - -Subsequent calls to ``conversations_open`` with the same set of users will return the already existing conversation. - -.. code-block:: python - - import os - from slack import WebClient - - client = WebClient(token=os.environ["SLACK_API_TOKEN"]) - response = client.conversations_open(users=["W123456789", "U987654321"]) - -See `conversations.open `_ additional info. - --------- - -Creating channels -------------------------------------- -Creates a new channel, either public or private. The ``name`` parameter is required, may contain numbers, letters, hyphens, and underscores, and must contain fewer than 80 characters. To make the channel private, set the option ``is_private`` parameter to ``True``. - -.. code-block:: python - - import os - from slack import WebClient - from time import time - - client = WebClient(token=os.environ["SLACK_API_TOKEN"]) - channel_name = f"my-private-channel-{round(time())}" - response = client.conversations_create( - name=channel_name, - is_private=True - ) - channel_id = response["channel"]["id"] - response = client.conversations_archive(channel=channel_id) - -See `conversations.create `_ additional info. - --------- - -Getting more information ------------------------------------------ -To retrieve a set of metadata about a channel (public, private, DM, or multi-party DM), use ``conversations_info``. The ``channel`` parameter is required and must be a valid channel ID. The optional ``include_locale`` boolean parameter will return locale data, which may be useful if you wish to return localized responses. The ``include_num_members`` boolean parameter will return the number of people in a channel. - -.. code-block:: python - - import os - from slack import WebClient - - client = WebClient(token=os.environ["SLACK_API_TOKEN"]) - response = client.conversations_info( - channel="C031415926", - include_num_members=1 - ) - -See `conversations.info `_ for more info. - - --------- - -Listing conversations --------------------------------- -To get a list of all the conversations in a workspace, use ``conversations_list``. By default, only public conversations are returned; use the ``types`` parameter specify which types of conversations you're interested in (Note: ``types`` is a string of comma-separated values) - - -.. code-block:: python - - import os - from slack import WebClient - - client = WebClient(token=os.environ["SLACK_API_TOKEN"]) - response = client.conversations_list() - conversations = response["channels"] - -Use the ``types`` parameter to request additional channels, including ``public_channel``, ``private_channel``, ``mpim``, and ``im``. This parameter is a string of comma-separated values. - -.. code-block:: python - - import os - from slack import WebClient - - client = WebClient(token=os.environ["SLACK_API_TOKEN"]) - response = client.conversations_list( - types="public_channel, private_channel" - ) - -See `conversations.list `_ for more info. - - --------- - -Leaving a conversation ------------------------ -To leave a conversation, use ``conversations_leave`` with the required ``channel`` param containing the ID of the channel to leave. - -.. code-block:: python - - import os - from slack import WebClient - - client = WebClient(token=os.environ["SLACK_API_TOKEN"]) - response = client.conversations_leave(channel="C27182818") - -See `conversations.leave `_ for more info. - --------- - -Getting members ------------------------------- -To get a list of the members of a conversation, use ``conversations_members`` with the required ``channel`` parameter. - -.. code-block:: python - - import os - from slack import WebClient - - client = WebClient(token=os.environ["SLACK_API_TOKEN"]) - response = client.conversations_members(channel="C16180339") - user_ids = response["members"] - -See `conversations.members `_ for more info. - -.. include:: metadata.rst diff --git a/docs-src-v2/faq.rst b/docs-src-v2/faq.rst deleted file mode 100644 index 5bcf05273..000000000 --- a/docs-src-v2/faq.rst +++ /dev/null @@ -1,88 +0,0 @@ -============================================== -Frequently Asked Questions -============================================== - -I cannot install slackclient... -********************************* - -We recommend using `virtualenv (venv) `_ to set up your Python runtime. - -.. code-block:: bash - - # Create a dedicated virtual env for running your Python scripts - python -m venv env - - # Run env\Scripts\activate on Windows OS - source env/bin/activate - - # Install slackclient PyPI package - pip install "slackclient>=2.0" - - # Set your token as an env variable (`set` command for Windows OS) - export SLACK_API_TOKEN=xoxb-*** - -Then, verify the following code works on the Python REPL (you can start it by just ``python``). - -.. code-block:: python - - import os - import logging - from slack import WebClient - logging.basicConfig(level=logging.DEBUG) - client = WebClient(token=os.environ["SLACK_API_TOKEN"]) - res = client.api_test() - - -If you encounter an error saying ``AttributeError: module 'slack' has no attribute 'WebClient'``, run ``pip list``. If you find both ``slackclient`` and ``slack`` in the output, try removing ``slack`` by ``pip uninstall slack`` and reinstalling ``slackclient``. - -Should I go with run_async? -**************************** - -For most cases, we recommend going with ``run_async=False`` mode. So, the default is ``False``. - -If your application turns ``run_async`` on, the app should follow right and efficient ways to use `asyncio `_'s non-blocking event loops and `aiohttp `_. Also, consider using async frameworks and their appropriate runtime. Running event loops along with Flask or similar may not be a good fit. - -If you have to simultaneously run ``WebClient`` with ``run_async=True`` outside an event loop for some reason, sharing a single ``WebClient`` instance doesn't work for you. Create an instance every time you run the code. The ``run_async=False`` mode doesn't have such issues. - -I found a bug! -****************** - -That's great! Thank you. Let us know on the `Issue Tracker`_. If you're feeling particularly ambitious, why not submit a `pull request`_ with a bug fix? - -There's a feature missing! -******************************* - -There's always something more that could be added! You can let us know in the `Issue Tracker`_ to start a discussion around the proposed feature, that's a good start. If you're feeling particularly ambitious, why not write the feature yourself, and submit a `pull request`_! We love feedback and we love help and we don't bite. Much. - -How do I contribute? -********************************* - -What an excellent question. First of all, please have a look at our general `contributing guidelines`_. - -All done? Great! While we're super excited to incorporate your new feature, there are a couple of things we want to make sure you've given thought to. - -- Please write unit tests for your new code. But don't **just** aim to increase the test coverage, rather, we expect you - to have written **thoughtful** tests that ensure your new feature will continue to work as expected, and to help future - contributors to ensure they don't break it! - -- Please document your new feature. Think about **concrete use cases** for your feature, and add a section to the - appropriate document, including a **complete** sample program that demonstrates your feature. Don't forget to update - the changelog in ``changelog.rst``! - -Including these two items with your pull request will totally make our dayโ€”and, more importantly, your future users' days! - -On that note... - -How do I compile the documentation? -************************************* - -This project's documentation is generated with `Sphinx `_. If you are editing one of the many reStructuredText files in the ``docs-src`` folder, you'll need to rebuild the documentation. It is recommended to run the following steps inside a ``virtualenv`` environment. - -.. code-block:: bash - - tox -e docs - -Do be sure to add the ``docs`` folder and its contents to your pull request! - - -.. include:: metadata.rst diff --git a/docs-src-v2/index.rst b/docs-src-v2/index.rst deleted file mode 100644 index ccca6c790..000000000 --- a/docs-src-v2/index.rst +++ /dev/null @@ -1,94 +0,0 @@ -.. toctree:: - :hidden: - - self - auth - basic_usage - conversations - real_time_messaging - faq - changelog - about - -Important Notice -********************* - -The `slackclient `_ PyPI project is in maintenance mode now and `slack-sdk `_ project is the successor. The v3 SDK provides more functionalities such as Socket Mode, OAuth flow module, SCIM API, Audit Logs API, better asyncio support, retry hanlders, and many more. - -Refer to `the migration guide `_ to learn how to smoothly migrate your existing code. - --------- - -============================================== -|product_name| -============================================== - -Slack's APIs allow anyone to build full featured integrations that extend -and expand the capabilities of your Slack workspace. These APIs allow you -to build applications that interact with Slack just like the people on your -team -- they can post messages, respond to events that happen -- as well -as build complex UIs for getting work done. - -To make it easier for Python programmers to build Slack applications, we've -provided this open source SDK. |product_name| will let you get started building -Python apps as quickly as possible. The current version, |current_version|, is -built for Python 3.6 and higher -- if you need to target Python 2.x, you might -consider using v1 of the SDK. - -Slack Platform Basics -********************* -If you're new to the Slack platform, we have a general purpose `guide for building apps `_ that isn't specific to any language or framework. It's a great place to learn all about the concepts that go into building a great Slack app. - -Before you get started building on the Slack platform, you need to `set up your app's configuration `_. This is where you define things like your app's permissions and the endpoints that Slack should use for interacting with the backend you will build with Python. - -The app configuration page is also where you will acquire the OAuth token you will use to call Slack's APIs. Treat this token with care, just like you would a password, because it has access to workspace and can potentially read and write data to and from it. - - -Installation -************ - -We recommend using `PyPI `_ to install |product_name| - -.. code-block:: bash - - pip install slackclient - -Of course, you can always pull the source code directly into your project: - -.. code-block:: bash - - git clone https://github.com/slackapi/python-slackclient.git - -And then, save a few lines of code as ``./test.py``. - -.. code-block:: python - - # test.py - import sys - # Load the local source directly - sys.path.insert(1, "./python-slackclient") - # Enable debug logging - import logging - logging.basicConfig(level=logging.DEBUG) - # Verify it works - from slack import WebClient - client = WebClient() - api_response = client.api_test() - -You can run the code this way. - -.. code-block:: bash - - python test.py - -It's also good to try on the Python REPL. - -Getting Help -************ - -If you get stuck, weโ€™re here to help. The following are the best ways to get assistance working through your issue: - -- Use our `Github Issue Tracker `_ for reporting bugs or requesting features. -- Visit the `Slack Developer Community `_ for getting help using |product_name| or just generally bond with your fellow Slack developers. - -.. include:: metadata.rst diff --git a/docs-src-v2/make.bat b/docs-src-v2/make.bat deleted file mode 100644 index e66ba12ac..000000000 --- a/docs-src-v2/make.bat +++ /dev/null @@ -1,281 +0,0 @@ -@ECHO OFF - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set BUILDDIR=_build -set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . -set I18NSPHINXOPTS=%SPHINXOPTS% . -if NOT "%PAPER%" == "" ( - set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% - set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% -) - -if "%1" == "" goto help - -if "%1" == "help" ( - :help - echo.Please use `make ^` where ^ is one of - echo. html to make standalone HTML files - echo. dirhtml to make HTML files named index.html in directories - echo. singlehtml to make a single large HTML file - echo. pickle to make pickle files - echo. json to make JSON files - echo. htmlhelp to make HTML files and a HTML help project - echo. qthelp to make HTML files and a qthelp project - echo. devhelp to make HTML files and a Devhelp project - echo. epub to make an epub - echo. epub3 to make an epub3 - echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter - echo. text to make text files - echo. man to make manual pages - echo. texinfo to make Texinfo files - echo. gettext to make PO message catalogs - echo. changes to make an overview over all changed/added/deprecated items - echo. xml to make Docutils-native XML files - echo. pseudoxml to make pseudoxml-XML files for display purposes - echo. linkcheck to check all external links for integrity - echo. doctest to run all doctests embedded in the documentation if enabled - echo. coverage to run coverage check of the documentation if enabled - echo. dummy to check syntax errors of document sources - goto end -) - -if "%1" == "clean" ( - for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i - del /q /s %BUILDDIR%\* - goto end -) - - -REM Check if sphinx-build is available and fallback to Python version if any -%SPHINXBUILD% 1>NUL 2>NUL -if errorlevel 9009 goto sphinx_python -goto sphinx_ok - -:sphinx_python - -set SPHINXBUILD=python -m sphinx.__init__ -%SPHINXBUILD% 2> nul -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -:sphinx_ok - - -if "%1" == "html" ( - %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/html. - goto end -) - -if "%1" == "dirhtml" ( - %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. - goto end -) - -if "%1" == "singlehtml" ( - %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. - goto end -) - -if "%1" == "pickle" ( - %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the pickle files. - goto end -) - -if "%1" == "json" ( - %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the JSON files. - goto end -) - -if "%1" == "htmlhelp" ( - %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run HTML Help Workshop with the ^ -.hhp project file in %BUILDDIR%/htmlhelp. - goto end -) - -if "%1" == "qthelp" ( - %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run "qcollectiongenerator" with the ^ -.qhcp project file in %BUILDDIR%/qthelp, like this: - echo.^> qcollectiongenerator %BUILDDIR%\qthelp\python-slackclient.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile %BUILDDIR%\qthelp\python-slackclient.ghc - goto end -) - -if "%1" == "devhelp" ( - %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. - goto end -) - -if "%1" == "epub" ( - %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The epub file is in %BUILDDIR%/epub. - goto end -) - -if "%1" == "epub3" ( - %SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3 - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The epub3 file is in %BUILDDIR%/epub3. - goto end -) - -if "%1" == "latex" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdf" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf - cd %~dp0 - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdfja" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf-ja - cd %~dp0 - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "text" ( - %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The text files are in %BUILDDIR%/text. - goto end -) - -if "%1" == "man" ( - %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The manual pages are in %BUILDDIR%/man. - goto end -) - -if "%1" == "texinfo" ( - %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. - goto end -) - -if "%1" == "gettext" ( - %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The message catalogs are in %BUILDDIR%/locale. - goto end -) - -if "%1" == "changes" ( - %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes - if errorlevel 1 exit /b 1 - echo. - echo.The overview file is in %BUILDDIR%/changes. - goto end -) - -if "%1" == "linkcheck" ( - %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck - if errorlevel 1 exit /b 1 - echo. - echo.Link check complete; look for any errors in the above output ^ -or in %BUILDDIR%/linkcheck/output.txt. - goto end -) - -if "%1" == "doctest" ( - %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest - if errorlevel 1 exit /b 1 - echo. - echo.Testing of doctests in the sources finished, look at the ^ -results in %BUILDDIR%/doctest/output.txt. - goto end -) - -if "%1" == "coverage" ( - %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage - if errorlevel 1 exit /b 1 - echo. - echo.Testing of coverage in the sources finished, look at the ^ -results in %BUILDDIR%/coverage/python.txt. - goto end -) - -if "%1" == "xml" ( - %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The XML files are in %BUILDDIR%/xml. - goto end -) - -if "%1" == "pseudoxml" ( - %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. - goto end -) - -if "%1" == "dummy" ( - %SPHINXBUILD% -b dummy %ALLSPHINXOPTS% %BUILDDIR%/dummy - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. Dummy builder generates no files. - goto end -) - -:end diff --git a/docs-src-v2/metadata.rst b/docs-src-v2/metadata.rst deleted file mode 100644 index 204e46f02..000000000 --- a/docs-src-v2/metadata.rst +++ /dev/null @@ -1,20 +0,0 @@ -.. Site settings -.. |product_name| replace:: slackclient (Legacy Python Slack SDK) -.. |current_version| replace:: 2.0 -.. |latest_release_date| replace:: April 29, 2019 -.. |email| replace:: opensource@slack.com -.. |repo_name| replace:: python-slackclient -.. |github_username| replace:: SlackAPI -.. |twitter_username| replace:: SlackAPI - -.. _Bot Developer Hangout: https://dev4slack.slack.com/archives/sdk-python-slackclient -.. _Issue Tracker: http://github.com/SlackAPI/python-slackclient/issues -.. _pull request: http://github.com/SlackAPI/python-slackclient/pulls -.. _Python RTMBot: https://slackapi.github.io/python-rtmbot -.. _License: https://github.com/SlackAPI/python-slackclient/blob/main/LICENSE -.. _Code of Conduct: https://slackhq.github.io/code-of-conduct -.. _Contributing: https://github.com/slackapi/python-slackclient/blob/main/.github/contributing.md -.. _contributing guidelines: https://github.com/slackapi/python-slackclient/blob/main/.github/contributing.md -.. _Contributor License Agreement: https://docs.google.com/a/slack-corp.com/forms/d/e/1FAIpQLSfzjVoCM7ohBnjWf7eDYQxzti1EPpinsIJQA5RAUBwJKRUQHg/viewform -.. _Real Time Messaging (RTM) API: https://api.slack.com/rtm -.. _Web API: https://api.slack.com/web diff --git a/docs-src-v2/real_time_messaging.rst b/docs-src-v2/real_time_messaging.rst deleted file mode 100644 index bc48b123d..000000000 --- a/docs-src-v2/real_time_messaging.rst +++ /dev/null @@ -1,105 +0,0 @@ -.. _real-time-messaging: - -============================================== -Real Time Messaging (RTM) -============================================== -The `Real Time Messaging (RTM) API`_ is a WebSocket-based API that allows you to receive events from Slack in real time and send messages as users. - -If you prefer events to be pushed to your app, we recommend using the HTTP-based `Events API `_ instead. -The Events API contains some events that aren't supported in the RTM API (like `app_home_opened event `_), -and it supports most of the event types in the RTM API. If you'd like to use the Events API, you can use the `Python Slack Events Adaptor `_. - -The RTMClient allows apps to communicate with the Slack Platform's RTM API. - -The event-driven architecture of this client allows you to simply link callbacks to their corresponding events. When an event occurs this client executes your callback while passing along any information it receives. We also give you the ability to call our web client from inside your callbacks. - -In our example below, we watch for a `message event `_ that contains "Hello" and if its received, we call the ``say_hello()`` function. We then issue a call to the web client to post back to the channel saying "Hi" to the user. - -Configuring the RTM API ------------------------------------------- - -Events using the RTM API **must** use a classic Slack app (with a plain ``bot`` scope). - -If you already have a classic Slack app, you can use those credentials. If you don't and need to use the RTM API, you can `create a classic Slack app `_. You can learn more in the `API documentation `_. - -Also, even if the Slack app configuration pages encourage you to upgrade to the newer permission model, don't upgrade it and keep using the "classic" bot permission. - -Connecting to the RTM API ------------------------------------------- - -.. code-block:: python - - import os - from slack import RTMClient - - @RTMClient.run_on(event="message") - def say_hello(**payload): - data = payload['data'] - web_client = payload['web_client'] - - if 'Hello' in data['text']: - channel_id = data['channel'] - thread_ts = data['ts'] - user = data['user'] # This is not username but user ID (the format is either U*** or W***) - - web_client.chat_postMessage( - channel=channel_id, - text=f"Hi <@{user}>!", - thread_ts=thread_ts - ) - - slack_token = os.environ["SLACK_API_TOKEN"] - rtm_client = RTMClient(token=slack_token) - rtm_client.start() - -rtm.start vs rtm.connect ---------------------------- - -By default, the RTM client uses ``rtm.connect`` to establish a WebSocket connection with Slack. The response contains basic information about the team and WebSocket url. - -If you'd rather use ``rtm.start`` to establish the connection, which provides more information about the conversations and users on the team, you can set the ``connect_method`` option to ``rtm.start`` when instantiating the RTM Client. Note that on larger teams, use of ``rtm.start`` can be slow and unreliable. - -.. code-block:: python - - import os - from slack import RTMClient - - @RTMClient.run_on(event="message") - def say_hello(**payload): - data = payload['data'] - web_client = payload['web_client'] - if 'text' in data and 'Hello' in data['text']: - channel_id = data['channel'] - thread_ts = data['ts'] - user = data['user'] # This is not username but user ID (the format is either U*** or W***) - - web_client.chat_postMessage( - channel=channel_id, - text=f"Hi <@{user}>!", - thread_ts=thread_ts - ) - - slack_token = os.environ["SLACK_API_TOKEN"] - rtm_client = RTMClient( - token=slack_token, - connect_method='rtm.start' - ) - rtm_client.start() - -Read the `rtm.connect docs `_ and the `rtm.start docs `_ for more details. - - -RTM Events -------------- -.. code-block:: javascript - - { - 'type': 'message', - 'ts': '1358878749.000002', - 'user': 'U023BECGF', - 'text': 'Hello' - } - -See `RTM Events `_ for a complete list of events. - -.. include:: metadata.rst diff --git a/docs-src/.gitignore b/docs-src/.gitignore deleted file mode 100644 index e35d8850c..000000000 --- a/docs-src/.gitignore +++ /dev/null @@ -1 +0,0 @@ -_build diff --git a/docs-src/_themes/slack/conf.py b/docs-src/_themes/slack/conf.py deleted file mode 100644 index 370a2c171..000000000 --- a/docs-src/_themes/slack/conf.py +++ /dev/null @@ -1,349 +0,0 @@ -# -*- coding: utf-8 -*- -# -# python-slack-sdk documentation build configuration file, created by -# sphinx-quickstart on Mon Jun 27 17:36:09 2016. -# -# This file is execfile()d with the current directory set to its -# containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# -import os -import sys -sys.path.insert(0, os.path.abspath('../')) - -# -- General configuration ------------------------------------------------ - -# If your documentation needs a minimal Sphinx version, state it here. -# -# needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.coverage', -] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['../../_templates'] - -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -# -# source_suffix = ['.rst', '.md'] -source_suffix = '.rst' - -# The encoding of source files. -# -# source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'Python Slack SDK' -copyright = u'2015โ€“ Slack Technologies, LLC and contributors' -author = u'Slack Technologies, LLC and contributors' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = u'1.0' -# The full version, including alpha/beta/rc tags. -release = u'1.0.1' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -# -# today = '' -# -# Else, today_fmt is used as the format for a strftime call. -# -# today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This patterns also effect to html_static_path and html_extra_path -exclude_patterns = [ - '_build', - 'Thumbs.db', - '.DS_Store', - 'auth.rst', - 'basic_usage.rst', - 'conversations.rst', -] - -# The reST default role (used for this markup: `text`) to use for all -# documents. -# -# default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -# -# add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -# -# add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -# -# show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'emacs' - -# A list of ignored prefixes for module index sorting. -# modindex_common_prefix = [] - -# If true, keep warnings as "system message" paragraphs in the built documents. -# keep_warnings = False - -# If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = False - - -# -- Options for HTML output ---------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = "slack" -html_theme_path = ["../../_themes", ] - -highlight_language = "python" - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# -# html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -# html_theme_path = [] - -# The name for this set of Sphinx documents. -# " v documentation" by default. -# -# html_title = u'python-slack-sdk v1.0.1' - -# A shorter title for the navigation bar. Default is the same as html_title. -# -# html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -# -# html_logo = None - -# The name of an image file (relative to this directory) to use as a favicon of -# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -# -# html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['static'] - -html_context = { - 'css_files': ['static/pygments.css'], -} - -# Add any extra paths that contain custom files (such as robots.txt or -# .htaccess) here, relative to this directory. These files are copied -# directly to the root of the documentation. -# -# html_extra_path = [] - -# If not None, a 'Last updated on:' timestamp is inserted at every page -# bottom, using the given strftime format. -# The empty string is equivalent to '%b %d, %Y'. -# -# html_last_updated_fmt = None - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -# -# html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -# -# html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -# -# html_additional_pages = {} - -# If false, no module index is generated. -# -# html_domain_indices = True - -# If false, no index is generated. -# -# html_use_index = True - -# If true, the index is split into individual pages for each letter. -# -# html_split_index = False - -# If true, links to the reST sources are added to the pages. -# -# html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -# -# html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -# -# html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -# -# html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -# html_file_suffix = None - -# Language to be used for generating the HTML full-text search index. -# Sphinx supports the following languages: -# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' -# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh' -# -# html_search_language = 'en' - -# A dictionary with options for the search language support, empty by default. -# 'ja' uses this config value. -# 'zh' user can custom change `jieba` dictionary path. -# -# html_search_options = {'type': 'default'} - -# The name of a javascript file (relative to the configuration directory) that -# implements a search results scorer. If empty, the default will be used. -# -# html_search_scorer = 'scorer.js' - -# Output file base name for HTML help builder. -htmlhelp_basename = 'python-slack-sdkdoc' - -# -- Options for LaTeX output --------------------------------------------- - -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # - # 'papersize': 'letterpaper', - - # The font size ('10pt', '11pt' or '12pt'). - # - # 'pointsize': '10pt', - - # Additional stuff for the LaTeX preamble. - # - # 'preamble': '', - - # Latex figure (float) alignment - # - # 'figure_align': 'htbp', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - (master_doc, 'python-slack-sdk.tex', u'python-slack-sdk Documentation', - u'Ryan Huber, Jeff Ammons', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -# -# latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -# -# latex_use_parts = False - -# If true, show page references after internal links. -# -# latex_show_pagerefs = False - -# If true, show URL addresses after external links. -# -# latex_show_urls = False - -# Documents to append as an appendix to all manuals. -# -# latex_appendices = [] - -# If false, no module index is generated. -# -# latex_domain_indices = True - - -# -- Options for manual page output --------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'python-slack-sdk', u'python-slack-sdk Documentation', - [author], 1) -] - -# If true, show URL addresses after external links. -# -# man_show_urls = False - - -# -- Options for Texinfo output ------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - (master_doc, 'python-slack-sdk', u'python-slack-sdk Documentation', - author, 'python-slack-sdk', 'A basic client for Slack.com, which can optionally connect to the Slack Real Time Messaging (RTM) API.', - 'Miscellaneous'), -] - -# Documents to append as an appendix to all manuals. -# -# texinfo_appendices = [] - -# If false, no module index is generated. -# -# texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -# -# texinfo_show_urls = 'footnote' - -# If true, do not generate a @detailmenu in the "Top" node's menu. -# -# texinfo_no_detailmenu = False diff --git a/docs-src/_themes/slack/layout.html b/docs-src/_themes/slack/layout.html deleted file mode 100644 index 2072809c9..000000000 --- a/docs-src/_themes/slack/layout.html +++ /dev/null @@ -1,165 +0,0 @@ - - - - - {{ - metatags - }} - - {%- block htmltitle %} - {{ title|striptags|e + " — "|safe + project|e }} - {%- endblock %} {%- macro css() %} - - - - - - {%- endmacro %} - - - - - {{ - css() - }} - {%- block linktags %} - - - {%- endblock %} - - - - - - - - {%- block header %} -
- - - - - - - {{ project }} - - -
- {% endblock %} - -
-
- - - - - -
- -
- {%- block body %} - {{ body }} - {% endblock %} -
-
-
- -
-
- -
-

- ยฉ 2015- Slack Technologies, LLC and contributors -

-
- - - - - diff --git a/docs-src/_themes/slack/localtoc.html b/docs-src/_themes/slack/localtoc.html deleted file mode 100644 index 9ee3dab97..000000000 --- a/docs-src/_themes/slack/localtoc.html +++ /dev/null @@ -1,4 +0,0 @@ -
Table of Contents?
-
    - {{ toc }} -
\ No newline at end of file diff --git a/docs-src/_themes/slack/relations.html b/docs-src/_themes/slack/relations.html deleted file mode 100644 index f90a7b212..000000000 --- a/docs-src/_themes/slack/relations.html +++ /dev/null @@ -1,19 +0,0 @@ -{# - basic/relations.html - ~~~~~~~~~~~~~~~~~~~~ - - Sphinx sidebar template: relation links. - - :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. - :license: BSD, see LICENSE for details. -#} -{%- if prev %} -

{{ _('Previous topic') }}

-

{{ prev.title }}

-{%- endif %} -{%- if next %} -

{{ _('Next topic') }}

-

{{ next.title }}

-{%- endif %} diff --git a/docs-src/_themes/slack/sidebar.html b/docs-src/_themes/slack/sidebar.html deleted file mode 100644 index 6f461834f..000000000 --- a/docs-src/_themes/slack/sidebar.html +++ /dev/null @@ -1,15 +0,0 @@ -{{ toctree(maxdepth=-1, collapse=False,includehidden=True) }} - - diff --git a/docs-src/_themes/slack/static/default.css_t b/docs-src/_themes/slack/static/default.css_t deleted file mode 100644 index d4c603379..000000000 --- a/docs-src/_themes/slack/static/default.css_t +++ /dev/null @@ -1,79 +0,0 @@ -a.headerlink { - display: none !important; -} - -h2 { - margin-top: -120px; - padding-top: 120px; -} - -.section-title { - font-size: 2rem; - line-height: 2.5rem; - letter-spacing: -1px; - font-weight: 700; - margin: 0 0 1rem; -} - -nav#api_nav .toctree-l1 { - margin-bottom: 1.5rem; -} - -nav#api_nav #api_sections ul { - list-style: none; - margin: 0; - padding: 0; -} - -nav#api_nav #api_sections ul li.toctree-l1>a { - color: #1264a3; - letter-spacing: 0; - font-size: .8rem; - font-weight: 800; - text-transform: uppercase; - border: none; - padding: 0; -} - -nav#api_nav #api_sections ul li.toctree-l2 { - margin: 0; - padding: 0; -} - -nav#api_nav #api_sections ul li.toctree-l2 a { - color: #1d1c1d; - text-transform: none; - font-weight: inherit; - padding: 0; - display: block; - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - font-size: 15px!important; - line-height:15px; - padding: 4px 8px; - border: 1px solid transparent; - border-radius: 4px; -} - -nav#api_nav #api_sections ul li.toctree-l2 a:hover { - cursor: pointer; - text-decoration: none; - background-color:#e8f5fa; - border-color:#dcf0fb; -} - -nav#api_nav #footer #footer_nav { - font-size: .9375rem; -} - -nav#api_nav #footer #footer_nav a { - border: none; - padding: 0; - color: #616061; -} - -nav#api_nav #footer #footer_nav a:hover { - text-decoration: none; - color: #1c1c1c; -} diff --git a/docs-src/_themes/slack/static/docs.css_t b/docs-src/_themes/slack/static/docs.css_t deleted file mode 100644 index 12c715d28..000000000 --- a/docs-src/_themes/slack/static/docs.css_t +++ /dev/null @@ -1,34 +0,0 @@ -/* Updates body font */ -body { - font-family: Slack-Lato,appleLogo,sans-serif; -} - -/* Replaces old sidebar styled links */ -.sidebar_menu h5 { - font-size: 0.8rem; - font-weight: 800; - margin-bottom: 3px; -} - -/* Aligns footer navigation to the left of the sidebar */ -.footer_nav { - margin: 0 !important; -} - -/* Styles the signature all nice and pretty <3 */ -#footer_signature { - color:#e01e5a; - font-size:.9rem; - margin-top: 10px; -} - -/* Fixes link hover state */ -a:hover { - text-decoration: underline; -} - -/* Makes footer consistent */ -footer { - background-color: transparent; - border: 0; -} \ No newline at end of file diff --git a/docs-src/_themes/slack/static/pygments.css_t b/docs-src/_themes/slack/static/pygments.css_t deleted file mode 100644 index ae05f6c90..000000000 --- a/docs-src/_themes/slack/static/pygments.css_t +++ /dev/null @@ -1,60 +0,0 @@ -.highlight .hll { background-color: #ffffcc } -.highlight { background: #ffffff; } -.highlight .c { color: #999988; font-style: italic } /* Comment */ -.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ -.highlight .k { font-weight: bold } /* Keyword */ -.highlight .o { font-weight: bold } /* Operator */ -.highlight .cm { color: #999988; font-style: italic } /* Comment.Multiline */ -.highlight .cp { color: #999999; font-weight: bold } /* 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 { 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 { font-weight: bold } /* Keyword.Constant */ -.highlight .kd { font-weight: bold } /* Keyword.Declaration */ -.highlight .kn { font-weight: bold } /* Keyword.Namespace */ -.highlight .kp { font-weight: bold } /* Keyword.Pseudo */ -.highlight .kr { font-weight: bold } /* Keyword.Reserved */ -.highlight .kt { color: #445588; font-weight: bold } /* Keyword.Type */ -.highlight .m { color: #009999 } /* Literal.Number */ -.highlight .s { color: #bb8844 } /* Literal.String */ -.highlight .na { color: #008080 } /* Name.Attribute */ -.highlight .nb { color: #999999 } /* Name.Builtin */ -.highlight .nc { color: #445588; font-weight: bold } /* Name.Class */ -.highlight .no { color: #008080 } /* Name.Constant */ -.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 .nn { color: #555555 } /* Name.Namespace */ -.highlight .nt { color: #000080 } /* Name.Tag */ -.highlight .nv { color: #008080 } /* Name.Variable */ -.highlight .ow { 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: #bb8844 } /* Literal.String.Backtick */ -.highlight .sc { color: #bb8844 } /* Literal.String.Char */ -.highlight .sd { color: #bb8844 } /* Literal.String.Doc */ -.highlight .s2 { color: #bb8844 } /* Literal.String.Double */ -.highlight .se { color: #bb8844 } /* Literal.String.Escape */ -.highlight .sh { color: #bb8844 } /* Literal.String.Heredoc */ -.highlight .si { color: #bb8844 } /* Literal.String.Interpol */ -.highlight .sx { color: #bb8844 } /* Literal.String.Other */ -.highlight .sr { color: #808000 } /* Literal.String.Regex */ -.highlight .s1 { color: #bb8844 } /* Literal.String.Single */ -.highlight .ss { color: #bb8844 } /* 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 */ diff --git a/docs-src/_themes/slack/theme.conf b/docs-src/_themes/slack/theme.conf deleted file mode 100644 index b13de3cbb..000000000 --- a/docs-src/_themes/slack/theme.conf +++ /dev/null @@ -1,6 +0,0 @@ -[theme] -inherit = default -stylesheet = default.css -show_sphinx = False - -[options] diff --git a/docs-src/about.rst b/docs-src/about.rst deleted file mode 100644 index 17fad157f..000000000 --- a/docs-src/about.rst +++ /dev/null @@ -1,18 +0,0 @@ -============================================== -About -============================================== - -|product_name| -************** - - -Access the Slack Platform from your Python app. |product_name| lets you build on the Slack Web APIs pythonically. - -|product_name| is proudly maintained with ๐Ÿ’– by the Slack Developer Tools team - -- `License`_ -- `Code of Conduct`_ -- `Contributing`_ -- `Contributor License Agreement`_ - -.. include:: metadata.rst \ No newline at end of file diff --git a/docs-src/audit-logs/index.rst b/docs-src/audit-logs/index.rst deleted file mode 100644 index b9001ca04..000000000 --- a/docs-src/audit-logs/index.rst +++ /dev/null @@ -1,110 +0,0 @@ -============================================== -Audit Logs API Client -============================================== - -`Audit Logs API `_ is a set of APIs for monitoring whatโ€™s happening in your `Enterprise Grid `_ organization. - -The Audit Logs API can be used by security information and event management (SIEM) tools to provide an analysis of how your Slack organization is being accessed. You can also use this API to write your own applications to see how members of your organization are using Slack. - -Follow the instructions in `the API document `_ to get a valid token for using Audit Logs API. The Slack app using the Audit Logs API needs to be installed in the Enterprise Grid Organization, not an individual workspace within the organization. - -The Python document for this module is available at https://slack.dev/python-slack-sdk/api-docs/slack_sdk/ - -AuditLogsClient -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -An OAuth token with `the admin scope `_ is required to access this API. - -You will likely use the ``/logs`` endpoint as it's the essential part of this API. - -To learn about the available parameters for this endpoint, check out `this guide `_. You can also learn more about the data structure of ``api_response.typed_body`` from `the class source code `_. - -.. code-block:: python - - import os - from slack_sdk.audit_logs import AuditLogsClient - - client = AuditLogsClient(token=os.environ["SLACK_ORG_ADMIN_USER_TOKEN"]) - - api_response = client.logs(action="user_login", limit=1) - api_response.typed_body # slack_sdk.audit_logs.v1.LogsResponse - -If you would like to access ``/schemes`` or ``/actions``, you can use the following methods: - -.. code-block:: python - - api_response = client.schemas() - api_response = client.actions() - -AsyncAuditLogsClient -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -If you are keen to use asyncio for SCIM API calls, we offer AsyncSCIMClient for it. This client relies on aiohttp library. - -.. code-block:: python - - from slack_sdk.audit_logs.async_client import AsyncAuditLogsClient - client = AsyncAuditLogsClient(token=os.environ["SLACK_ORG_ADMIN_USER_TOKEN"]) - - api_response = await client.logs(action="user_login", limit=1) - api_response.typed_body # slack_sdk.audit_logs.v1.LogsResponse - - --------- - -RetryHandler -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -With the default settings, only ``ConnectionErrorRetryHandler`` with its default configuration (=only one retry in the manner of `exponential backoff and jitter `_) is enabled. The retry handler retries if an API client encounters a connectivity-related failure (e.g., Connection reset by peer). - -To use other retry handlers, you can pass a list of ``RetryHandler`` to the client constructor. For instance, you can add the built-in ``RateLimitErrorRetryHandler`` this way: - -.. code-block:: python - - import os - from slack_sdk.audit_logs import AuditLogsClient - client = AuditLogsClient(token=os.environ["SLACK_ORG_ADMIN_USER_TOKEN"]) - - # This handler does retries when HTTP status 429 is returned - from slack_sdk.http_retry.builtin_handlers import RateLimitErrorRetryHandler - rate_limit_handler = RateLimitErrorRetryHandler(max_retry_count=1) - - # Enable rate limited error retries as well - client.retry_handlers.append(rate_limit_handler) - -Creating your own ones is also quite simple. Defining a new class that inherits ``slack_sdk.http_retry.RetryHandler`` (``AsyncRetryHandler`` for asyncio apps) and implements required methods (internals of ``can_retry`` / ``prepare_for_next_retry``). Check the built-in ones' source code for learning how to properly implement. - -.. code-block:: python - - import socket - from typing import Optional - from slack_sdk.http_retry import (RetryHandler, RetryState, HttpRequest, HttpResponse) - from slack_sdk.http_retry.builtin_interval_calculators import BackoffRetryIntervalCalculator - from slack_sdk.http_retry.jitter import RandomJitter - - class MyRetryHandler(RetryHandler): - def _can_retry( - self, - *, - state: RetryState, - request: HttpRequest, - response: Optional[HttpResponse] = None, - error: Optional[Exception] = None - ) -> bool: - # [Errno 104] Connection reset by peer - return error is not None and isinstance(error, socket.error) and error.errno == 104 - - client = AuditLogsClient( - token=os.environ["SLACK_ORG_ADMIN_USER_TOKEN"], - retry_handlers=[MyRetryHandler( - max_retry_count=1, - interval_calculator=BackoffRetryIntervalCalculator( - backoff_factor=0.5, - jitter=RandomJitter(), - ), - )], - ) - -For asyncio apps, ``Async`` prefixed corresponding modules are available. All the methods in those methods are async/await compatible. Check `the source code `_ and `tests `_ for more details. - -.. include:: ../metadata.rst diff --git a/docs-src/auth.rst b/docs-src/auth.rst deleted file mode 100644 index e8f6c125f..000000000 --- a/docs-src/auth.rst +++ /dev/null @@ -1,8 +0,0 @@ -============================================== -Tokens & Installation -============================================== -.. _handling-tokens: - -See `Installation <./installation/index.html>`_ page. - -.. include:: metadata.rst diff --git a/docs-src/basic_usage.rst b/docs-src/basic_usage.rst deleted file mode 100644 index c021499e5..000000000 --- a/docs-src/basic_usage.rst +++ /dev/null @@ -1,8 +0,0 @@ -============================================== -Basic Usage -============================================== - -See `Installation <./web/index.html>`_ page. - -.. include:: metadata.rst - diff --git a/docs-src/conversations.rst b/docs-src/conversations.rst deleted file mode 100644 index 64074ff40..000000000 --- a/docs-src/conversations.rst +++ /dev/null @@ -1,8 +0,0 @@ -============================================== -Conversations API -============================================== - -See `WebClient Basics <./web/index.html>`_ page. - -.. include:: metadata.rst - diff --git a/docs-src/faq.rst b/docs-src/faq.rst deleted file mode 100644 index 598c176b2..000000000 --- a/docs-src/faq.rst +++ /dev/null @@ -1,80 +0,0 @@ -============================================== -FAQ -============================================== - -Python Documents -********************************* - -The Python module documents are available at https://slack.dev/python-slack-sdk/api-docs/slack_sdk/ - -Installation Issues -********************************* - -We recommend using `virtualenv (venv) `_ to set up your Python runtime. - -.. code-block:: bash - - # Create a dedicated virtual env for running your Python scripts - python -m venv .venv - - # Run .venv\Scripts\activate on Windows OS - source .venv/bin/activate - - # Install slack_sdk PyPI package - pip install "slack_sdk>=3.0" - - # Set your token as an env variable (`set` command for Windows OS) - export SLACK_BOT_TOKEN=xoxb-*** - -Then, verify the following code works on the Python REPL (you can start it by just ``python``). - -.. code-block:: python - - import os - import logging - from slack_sdk import WebClient - logging.basicConfig(level=logging.DEBUG) - client = WebClient(token=os.environ["SLACK_BOT_TOKEN"]) - res = client.api_test() - - -As ``slack`` package is deprecated, we recommend switching to ``slack_sdk`` package. That being said, the code you're working on may be still using the old package. If you encounter an error saying ``AttributeError: module 'slack' has no attribute 'WebClient'``, run ``pip list``. If you find both ``slack_sdk`` and ``slack`` in the output, try removing ``slack`` by ``pip uninstall slack`` and reinstalling ``slack_sdk``. - - -Bug Report -****************** - -That's great! Thank you. Let us know on the `Issue Tracker`_. If you're feeling particularly ambitious, why not submit a `pull request`_ with a bug fix? - -Feature Requests -******************************* - -There's always something more that could be added! You can let us know in the `Issue Tracker`_ to start a discussion around the proposed feature, that's a good start. If you're feeling particularly ambitious, why not write the feature yourself, and submit a `pull request`_! We love feedback and we love help and we don't bite. Much. - -Contributions -********************************* - -What an excellent question. First of all, please have a look at our general `contributing guidelines`_. - -All done? Great! While we're super excited to incorporate your new feature, there are a couple of things we want to make sure you've given thought to. - -- Please write unit tests for your new code. But don't **just** aim to increase the test coverage, rather, we expect you to have written **thoughtful** tests that ensure your new feature will continue to work as expected, and to help future contributors to ensure they don't break it! - -- Please document your new feature. Think about **concrete use cases** for your feature, and add a section to the appropriate document, including a **complete** sample program that demonstrates your feature. Don't forget to update the changelog in ``changelog.rst``! - -Including these two items with your pull request will totally make our dayโ€”and, more importantly, your future users' days! - -On that note... - -Documentation -************************************* - -This project's documentation is generated with `Sphinx `_. If you are editing one of the many reStructuredText files in the ``docs-src`` folder, you'll need to rebuild the documentation. It is recommended to run the following steps inside a ``virtualenv`` environment. - -.. code-block:: bash - - ./docs-v3.sh - -Do be sure to add the ``docs-v3`` folder and its contents to your pull request! - -.. include:: metadata.rst diff --git a/docs-src/index.rst b/docs-src/index.rst deleted file mode 100644 index c50ef3750..000000000 --- a/docs-src/index.rst +++ /dev/null @@ -1,99 +0,0 @@ -.. toctree:: - :hidden: - - self - v3-migration/index - installation/index - web/index - webhook/index - socket-mode/index - oauth/index - audit-logs/index - scim/index - real_time_messaging - faq - about - -============================================== -|product_name| -============================================== - -The Slack platform offers several APIs to build apps. Each Slack API delivers part of the capabilities from the platform, so that you can pick just those that fit for your needs. This SDK offers a corresponding package for each of Slackโ€™s APIs. They are small and powerful when used independently, and work seamlessly when used together, too. - -+--------------------------------+-----------------------------------------------------------------------------------------------+------------------------------------+ -| Feature | What its for | Package | -+--------------------------------+-----------------------------------------------------------------------------------------------+------------------------------------+ -| Web API | Send data to or query data from Slack using any of over 200 methods. | ``slack_sdk.web`` | -| | | ``slack_sdk.web.async_client`` | -+--------------------------------+-----------------------------------------------------------------------------------------------+------------------------------------+ -| Webhooks / response_url | Send a message using Incoming Webhooks or response_url | ``slack_sdk.webhook`` | -| | | ``slack_sdk.webhook.async_client`` | -+--------------------------------+-----------------------------------------------------------------------------------------------+------------------------------------+ -| Socket Mode | Receive and send messages over Socket Mode connections. | ``slack_sdk.socket_mode`` | -+--------------------------------+-----------------------------------------------------------------------------------------------+------------------------------------+ -| OAuth | Setup the authentication flow using V2 OAuth, OpenID Connect for Slack apps. | ``slack_sdk.oauth`` | -+--------------------------------+-----------------------------------------------------------------------------------------------+------------------------------------+ -| Audit Logs API | Receive audit logs API data. | ``slack_sdk.audit_logs`` | -+--------------------------------+-----------------------------------------------------------------------------------------------+------------------------------------+ -| SCIM API | Utilize the SCIM APIs for provisioning and managing user accounts and groups. | ``slack_sdk.scim`` | -+--------------------------------+-----------------------------------------------------------------------------------------------+------------------------------------+ -| RTM API | Listen for incoming messages and a limited set of events happening in Slack, using WebSocket. | ``slack_sdk.rtm_v2`` | -+--------------------------------+-----------------------------------------------------------------------------------------------+------------------------------------+ -| Request Signature Verification | Verify incoming requests from the Slack API servers. | ``slack_sdk.signature`` | -+--------------------------------+-----------------------------------------------------------------------------------------------+------------------------------------+ -| UI Builders | Construct UI components using easy-to-use builders. | ``slack_sdk.models`` | -+--------------------------------+-----------------------------------------------------------------------------------------------+------------------------------------+ - -The Python module documents are available at https://slack.dev/python-slack-sdk/api-docs/slack_sdk/ - -Installation -************ - -This package supports Python 3.6 and higher. We recommend using `PyPI `_ to install |product_name| - -.. code-block:: bash - - pip install slack_sdk - -Of course, you can always pull the source code directly into your project: - -.. code-block:: bash - - git clone https://github.com/slackapi/python-slack-sdk.git - cd python-slack-sdk - python3 -m venv .venv - source .venv/bin/activate - pip install -U pip - pip install -e . # install the SDK project into the virtual env - -And then, save a few lines of code as ``./test.py``. - -.. code-block:: python - - # test.py - import sys - # Enable debug logging - import logging - logging.basicConfig(level=logging.DEBUG) - # Verify it works - from slack_sdk import WebClient - client = WebClient() - api_response = client.api_test() - -You can run the code this way. - -.. code-block:: bash - - python test.py - -It's also good to try on the Python REPL. - -Getting Help -************ - -If you get stuck, weโ€™re here to help. The following are the best ways to get assistance working through your issue: - -- `GitHub Issue Tracker `_ for questions, feature requests, bug reports and general discussion related to this package. -- Visit the `Slack Developer Community `_ for getting help using |product_name| or just generally bond with your fellow Slack developers. - -.. include:: metadata.rst diff --git a/docs-src/installation/index.rst b/docs-src/installation/index.rst deleted file mode 100644 index e35413887..000000000 --- a/docs-src/installation/index.rst +++ /dev/null @@ -1,143 +0,0 @@ -============================================== -Installation -============================================== -.. _handling-tokens: - -Access Tokens -------------------- - -**Keeping access tokens safe** - -The OAuth token you use to call the Slack API has access to the data on the workspace where it is installed. - -Depending on the scopes granted to the token, it potentially has the ability to read and write data. Treat these tokens just as you would a password -- don't publish them, don't check them into source code, don't share them with others. - -๐ŸšซAvoid this: - -.. code-block:: python - - token = 'xoxb-111-222-xxxxx' - -We recommend you pass tokens in as environment variables, or persist them in a database that is accessed at runtime. You can add a token to the environment by starting your app as: - -.. code-block:: python - - SLACK_BOT_TOKEN="xoxb-111-222-xxxxx" python myapp.py - -Then retrieve the key with: - -.. code-block:: python - - import os - SLACK_BOT_TOKEN = os.environ["SLACK_BOT_TOKEN"] - -For additional information, please see our `Safely Storing Credentials `_ page. - -Workspace Installations ---------------------------------------- - -**Single Workspace Install** - -If you're building an application for a single Slack workspace, there's no need to build out the entire OAuth flow. - -Once you've setup your features, click on the **Install App to Team** button found on the **Install App** page. -If you add new permission scopes or Slack app features after an app has been installed, you must reinstall the app to -your workspace for changes to take effect. - -For additional information, see the `Installing Apps `_ of our `Building Slack apps `_ page. - -**Multiple Workspace Install** - -If you intend for an app to be installed on multiple Slack workspaces, you will need to handle this installation via the industry-standard OAuth protocol. You can read more about `how Slack handles Oauth `_. - -(The OAuth exchange is facilitated via HTTP and requires a webserver; in this example, we'll use `Flask `_.) - -To configure your app for OAuth, you'll need a client ID, a client secret, and a set of one or more scopes that will be applied to the token once it is granted. The client ID and client secret are available from your `app's configuration page `_. The scopes are determined by the functionality of the app -- every method you wish to access has a corresponding scope and your app will need to request that scope in order to be able to access the method. Review Slack's `full list of OAuth scopes `_. - -.. code-block:: python - - import os - from slack_sdk import WebClient - from flask import Flask, request - - client_id = os.environ["SLACK_CLIENT_ID"] - client_secret = os.environ["SLACK_CLIENT_SECRET"] - oauth_scope = os.environ["SLACK_SCOPES"] - - app = Flask(__name__) - -**The OAuth initiation link** - -To begin the OAuth flow that will install your app on a workspace, you'll need to provide the user with a link to Slack's OAuth page. This can be a simple link to ``https://slack.com/oauth/v2/authorize`` with ``scope`` and ``client_id`` query parameters, or you can use our pre-built `Add to Slack button `_ to do all the work for you. - -This link directs the user to Slack's OAuth acceptance page, where the user will review and accept or refuse the permissions your app is requesting as defined by the scope(s). - -.. code-block:: python - - @app.route("/slack/install", methods=["GET"]) - def pre_install(): - state = "randomly-generated-one-time-value" - return '' \ - 'Add to Slack' - -**The OAuth completion page** - -Once the user has agreed to the permissions you've requested, Slack will redirect the user to your auth completion page, which includes a ``code`` query string param. You'll use the ``code`` param to call the ``oauth.v2.access`` `endpoint `_ that will finally grant you the token. - -.. code-block:: python - - @app.route("/slack/oauth_redirect", methods=["GET"]) - def post_install(): - # Verify the "state" parameter - - # Retrieve the auth code from the request params - code_param = request.args['code'] - - # An empty string is a valid token for this request - client = WebClient() - - # Request the auth tokens from Slack - response = client.oauth_v2_access( - client_id=client_id, - client_secret=client_secret, - code=code_param - ) - -A successful request to ``oauth.v2.access`` will yield a JSON payload with at least one token, a bot token that begins with ``xoxb``. - -.. code-block:: python - - @app.route("/slack/oauth_redirect", methods=["GET"]) - def post_install(): - # Verify the "state" parameter - - # Retrieve the auth code from the request params - code_param = request.args['code'] - - # An empty string is a valid token for this request - client = WebClient() - - # Request the auth tokens from Slack - response = client.oauth_v2_access( - client_id=client_id, - client_secret=client_secret, - code=code_param - ) - print(response) - - # Save the bot token to an environmental variable or to your data store - # for later use - os.environ["SLACK_BOT_TOKEN"] = response['access_token'] - - # Don't forget to let the user know that OAuth has succeeded! - return "Installation is completed!" - - if __name__ == "__main__": - app.run("localhost", 3000) - -Once your user has completed the OAuth flow, you'll be able to use the provided tokens to call any of Slack's API methods that require an access token. - -See the `Basic Usage <../basic_usage.html>`_ section of this documentation for usage examples. - -.. include:: ../metadata.rst diff --git a/docs-src/metadata.rst b/docs-src/metadata.rst deleted file mode 100644 index d7f207c07..000000000 --- a/docs-src/metadata.rst +++ /dev/null @@ -1,17 +0,0 @@ -.. Site settings -.. |product_name| replace:: Python Slack SDK -.. |email| replace:: opensource@slack.com -.. |repo_name| replace:: python-slack-sdk -.. |github_username| replace:: SlackAPI -.. |twitter_username| replace:: SlackAPI - -.. _Bot Developer Hangout: https://dev4slack.slack.com/archives/sdk-python-slack-sdk -.. _Issue Tracker: http://github.com/SlackAPI/python-slack-sdk/issues -.. _pull request: http://github.com/SlackAPI/python-slack-sdk/pulls -.. _License: https://github.com/SlackAPI/python-slack-sdk/blob/main/LICENSE -.. _Code of Conduct: https://slackhq.github.io/code-of-conduct -.. _Contributing: https://github.com/slackapi/python-slack-sdk/blob/main/.github/contributing.md -.. _contributing guidelines: https://github.com/slackapi/python-slack-sdk/blob/main/.github/contributing.md -.. _Contributor License Agreement: https://docs.google.com/a/slack-corp.com/forms/d/e/1FAIpQLSfzjVoCM7ohBnjWf7eDYQxzti1EPpinsIJQA5RAUBwJKRUQHg/viewform -.. _Real Time Messaging (RTM) API: https://api.slack.com/rtm -.. _Web API: https://api.slack.com/web diff --git a/docs-src/oauth/index.rst b/docs-src/oauth/index.rst deleted file mode 100644 index 910207bf3..000000000 --- a/docs-src/oauth/index.rst +++ /dev/null @@ -1,270 +0,0 @@ -============================================== -OAuth Modules -============================================== - -This section explains the details about how to handle Slack's OAuth flow. - -If you're looking for a much easier way to do the same, check `Bolt for Python `_, which is a full-stack Slack App framework. With Bolt, you don't need to implement most of the following code on your own. - -The Python document for this module is available at https://slack.dev/python-slack-sdk/api-docs/slack_sdk/ - -App Installation Flow -************************************************* - -OAuth lets a user in any Slack workspace install your app. At the end of OAuth, your app gains an access token. Refer to `Installing with OAuth `_ for details. - -Python Slack SDK provides the necessary modules for building the OAuth flow. - -**Starting an OAuth flow** - -The first step of Slack OAuth flow is to redirect a Slack user to https://slack.com/oauth/v2/authorize with a valida ``state`` parameter. To implement this process, you can use the following modules. - -+---------------------------+-----------------------------------------------------------------------------+---------------------------+ -| Module | What its for | Default Implementation | -+---------------------------+-----------------------------------------------------------------------------+---------------------------+ -| ``InstallationStore`` | Persist installation data and lookup it by IDs. | ``FileInstallationStore`` | -+---------------------------+-----------------------------------------------------------------------------+---------------------------+ -| ``OAuthStateStore`` | Issue and consume ``state`` parameter value on the server-side. | ``FileOAuthStateStore`` | -+---------------------------+-----------------------------------------------------------------------------+---------------------------+ -| ``AuthorizeUrlGenerator`` | Build https://slack.com/oauth/v2/authorize with sufficient query parameters | (same) | -+---------------------------+-----------------------------------------------------------------------------+---------------------------+ - -The code snippet below demonstrates how to build it using `Flask `_. - -.. code-block:: python - - import os - from slack_sdk.oauth import AuthorizeUrlGenerator - from slack_sdk.oauth.installation_store import FileInstallationStore, Installation - from slack_sdk.oauth.state_store import FileOAuthStateStore - - # Issue and consume state parameter value on the server-side. - state_store = FileOAuthStateStore(expiration_seconds=300, base_dir="./data") - # Persist installation data and lookup it by IDs. - installation_store = FileInstallationStore(base_dir="./data") - - # Build https://slack.com/oauth/v2/authorize with sufficient query parameters - authorize_url_generator = AuthorizeUrlGenerator( - client_id=os.environ["SLACK_CLIENT_ID"], - scopes=["app_mentions:read", "chat:write"], - user_scopes=["search:read"], - ) - - from flask import Flask, request, make_response - app = Flask(__name__) - - @app.route("/slack/install", methods=["GET"]) - def oauth_start(): - # Generate a random value and store it on the server-side - state = state_store.issue() - # https://slack.com/oauth/v2/authorize?state=(generated value)&client_id={client_id}&scope=app_mentions:read,chat:write&user_scope=search:read - url = authorize_url_generator.generate(state) - return f'' \ - f'' - -When accessing ``https://(your domain)/slack/install``, you will see "Add to Slack" button in the webpage. You can start the app's installation flow by clicking the button. - -**Handling a callback request from Slack** - -If all's well, a user goes through the Slack app installation UI and okays your app with all the scopes that it requests. After that happens, Slack redirects the user back to your specified Redirect URL. - -The redirection gives you a ``code`` parameter. You can exchange the value for an access token by calling `oauth.v2.access `_ API method. - -.. code-block:: python - - from slack_sdk.web import WebClient - client_secret = os.environ["SLACK_CLIENT_SECRET"] - - # Redirect URL - @app.route("/slack/oauth/callback", methods=["GET"]) - def oauth_callback(): - # Retrieve the auth code and state from the request params - if "code" in request.args: - # Verify the state parameter - if state_store.consume(request.args["state"]): - client = WebClient() # no prepared token needed for this - # Complete the installation by calling oauth.v2.access API method - oauth_response = client.oauth_v2_access( - client_id=client_id, - client_secret=client_secret, - redirect_uri=redirect_uri, - code=request.args["code"] - ) - - installed_enterprise = oauth_response.get("enterprise", {}) - is_enterprise_install = oauth_response.get("is_enterprise_install") - installed_team = oauth_response.get("team", {}) - installer = oauth_response.get("authed_user", {}) - incoming_webhook = oauth_response.get("incoming_webhook", {}) - - bot_token = oauth_response.get("access_token") - # NOTE: oauth.v2.access doesn't include bot_id in response - bot_id = None - enterprise_url = None - if bot_token is not None: - auth_test = client.auth_test(token=bot_token) - bot_id = auth_test["bot_id"] - if is_enterprise_install is True: - enterprise_url = auth_test.get("url") - - installation = Installation( - app_id=oauth_response.get("app_id"), - enterprise_id=installed_enterprise.get("id"), - enterprise_name=installed_enterprise.get("name"), - enterprise_url=enterprise_url, - team_id=installed_team.get("id"), - team_name=installed_team.get("name"), - bot_token=bot_token, - bot_id=bot_id, - bot_user_id=oauth_response.get("bot_user_id"), - bot_scopes=oauth_response.get("scope"), # comma-separated string - user_id=installer.get("id"), - user_token=installer.get("access_token"), - user_scopes=installer.get("scope"), # comma-separated string - incoming_webhook_url=incoming_webhook.get("url"), - incoming_webhook_channel=incoming_webhook.get("channel"), - incoming_webhook_channel_id=incoming_webhook.get("channel_id"), - incoming_webhook_configuration_url=incoming_webhook.get("configuration_url"), - is_enterprise_install=is_enterprise_install, - token_type=oauth_response.get("token_type"), - ) - - # Store the installation - installation_store.save(installation) - - return "Thanks for installing this app!" - else: - return make_response(f"Try the installation again (the state value is already expired)", 400) - - error = request.args["error"] if "error" in request.args else "" - return make_response(f"Something is wrong with the installation (error: {error})", 400) - -Token Lookup -************************************************* - -Now that your Flask app can choose the right access token for incoming event requests, let's add the Slack event handler endpoint. - -You can use the same ``InstallationStore`` in the Slack event handler. - -.. code-block:: python - - import json - from slack_sdk.errors import SlackApiError - - from slack_sdk.signature import SignatureVerifier - signing_secret = os.environ["SLACK_SIGNING_SECRET"] - signature_verifier = SignatureVerifier(signing_secret=signing_secret) - - @app.route("/slack/events", methods=["POST"]) - def slack_app(): - # Verify incoming requests from Slack - # https://api.slack.com/authentication/verifying-requests-from-slack - if not signature_verifier.is_valid( - body=request.get_data(), - timestamp=request.headers.get("X-Slack-Request-Timestamp"), - signature=request.headers.get("X-Slack-Signature")): - return make_response("invalid request", 403) - - # Handle a slash command invocation - if "command" in request.form \ - and request.form["command"] == "/open-modal": - try: - # in the case where this app gets a request from an Enterprise Grid workspace - enterprise_id = request.form.get("enterprise_id") - # The workspace's ID - team_id = request.form["team_id"] - # Lookup the stored bot token for this workspace - bot = installation_store.find_bot( - enterprise_id=enterprise_id, - team_id=team_id, - ) - bot_token = bot.bot_token if bot else None - if not bot_token: - # The app may be uninstalled or be used in a shared channel - return make_response("Please install this app first!", 200) - - # Open a modal using the valid bot token - client = WebClient(token=bot_token) - trigger_id = request.form["trigger_id"] - response = client.views_open( - trigger_id=trigger_id, - view={ - "type": "modal", - "callback_id": "modal-id", - "title": { - "type": "plain_text", - "text": "Awesome Modal" - }, - "submit": { - "type": "plain_text", - "text": "Submit" - }, - "blocks": [ - { - "type": "input", - "block_id": "b-id", - "label": { - "type": "plain_text", - "text": "Input label", - }, - "element": { - "action_id": "a-id", - "type": "plain_text_input", - } - } - ] - } - ) - return make_response("", 200) - except SlackApiError as e: - code = e.response["error"] - return make_response(f"Failed to open a modal due to {code}", 200) - - elif "payload" in request.form: - # Data submission from the modal - payload = json.loads(request.form["payload"]) - if payload["type"] == "view_submission" \ - and payload["view"]["callback_id"] == "modal-id": - submitted_data = payload["view"]["state"]["values"] - print(submitted_data) # {'b-id': {'a-id': {'type': 'plain_text_input', 'value': 'your input'}}} - # You can use WebClient with a valid token here too - return make_response("", 200) - - # Indicate unsupported request patterns - return make_response("", 404) - - -Again, if you're looking for an easier solution, take a look at `Bolt for Python `_. With Bolt, you don't need to implement most of the above code on your own. - -Sign in with Slack -************************************************* - -`Sign in with Slack `_ helps users log into your service using their Slack profile. The platform feature was recently upgraded to be compatible with the standard `OpenID Connect `_ specification. With slack-sdk v3.9+, implementing the auth flow is much easier. - -When you create a new Slack app, set the following user scopes: - -.. code-block:: yaml - - oauth_config: - redirect_urls: - - https://{your-domain}/slack/oauth_redirect - scopes: - user: - - openid # required - - email # optional - - profile # optional - -Check `the Flask app example `_ to learn how to implement your Web app that handles the OpenID Connect flow with end-users. It does the following: - -**Build the OpenID Connect authorize URL** - -- ``slack_sdk.oauth.OpenIDConnectAuthorizeUrlGenerator`` helps you easily do this -- ``slack_sdk.oauth.OAuthStateStore`` is still available for generating ``state`` parameter value. It's available for ``nonce`` management too. - -**openid.connect.* API calls** - -``WebClient`` can perform ``openid.connect.token`` API calls with given ``code`` parameter - -If you want to know the way with asyncio, check `the Sanic app example `_ in the same directory. - -.. include:: ../metadata.rst diff --git a/docs-src/real_time_messaging.rst b/docs-src/real_time_messaging.rst deleted file mode 100644 index ba2a502d9..000000000 --- a/docs-src/real_time_messaging.rst +++ /dev/null @@ -1,133 +0,0 @@ - -.. _real-time-messaging: - -============================================== -RTM Client -============================================== - -Real Time Messaging (RTM) ---------------------------------------- - -The `Real Time Messaging (RTM) API`_ is a WebSocket-based API that allows you to receive events from Slack in real time and send messages as users. - -If you prefer events to be pushed to your app, we recommend using the HTTP-based `Events API `_ along with `Socket Mode `_ instead. The Events API contains some events that aren't supported in the RTM API (like `app_home_opened event `_), and it supports most of the event types in the RTM API. If you'd like to use the Events API, you can use the `Python Slack Events Adaptor `_. - -The RTMClient allows apps to communicate with the Slack Platform's RTM API. - -The event-driven architecture of this client allows you to simply link callbacks to their corresponding events. When an event occurs this client executes your callback while passing along any information it receives. We also give you the ability to call our web client from inside your callbacks. - -In our example below, we watch for a `message event `_ that contains "Hello" and if its received, we call the ``say_hello()`` function. We then issue a call to the web client to post back to the channel saying "Hi" to the user. - -**Configuring the RTM API** - -Events using the RTM API **must** use a classic Slack app (with a plain ``bot`` scope). - -If you already have a classic Slack app, you can use those credentials. If you don't and need to use the RTM API, you can `create a classic Slack app `_. You can learn more in the `API documentation `_. - -Also, even if the Slack app configuration pages encourage you to upgrade to the newer permission model, don't upgrade it and keep using the "classic" bot permission. - -**Connecting to the RTM API** - -Note that the import here is not ``from slack_sdk.rtm import RTMClient`` but ``from slack_sdk.rtm_v2 import RTMClient`` (``_v2`` is added in the latter one). If you would like to use the legacy version of the client, go to the next section. - -.. code-block:: python - - import os - from slack_sdk.rtm_v2 import RTMClient - - rtm = RTMClient(token=os.environ["SLACK_BOT_TOKEN"]) - - @rtm.on("message") - def handle(client: RTMClient, event: dict): - if 'Hello' in event['text']: - channel_id = event['channel'] - thread_ts = event['ts'] - user = event['user'] # This is not username but user ID (the format is either U*** or W***) - - client.web_client.chat_postMessage( - channel=channel_id, - text=f"Hi <@{user}>!", - thread_ts=thread_ts - ) - - rtm.start() - - -**Connecting to the RTM API (v1 client)** - -Below is a code snippet that uses the legacy version of ``RTMClient``. For new app development, we **do not recommend** using it as it contains issues that have been resolved in v2. Please refer to the `list of these issues `_ for more details. - -.. code-block:: python - - import os - from slack_sdk.rtm import RTMClient - - @RTMClient.run_on(event="message") - def say_hello(**payload): - data = payload['data'] - web_client = payload['web_client'] - - if 'Hello' in data['text']: - channel_id = data['channel'] - thread_ts = data['ts'] - user = data['user'] # This is not username but user ID (the format is either U*** or W***) - - web_client.chat_postMessage( - channel=channel_id, - text=f"Hi <@{user}>!", - thread_ts=thread_ts - ) - - slack_token = os.environ["SLACK_BOT_TOKEN"] - rtm_client = RTMClient(token=slack_token) - rtm_client.start() - -**rtm.start vs rtm.connect (v1 client)** - -By default, the RTM client uses ``rtm.connect`` to establish a WebSocket connection with Slack. The response contains basic information about the team and WebSocket url. - -If you'd rather use ``rtm.start`` to establish the connection, which provides more information about the conversations and users on the team, you can set the ``connect_method`` option to ``rtm.start`` when instantiating the RTM Client. Note that on larger teams, use of ``rtm.start`` can be slow and unreliable. - -.. code-block:: python - - import os - from slack_sdk.rtm import RTMClient - - @RTMClient.run_on(event="message") - def say_hello(**payload): - data = payload['data'] - web_client = payload['web_client'] - if 'text' in data and 'Hello' in data['text']: - channel_id = data['channel'] - thread_ts = data['ts'] - user = data['user'] # This is not username but user ID (the format is either U*** or W***) - - web_client.chat_postMessage( - channel=channel_id, - text=f"Hi <@{user}>!", - thread_ts=thread_ts - ) - - slack_token = os.environ["SLACK_BOT_TOKEN"] - rtm_client = RTMClient( - token=slack_token, - connect_method='rtm.start' - ) - rtm_client.start() - -Read the `rtm.connect docs `_ and the `rtm.start docs `_ for more details. Also, note that ``slack.rtm_v2.RTMClient`` does not support ``rtm.start``. - -**RTM Events** - -.. code-block:: javascript - - { - 'type': 'message', - 'ts': '1358878749.000002', - 'user': 'U023BECGF', - 'text': 'Hello' - } - -See `RTM Events `_ for a complete list of events. - -.. include:: metadata.rst diff --git a/docs-src/scim/index.rst b/docs-src/scim/index.rst deleted file mode 100644 index 9599fdd49..000000000 --- a/docs-src/scim/index.rst +++ /dev/null @@ -1,158 +0,0 @@ -============================================== -SCIM API Client -============================================== - -`SCIM API `_ is a set of APIs for provisioning and managing user accounts and groups. SCIM is used by Single Sign-On (SSO) services and identity providers to manage people across a variety of tools, including Slack. - -`SCIM (System for Cross-domain Identity Management) `_ is supported by a myriad of services. It behaves slightly differently from other Slack APIs. - -Refer to `the API document `_ for more details. - -The Python document for this module is available at https://slack.dev/python-slack-sdk/api-docs/slack_sdk/ - -SCIMClient -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -An OAuth token with `the admin scope `_ is required to access the SCIM API. - -To fetch provisioned user data, you can use the ``search_users`` method in the client. - -.. code-block:: python - - import os - from slack_sdk.scim import SCIMClient - - client = SCIMClient(token=os.environ["SLACK_ORG_ADMIN_USER_TOKEN"]) - - response = client.search_users( - start_index=1, - count=100, - filter="""filter=userName Eq "Carly"""", - ) - response.users # List[User] - -Check out `the class source code `_ to learn more about the structure of the ``user`` in ``response.users``. - -Similarly, the ``search_groups`` method is available and the shape of the ``Group`` object can be `found here `_. - -.. code-block:: python - - response = client.search_groups( - start_index=1, - count=10, - ) - response.groups # List[Group] - -For creating, updating, and deleting users/groups: - -.. code-block:: python - - from slack_sdk.scim.v1.user import User, UserName, UserEmail - - # POST /Users - # Creates a user. Must include the user_name argument and at least one email address. - # You may provide an email address as the user_name value, - # but it will be automatically converted to a Slack-appropriate username. - user = User( - user_name="cal", - name=UserName(given_name="C", family_name="Henderson"), - emails=[UserEmail(value="your-unique-name@example.com")], - ) - creation_result = client.create_user(user) - - # PATCH /Users/{user_id} - # Updates an existing user resource, overwriting values for specified attributes. - patch_result = client.patch_user( - id=creation_result.user.id, - partial_user=User(user_name="chenderson"), - ) - - # PUT /Users/{user_id} - # Updates an existing user resource, overwriting all values for a user - # even if an attribute is empty or not provided. - user_to_update = patch_result.user - user_to_update.name = UserName(given_name="Cal", family_name="Henderson") - update_result = client.update_user(user=user_to_update) - - # DELETE /Users/{user_id} - # Sets a Slack user to deactivated. The value of the {id} - # should be the user's corresponding Slack ID, beginning with either U or W. - delete_result = client.delete_user(user_to_update.id) - -AsyncSCIMClient -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Lastly, if you are keen to use asyncio for SCIM API calls, we offer ``AsyncSCIMClient`` for it. This client relies on aiohttp library. - -.. code-block:: python - - import asyncio - import os - from slack_sdk.scim.async_client import AsyncSCIMClient - - client = AsyncSCIMClient(token=os.environ["SLACK_ORG_ADMIN_USER_TOKEN"]) - - async def main(): - response = await client.search_groups(start_index=1, count=2) - print(response.groups) - - asyncio.run(main()) - --------- - -RetryHandler -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -With the default settings, only ``ConnectionErrorRetryHandler`` with its default configuration (=only one retry in the manner of `exponential backoff and jitter `_) is enabled. The retry handler retries if an API client encounters a connectivity-related failure (e.g., Connection reset by peer). - -To use other retry handlers, you can pass a list of ``RetryHandler`` to the client constructor. For instance, you can add the built-in ``RateLimitErrorRetryHandler`` this way: - -.. code-block:: python - - import os - from slack_sdk.scim import SCIMClient - client = SCIMClient(token=os.environ["SLACK_ORG_ADMIN_USER_TOKEN"]) - - # This handler does retries when HTTP status 429 is returned - from slack_sdk.http_retry.builtin_handlers import RateLimitErrorRetryHandler - rate_limit_handler = RateLimitErrorRetryHandler(max_retry_count=1) - - # Enable rate limited error retries as well - client.retry_handlers.append(rate_limit_handler) - -Creating your own ones is also quite simple. Defining a new class that inherits ``slack_sdk.http_retry.RetryHandler`` (``AsyncRetryHandler`` for asyncio apps) and implements required methods (internals of ``can_retry`` / ``prepare_for_next_retry``). Check the built-in ones' source code for learning how to properly implement. - -.. code-block:: python - - import socket - from typing import Optional - from slack_sdk.http_retry import (RetryHandler, RetryState, HttpRequest, HttpResponse) - from slack_sdk.http_retry.builtin_interval_calculators import BackoffRetryIntervalCalculator - from slack_sdk.http_retry.jitter import RandomJitter - - class MyRetryHandler(RetryHandler): - def _can_retry( - self, - *, - state: RetryState, - request: HttpRequest, - response: Optional[HttpResponse] = None, - error: Optional[Exception] = None - ) -> bool: - # [Errno 104] Connection reset by peer - return error is not None and isinstance(error, socket.error) and error.errno == 104 - - client = SCIMClient( - token=os.environ["SLACK_ORG_ADMIN_USER_TOKEN"], - retry_handlers=[MyRetryHandler( - max_retry_count=1, - interval_calculator=BackoffRetryIntervalCalculator( - backoff_factor=0.5, - jitter=RandomJitter(), - ), - )], - ) - -For asyncio apps, ``Async`` prefixed corresponding modules are available. All the methods in those methods are async/await compatible. Check `the source code `_ and `tests `_ for more details. - -.. include:: ../metadata.rst diff --git a/docs-src/socket-mode/index.rst b/docs-src/socket-mode/index.rst deleted file mode 100644 index 610de29d4..000000000 --- a/docs-src/socket-mode/index.rst +++ /dev/null @@ -1,151 +0,0 @@ -============================================== -Socket Mode Client -============================================== - -Socket Mode is a method of connecting your app to Slackโ€™s APIs using WebSockets instead of HTTP. You can use ``slack_sdk.socket_mode.SocketModeClient`` for managing `Socket Mode `_ connections and performing interactions with Slack. - -The Python document for this module is available at https://slack.dev/python-slack-sdk/api-docs/slack_sdk/ - -SocketModeClient -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -First off, let's start with enabling Socket Mode. Visit `the Slack App configuration page `_, choose the app you're working on, and go to **Settings** on the left pane. There are a few things to do on the page. - -* Go to **Settings** > **Basic Information**, then add a new **App-Level Token** with the `connections:write` scope -* Go to **Settings** > **Socket Mode**, then turn on **Enable Socket Mode** - -You will be using the app-level token that starts with ``xapp-`` prefix. Note that the token here is not the ones starting with either ``xoxb-`` or ``xoxp-``. - -.. code-block:: python - - import os - from slack_sdk.web import WebClient - from slack_sdk.socket_mode import SocketModeClient - - # Initialize SocketModeClient with an app-level token + WebClient - client = SocketModeClient( - # This app-level token will be used only for establishing a connection - app_token=os.environ.get("SLACK_APP_TOKEN"), # xapp-A111-222-xyz - # You will be using this WebClient for performing Web API calls in listeners - web_client=WebClient(token=os.environ.get("SLACK_BOT_TOKEN")) # xoxb-111-222-xyz - ) - - from slack_sdk.socket_mode.response import SocketModeResponse - from slack_sdk.socket_mode.request import SocketModeRequest - - def process(client: SocketModeClient, req: SocketModeRequest): - if req.type == "events_api": - # Acknowledge the request anyway - response = SocketModeResponse(envelope_id=req.envelope_id) - client.send_socket_mode_response(response) - - # Add a reaction to the message if it's a new message - if req.payload["event"]["type"] == "message" \ - and req.payload["event"].get("subtype") is None: - client.web_client.reactions_add( - name="eyes", - channel=req.payload["event"]["channel"], - timestamp=req.payload["event"]["ts"], - ) - - # Add a new listener to receive messages from Slack - # You can add more listeners like this - client.socket_mode_request_listeners.append(process) - # Establish a WebSocket connection to the Socket Mode servers - client.connect() - # Just not to stop this process - from threading import Event - Event().wait() - --------- - -Supported Libraries -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -This SDK offers its own simple WebSocket client covering only required features for Socket Mode. In addition to that, ``SocketModeClient`` is implemented with a few 3rd party open-source libraries. If you prefer any of the following, you can use it over the built-in one. - -.. list-table:: - :header-rows: 1 - - * - PyPI Project - - SocketModeClient - * - `slack_sdk `_ - - `slack_sdk.socket_mode.SocketModeClient `_ - * - `websocket_client `_ - - `slack_sdk.socket_mode.websocket_client.SocketModeClient `_ - * - `aiohttp `_ (asyncio-based) - - `slack_sdk.socket_mode.aiohttp.SocketModeClient `_ - * - `websockets `_ (asyncio-based) - - `slack_sdk.socket_mode.websockets.SocketModeClient `_ - - -To use the `websocket_client `_ based one, all you need to do are to add `websocket_client `_ dependency and to change the import as below. - -.. code-block:: python - - # Note that the pockage is different - from slack_sdk.socket_mode.websocket_client import SocketModeClient - - client = SocketModeClient( - app_token=os.environ.get("SLACK_APP_TOKEN"), # xapp-A111-222-xyz - web_client=WebClient(token=os.environ.get("SLACK_BOT_TOKEN")) # xoxb-111-222-xyz - ) - -You can pass a few additional arguments that are specific to the library. Apart from that, all the functionalities work in the same way with the built-in version. - --------- - -Asyncio Based Libraries -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -To use the asyncio-based ones such as aiohttp, your app needs to be compatible with asyncio's async/await programming model. The `SocketModeClient` only works with `AsyncWebClient` and async listeners. - -.. code-block:: python - - import asyncio - import os - from slack_sdk.web.async_client import AsyncWebClient - from slack_sdk.socket_mode.aiohttp import SocketModeClient - - # Initialize SocketModeClient with an app-level token + AsyncWebClient - client = SocketModeClient( - # This app-level token will be used only for establishing a connection - app_token=os.environ.get("SLACK_APP_TOKEN"), # xapp-A111-222-xyz - # You will be using this AsyncWebClient for performing Web API calls in listeners - web_client=AsyncWebClient(token=os.environ.get("SLACK_BOT_TOKEN")) # xoxb-111-222-xyz - ) - - # Use async method - async def main(): - from slack_sdk.socket_mode.response import SocketModeResponse - from slack_sdk.socket_mode.request import SocketModeRequest - - # Use async method - async def process(client: SocketModeClient, req: SocketModeRequest): - if req.type == "events_api": - # Acknowledge the request anyway - response = SocketModeResponse(envelope_id=req.envelope_id) - # Don't forget having await for method calls - await client.send_socket_mode_response(response) - - # Add a reaction to the message if it's a new message - if req.payload["event"]["type"] == "message" \ - and req.payload["event"].get("subtype") is None: - await client.web_client.reactions_add( - name="eyes", - channel=req.payload["event"]["channel"], - timestamp=req.payload["event"]["ts"], - ) - - # Add a new listener to receive messages from Slack - # You can add more listeners like this - client.socket_mode_request_listeners.append(process) - # Establish a WebSocket connection to the Socket Mode servers - await client.connect() - # Just not to stop this process - await asyncio.sleep(float("inf")) - - # You can go with other way to run it. This is just for easiness to try it out. - asyncio.run(main()) - -.. include:: ../metadata.rst diff --git a/docs-src/v3-migration/index.rst b/docs-src/v3-migration/index.rst deleted file mode 100644 index 6a24a5995..000000000 --- a/docs-src/v3-migration/index.rst +++ /dev/null @@ -1,64 +0,0 @@ -============================================== -Migration Guide -============================================== - -The v2 website is live `here `_ just like before. However, the **slackclient** project is in maintenance mode now and this **slack_sdk** project is the successor. - -From slackclient 2.x -************************************************* - -There are a few changes introduced in v3.0: - -* The PyPI project is renamed from ``slackclient`` to ``slack_sdk`` -* Importing ``slack_sdk.*`` is recommended. You can still use ``slack.*`` with deprecation warnings for a while. -* ``slack_sdk`` has no required dependencies. This means ``aiohttp`` is no longer automatically resolved. -* ``WebClient`` no longer has ``run_async`` and ``aiohttp`` specific options. If you still need the option or other ``aiohttp`` specific options, use ``LegacyWebClient`` (``slackclient`` v2 compatible) or ``AsyncWebClient``. - -We're sorry for the inconvenience. - ------ - -**Change:** The PyPI project is renamed from ``slackclient`` to ``slack_sdk`` - -**Action**: Remove ``slackclient``, add ``slack_sdk`` in ``requirements.txt`` - -Since v3, the PyPI project name is `slack_sdk `_ (technically ``slack-sdk`` also works). - -The biggest reason for the renaming is the feature coverage in v3 and newer. The SDK v3 provides not only API clients but also other modules. As the first step, it starts supporting OAuth flow out-of-the-box. The secondary reason is to make the names more consistent. The renaming addresses the long-lived confusion between the PyPI project and package names. - ------ - -**Change:** Importing ``slack_sdk.*`` is recommended. You can still use ``slack.*`` with deprecation warnings for a while. - -**Action**: Replace ``from slack import``, ``import slack``, and so on in your source code. - - -Most imports can be simply replaced by ``find your_app -name '*.py' | xargs sed -i '' 's/from slack /from slack_sdk /g'`` or something similar. If you use ``slack.web.classes.*``, the conversion is not so simple that we recommend manually replacing imports for those. - -That said, all existing code can be migrated to v3 without any code changes. If you don't have time for it, you can use ``slack`` package with deprecation warnings saying ``UserWarning: slack package is deprecated. Please use slack_sdk.web/webhook/rtm package instead. For more info, go to https://slack.dev/python-slack-sdk/v3-migration/`` for a while. We won't remove the compatibility in the short term. - ------ - -**Change:** ``slack_sdk`` has no required dependencies. This means ``aiohttp`` is no longer automatically resolved. - -**Action**: Add ``aiohttp`` to ``requirements.txt`` if you use any of ``AsyncWebClient``, ``AsyncWebhookClient``, and ``LegacyWebClient`` - -If you use some modules that require ``aiohttp``, your ``requirements.txt`` needs to explicitly have ``aiohttp``. The ``slack_sdk`` dependency doesn't resolve it for you, unlike ``slackclient`` v2. - - ------ - -**Change:** ``WebClient`` no longer has ``run_async`` and ``aiohttp`` specific options. - -**Action:** If you still need the option or other ``aiohttp`` specific options, use ``LegacyWebClient`` (``slackclient`` v2 compatible) or ``AsyncWebClient``. - -The new ``slack_sdk.web.WebClient`` doesn't rely on ``aiohttp`` internally at all. The class provides only the synchronous way to call Web APIs. If you need a v2 compatible one, you can use ``LegacyWebClient``. Apart from the name, there is no breaking change in the class. - -If you're using ``run_async=True`` option, we highly recommend switching to ``AsyncWebClient``. ``AsyncWebClient`` is a straight-forward async HTTP client. You can expect the class properly works in the nature of ``async/await`` provided by the standard ``asyncio`` library. - -From slackclient 1.x -************************************************* - -Refer to `the migration guide `_. - -.. include:: ../metadata.rst diff --git a/docs-src/web/index.rst b/docs-src/web/index.rst deleted file mode 100644 index 74dfef7aa..000000000 --- a/docs-src/web/index.rst +++ /dev/null @@ -1,713 +0,0 @@ -============================================== -Web Client -============================================== - -The Slack Web API allows you to build applications that interact with Slack in more complex ways than the integrations we provide out of the box. - -Access Slack's API methods requires an OAuth token -- see the `Tokens & Authentication <../installation/index.html>`_ section for more on how Slack uses OAuth tokens as well as best practices. - -`Each of these API methods `_ is fully documented on our developer site at https://api.slack.com/ - -The Python document for this module is available at https://slack.dev/python-slack-sdk/api-docs/slack_sdk/ - -Messaging -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -**Sending a message** - -One of the primary uses of Slack is posting messages to a channel using the channel ID or as a DM to another person using their user ID. This method will handle either a channel ID or a user ID passed to the ``channel`` parameter. - -Note that your app's bot user needs to be in the channel (otherwise, you will get either ``not_in_channel`` or ``channel_not_found`` error code). If your app has `chat:write.public `_ scope, your app can post messages without joining a channel as long as the channel is public. See `chat.postMessage `_ for more info. - -.. code-block:: python - - import logging - logging.basicConfig(level=logging.DEBUG) - - import os - from slack_sdk import WebClient - from slack_sdk.errors import SlackApiError - - slack_token = os.environ["SLACK_BOT_TOKEN"] - client = WebClient(token=slack_token) - - try: - response = client.chat_postMessage( - channel="C0XXXXXX", - text="Hello from your app! :tada:" - ) - except SlackApiError as e: - # You will get a SlackApiError if "ok" is False - assert e.response["error"] # str like 'invalid_auth', 'channel_not_found' - -Sending an ephemeral message, which is only visible to an assigned user in a specified channel, is nearly the same -as sending a regular message, but with an additional ``user`` parameter. - -.. code-block:: python - - import os - from slack_sdk import WebClient - - slack_token = os.environ["SLACK_BOT_TOKEN"] - client = WebClient(token=slack_token) - - response = client.chat_postEphemeral( - channel="C0XXXXXX", - text="Hello silently from your app! :tada:", - user="U0XXXXXXX" - ) - -See `chat.postEphemeral `_ for more info. - -**Formatting with Block Kit** - -Messages posted from apps can contain more than just text, though. They can include full user interfaces composed of `blocks `_. - -The chat.postMessage method takes an optional ``blocks`` argument that allows you to customize the layout of a message. Blocks can be specified in a single array of either dict values or `slack_sdk.models.blocks.Block `_ objects. - -To send a message to a channel, use the channel's ID. For IMs, use the user's ID. - -.. code-block:: python - - client.chat_postMessage( - channel="C0XXXXXX", - blocks=[ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "Danny Torrence left the following review for your property:" - } - }, - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": " \n :star: \n Doors had too many axe holes, guest in room " + - "237 was far too rowdy, whole place felt stuck in the 1920s." - }, - "accessory": { - "type": "image", - "image_url": "https://images.pexels.com/photos/750319/pexels-photo-750319.jpeg", - "alt_text": "Haunted hotel image" - } - }, - { - "type": "section", - "fields": [ - { - "type": "mrkdwn", - "text": "*Average Rating*\n1.0" - } - ] - } - ] - ) - -**Note:** You can use the `Block Kit Builder `_ to prototype your message's look and feel. - -**Threading Messages** - -Threaded messages are a way of grouping messages together to provide greater context. You can reply to a thread or start a new threaded conversation by simply passing the original message's ``ts`` ID in the ``thread_ts`` attribute when posting a message. If you're replying to a threaded message, you'll pass the `thread_ts` ID of the message you're replying to. - -A channel or DM conversation is a nearly linear timeline of messages exchanged between people, bots, and apps. When one of these messages is replied to, it becomes the parent of a thread. By default, threaded replies do not appear directly in the channel, instead relegated to a kind of forked timeline descending from the parent message. - -.. code-block:: python - - response = client.chat_postMessage( - channel="C0XXXXXX", - thread_ts="1476746830.000003", - text="Hello from your app! :tada:" - ) - -By default, ``reply_broadcast`` is set to ``False``. To indicate your reply is germane to all members of a channel, and therefore a notification of the reply should be posted in-channel, set the ``reply_broadcast`` to ``True``. - -.. code-block:: python - - response = client.chat_postMessage( - channel="C0XXXXXX", - thread_ts="1476746830.000003", - text="Hello from your app! :tada:", - reply_broadcast=True - ) - -**Note:** While threaded messages may contain attachments and message buttons, when your reply is broadcast to the -channel, it'll actually be a reference to your reply, not the reply itself. -So, when appearing in the channel, it won't contain any attachments or message buttons. Also note that updates and -deletion of threaded replies works the same as regular messages. - -See the `Threading messages together `_ article for more information. - -**Updating a message** - -Let's say you have a bot which posts the status of a request. When that request changes, you'll want to update the message to reflect it's state. - -.. code-block:: python - - response = client.chat_update( - channel="C0XXXXXX", - ts="1476746830.000003", - text="updates from your app! :tada:" - ) - -See `chat.update `_ for formatting options and some special considerations when calling this with a bot user. - -**Deleting a message** - -Sometimes you need to delete things. - -.. code-block:: python - - response = client.chat_delete( - channel="C0XXXXXX", - ts="1476745373.000002" - ) - -See `chat.delete `_ for more info. - - -**Emoji reactions** - -You can quickly respond to any message on Slack with an emoji reaction. Reactions can be used for any purpose: voting, checking off to-do items, showing excitement -โ€” or just for fun. - -This method adds a reaction (emoji) to an item (``file``, ``file comment``, ``channel message``, ``group message``, or ``direct message``). One of file, file_comment, or the combination of channel and timestamp must be specified. Also, note that your app's bot user needs to be in the channel (otherwise, you will get either ``not_in_channel`` or ``channel_not_found`` error code). - -.. code-block:: python - - response = client.reactions_add( - channel="C0XXXXXXX", - name="thumbsup", - timestamp="1234567890.123456" - ) - -Removing an emoji reaction is basically the same format, but you'll use ``reactions.remove`` instead of ``reactions.add`` - -.. code-block:: python - - response = client.reactions_remove( - channel="C0XXXXXXX", - name="thumbsup", - timestamp="1234567890.123456" - ) - - -See `reactions.add `_ and `reactions.remove `_ for more info. - --------- - -Files -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -**Uploading files** - -You can upload files onto Slack and share the file with people in channels. Note that your app's bot user needs to be in the channel (otherwise, you will get either ``not_in_channel`` or ``channel_not_found`` error code). - -.. code-block:: python - - response = client.files_upload( - channels="C3UKJTQAC", - file="files.pdf", - title="Test upload" - ) - -See `files.upload `_ for more info. - -**Adding a remote file** - -You can add a file information that is stored in an external storage, not in Slack. - -.. code-block:: python - - response = client.files_remote_add( - external_id="the-all-hands-deck-12345", - external_url="https://{your domain}/files/the-all-hands-deck-12345", - title="The All-hands Deck", - preview_image="./preview.png" # will be displayed in channels - ) - -See `files.remote.add `_ for more info. - - --------- - -Conversations -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The Slack Conversations API provides your app with a unified interface to work with all the channel-like things encountered in Slack; public channels, private channels, direct messages, group direct messages, and our newest channel type, Shared Channels. - -See `Conversations API `_ docs for more info. - -**Start a direct message** - -The ``conversations_open`` method opens either a 1:1 direct message with a single user or a a multi-person direct message, depending on the number of users supplied to the ``users`` parameter. - -*For public or private channels, use the conversations_create method.* - -Provide a ``users`` parameter as an array with 1 to 8 user IDs to open or resume a conversation. Providing only 1 ID will create a direct message. Providing more will create a new multi-party DM or resume an existing conversation. - -Subsequent calls to ``conversations_open`` with the same set of users will return the already existing conversation. - -.. code-block:: python - - import os - from slack_sdk import WebClient - - client = WebClient(token=os.environ["SLACK_BOT_TOKEN"]) - response = client.conversations_open(users=["W123456789", "U987654321"]) - -See `conversations.open `_ additional info. - -**Creating channels** - -Creates a new channel, either public or private. The ``name`` parameter is required, may contain numbers, letters, hyphens, and underscores, and must contain fewer than 80 characters. To make the channel private, set the option ``is_private`` parameter to ``True``. - -.. code-block:: python - - import os - from slack_sdk import WebClient - from time import time - - client = WebClient(token=os.environ["SLACK_BOT_TOKEN"]) - channel_name = f"my-private-channel-{round(time())}" - response = client.conversations_create( - name=channel_name, - is_private=True - ) - channel_id = response["channel"]["id"] - response = client.conversations_archive(channel=channel_id) - -See `conversations.create `_ additional info. - -**Listing conversations** - -To get a list of all the conversations in a workspace, use ``conversations_list``. By default, only public conversations are returned; use the ``types`` parameter specify which types of conversations you're interested in (Note: ``types`` is a string of comma-separated values) - -.. code-block:: python - - import os - from slack_sdk import WebClient - - client = WebClient(token=os.environ["SLACK_BOT_TOKEN"]) - response = client.conversations_list() - conversations = response["channels"] - -Use the ``types`` parameter to request additional channels, including ``public_channel``, ``private_channel``, ``mpim``, and ``im``. This parameter is a string of comma-separated values. - -.. code-block:: python - - import os - from slack_sdk import WebClient - - client = WebClient(token=os.environ["SLACK_BOT_TOKEN"]) - response = client.conversations_list( - types="public_channel, private_channel" - ) - -See `conversations.list `_ for more info. - -Archived channels are included by default. You can exclude them by passing ``exclude_archived=True`` to your request. - -.. code-block:: python - - response = client.conversations_list(exclude_archived=True) - -See `conversations.list `_ for more info. - -**Getting a conversation information** - -To retrieve a set of metadata about a channel (public, private, DM, or multi-party DM), use ``conversations_info``. The ``channel`` parameter is required and must be a valid channel ID. The optional ``include_locale`` boolean parameter will return locale data, which may be useful if you wish to return localized responses. The ``include_num_members`` boolean parameter will return the number of people in a channel. - -.. code-block:: python - - import os - from slack_sdk import WebClient - - client = WebClient(token=os.environ["SLACK_BOT_TOKEN"]) - response = client.conversations_info( - channel="C031415926", - include_num_members=1 - ) - -See `conversations.info `_ for more info. - -**Getting members of a conversation** - -To get a list of the members of a conversation, use ``conversations_members`` with the required ``channel`` parameter. - -.. code-block:: python - - import os - from slack_sdk import WebClient - - client = WebClient(token=os.environ["SLACK_BOT_TOKEN"]) - response = client.conversations_members(channel="C16180339") - user_ids = response["members"] - -See `conversations.members `_ for more info. - -**Joining a conversation** - -Channels are the social hub of most Slack teams. Here's how you hop into one: - -.. code-block:: python - - response = client.conversations_join(channel="C0XXXXXXY") - -If you are already in the channel, the response is slightly different. -``already_in_channel`` will be true, and a limited ``channel`` object will be returned. Bot users cannot join a channel on their own, they need to be invited by another user. - -See `conversations.join `_ for more info. - -**Leaving a conversation** - -To leave a conversation, use ``conversations_leave`` with the required ``channel`` param containing the ID of the channel to leave. - -.. code-block:: python - - import os - from slack_sdk import WebClient - - client = WebClient(token=os.environ["SLACK_BOT_TOKEN"]) - response = client.conversations_leave(channel="C27182818") - -See `conversations.leave `_ for more info. - --------- - -Modals -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -**Opening a modal** - -Modals allow you to collect data from users and display dynamic information in a focused surface. - -Modals use the same ``blocks`` that compose messages with the addition of an ``input`` block. - -.. code-block:: python - - from slack_sdk.signature import SignatureVerifier - signature_verifier = SignatureVerifier(os.environ["SLACK_SIGNING_SECRET"]) - - from flask import Flask, request, make_response, jsonify - app = Flask(__name__) - - @app.route("/slack/events", methods=["POST"]) - def slack_app(): - if not signature_verifier.is_valid_request(request.get_data(), request.headers): - return make_response("invalid request", 403) - - if "payload" in request.form: - payload = json.loads(request.form["payload"]) - if payload["type"] == "shortcut" and payload["callback_id"] == "test-shortcut": - # Open a new modal by a global shortcut - try: - api_response = client.views_open( - trigger_id=payload["trigger_id"], - view={ - "type": "modal", - "callback_id": "modal-id", - "title": { - "type": "plain_text", - "text": "Awesome Modal" - }, - "submit": { - "type": "plain_text", - "text": "Submit" - }, - "blocks": [ - { - "type": "input", - "block_id": "b-id", - "label": { - "type": "plain_text", - "text": "Input label", - }, - "element": { - "action_id": "a-id", - "type": "plain_text_input", - } - } - ] - } - ) - return make_response("", 200) - except SlackApiError as e: - code = e.response["error"] - return make_response(f"Failed to open a modal due to {code}", 200) - - if ( - payload["type"] == "view_submission" - and payload["view"]["callback_id"] == "modal-id" - ): - # Handle a data submission request from the modal - submitted_data = payload["view"]["state"]["values"] - print(submitted_data) # {'b-id': {'a-id': {'type': 'plain_text_input', 'value': 'your input'}}} - - # Close this modal with an empty response body - return make_response("", 200) - - return make_response("", 404) - - if __name__ == "__main__": - # export SLACK_SIGNING_SECRET=*** - # export SLACK_BOT_TOKEN=xoxb-*** - # export FLASK_ENV=development - # python3 app.py - app.run("localhost", 3000) - -See `views.open `_ more details and additional parameters. - -Also, to run the above example, the following `Slack app configurations `_ are required. - -* Enable **Interactivity** with a valid Request URL: ``https://{your-public-domain}/slack/events`` -* Add a global shortcut with the Callback ID: ``open-modal-shortcut`` - -**Updating and pushing modals** - -In response to `view_submission` requests, you can tell Slack to update the current modal view by having `"response_action": "update"` and an updated view. Also, there are other response_action types such as `errors` and `push`. Refer to `the API document `_ for more details. - -.. code-block:: python - - if ( - payload["type"] == "view_submission" - and payload["view"]["callback_id"] == "modal-id" - ): - # Handle a data submission request from the modal - submitted_data = payload["view"]["state"]["values"] - print(submitted_data) # {'b-id': {'a-id': {'type': 'plain_text_input', 'value': 'your input'}}} - - # Update the modal with a new view - return make_response( - jsonify( - { - "response_action": "update", - "view": { - "type": "modal", - "title": {"type": "plain_text", "text": "Accepted"}, - "close": {"type": "plain_text", "text": "Close"}, - "blocks": [ - { - "type": "section", - "text": { - "type": "plain_text", - "text": "Thanks for submitting the data!", - }, - } - ], - }, - } - ), - 200, - ) - -If your app modify the current modal view when receiving `block_actions` requests from Slack, you can call `views.update` API method with the given view ID. - -.. code-block:: python - - private_metadata = "any str data you want to store" - response = client.views_update( - view_id=payload["view"]["id"], - hash=payload["view"]["hash"], - view={ - "type": "modal", - "callback_id": "modal-id", - "private_metadata": private_metadata, - "title": { - "type": "plain_text", - "text": "Awesome Modal" - }, - "submit": { - "type": "plain_text", - "text": "Submit" - }, - "close": { - "type": "plain_text", - "text": "Cancel" - }, - "blocks": [ - { - "type": "input", - "block_id": "b-id", - "label": { - "type": "plain_text", - "text": "Input label", - }, - "element": { - "action_id": "a-id", - "type": "plain_text_input", - } - } - ] - } - ) - -See `views.update `_ for more info. - -If you want to push a new view onto the modal instead of updating an existing view, reference the `views.push `_ documentation. - - --------- - -Rate Limits -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -When posting messages to a channel, Slack allows applications to send no more than one message per channel per second. We allow bursts over that limit for short periods. However, if your app continues to exceed the limit over a longer period of time it will be rate limited. Different API methods have other rate limits -- be sure to `check the limits `_ and test that your application has a graceful fallback if it should hit those limits. - -If you go over these limits, Slack will start returning a HTTP 429 Too Many Requests error, a JSON object containing the number of calls you have been making, and a Retry-After header containing the number of seconds until you can retry. - -Here's a very basic example of how one might deal with rate limited requests. - -.. code-block:: python - - import os - import time - from slack_sdk import WebClient - from slack_sdk.errors import SlackApiError - - client = WebClient(token=os.environ["SLACK_BOT_TOKEN"]) - - # Simple wrapper for sending a Slack message - def send_slack_message(channel, message): - return client.chat_postMessage( - channel=channel, - text=message - ) - - # Make the API call and save results to `response` - channel = "#random" - message = "Hello, from Python!" - # Do until being rate limited - while True: - try: - response = send_slack_message(channel, message) - except SlackApiError as e: - if e.response.status_code == 429: - # The `Retry-After` header will tell you how long to wait before retrying - delay = int(e.response.headers['Retry-After']) - print(f"Rate limited. Retrying in {delay} seconds") - time.sleep(delay) - response = send_slack_message(channel, message) - else: - # other errors - raise e - -Since v3.9.0, the built-in ``RateLimitErrorRetryHandler`` is available as an easier way to do the retries for rate limited errors. Refer to the RetryHandler section in this page for more details. - -To learn the Slack rate limits in general, see the documentation on `Rate Limiting `_. - --------- - -Calling any API methods --------------------------- - -This library covers all the public endpoints as the methods in ``WebClient``. That said, you may see a bit delay of the library release. When you're in a hurry, you can directly use ``api_call`` method as below. - -.. code-block:: python - - import os - from slack_sdk import WebClient - - client = WebClient(token=os.environ['SLACK_BOT_TOKEN']) - response = client.api_call( - api_method='chat.postMessage', - params={'channel': '#random','text': "Hello world!"} - ) - assert response["message"]["text"] == "Hello world!" - - --------- - -AsyncWebClient -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -All the API methods are available in asynchronous programming using the standard `asyncio `_ library. You use ``AsyncWebClient`` instead for it. - -``AsyncWebClient`` internally relies on `AIOHTTP `_ library but it is an optional dependency. So, to use this class, run ``pip install aiohttp`` beforehand. - -.. code-block:: python - - import asyncio - import os - # requires: pip install aiohttp - from slack_sdk.web.async_client import AsyncWebClient - from slack_sdk.errors import SlackApiError - - client = AsyncWebClient(token=os.environ['SLACK_API_TOKEN']) - - # This must be an async method - async def post_message(): - try: - # Don't forget `await` keyword here - response = await client.chat_postMessage( - channel='#random', - text="Hello world!" - ) - assert response["message"]["text"] == "Hello world!" - except SlackApiError as e: - assert e.response["ok"] is False - assert e.response["error"] # str like 'invalid_auth', 'channel_not_found' - print(f"Got an error: {e.response['error']}") - - # This is the simplest way to run the async method - # but you can go with any ways to run it - asyncio.run(post_message()) - - --------- - -RetryHandler -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -With the default settings, only ``ConnectionErrorRetryHandler`` with its default configuration (=only one retry in the manner of `exponential backoff and jitter `_) is enabled. The retry handler retries if an API client encounters a connectivity-related failure (e.g., Connection reset by peer). - -To use other retry handlers, you can pass a list of ``RetryHandler`` to the client constructor. For instance, you can add the built-in ``RateLimitErrorRetryHandler`` this way: - -.. code-block:: python - - import os - from slack_sdk.web import WebClient - client = WebClient(token=os.environ["SLACK_BOT_TOKEN"]) - - # This handler does retries when HTTP status 429 is returned - from slack_sdk.http_retry.builtin_handlers import RateLimitErrorRetryHandler - rate_limit_handler = RateLimitErrorRetryHandler(max_retry_count=1) - - # Enable rate limited error retries as well - client.retry_handlers.append(rate_limit_handler) - -Creating your own ones is also quite simple. Defining a new class that inherits ``slack_sdk.http_retry.RetryHandler`` (``AsyncRetryHandler`` for asyncio apps) and implements required methods (internals of ``can_retry`` / ``prepare_for_next_retry``). Check the built-in ones' source code for learning how to properly implement. - -.. code-block:: python - - import socket - from typing import Optional - from slack_sdk.http_retry import (RetryHandler, RetryState, HttpRequest, HttpResponse) - from slack_sdk.http_retry.builtin_interval_calculators import BackoffRetryIntervalCalculator - from slack_sdk.http_retry.jitter import RandomJitter - - class MyRetryHandler(RetryHandler): - def _can_retry( - self, - *, - state: RetryState, - request: HttpRequest, - response: Optional[HttpResponse] = None, - error: Optional[Exception] = None - ) -> bool: - # [Errno 104] Connection reset by peer - return error is not None and isinstance(error, socket.error) and error.errno == 104 - - client = WebClient( - token=os.environ["SLACK_BOT_TOKEN"], - retry_handlers=[MyRetryHandler( - max_retry_count=1, - interval_calculator=BackoffRetryIntervalCalculator( - backoff_factor=0.5, - jitter=RandomJitter(), - ), - )], - ) - -For asyncio apps, ``Async`` prefixed corresponding modules are available. All the methods in those methods are async/await compatible. Check `the source code `_ and `tests `_ for more details. - - -.. include:: ../metadata.rst diff --git a/docs-src/webhook/index.rst b/docs-src/webhook/index.rst deleted file mode 100644 index 3a498096b..000000000 --- a/docs-src/webhook/index.rst +++ /dev/null @@ -1,167 +0,0 @@ -============================================== -Webhook Client -============================================== - -You can use ``slack_sdk.webhook.WebhookClient`` for `Incoming Webhooks `_ and message responses using `response_url in payloads `_. - -The Python document for this module is available at https://slack.dev/python-slack-sdk/api-docs/slack_sdk/ - -Incoming Webhooks -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -To use `Incoming Webhooks `_, just calling ``WebhookClient(url)#send(payload)`` method works for you. The call posts a message in a channel associated with the webhook URL. - -.. code-block:: python - - from slack_sdk.webhook import WebhookClient - url = "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX" - webhook = WebhookClient(url) - - response = webhook.send(text="Hello!") - assert response.status_code == 200 - assert response.body == "ok" - -It's also possible to use ``blocks``, richer message using `Block Kit `_. - -.. code-block:: python - - from slack_sdk.webhook import WebhookClient - url = "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX" - webhook = WebhookClient(url) - response = webhook.send( - text="fallback", - blocks=[ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "You have a new request:\n**" - } - } - ] - ) - - -response_url -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -User actions in channels generates a `response_url `_ and includes the URL in its payload. You can use ``WebhookClient`` to send a message via the ``response_url``. - -.. code-block:: python - - import os - from slack_sdk.signature import SignatureVerifier - signature_verifier = SignatureVerifier( - signing_secret=os.environ["SLACK_SIGNING_SECRET"] - ) - - from slack_sdk.webhook import WebhookClient - - from flask import Flask, request, make_response - app = Flask(__name__) - - @app.route("/slack/events", methods=["POST"]) - def slack_app(): - # Verify incoming requests from Slack - # https://api.slack.com/authentication/verifying-requests-from-slack - if not signature_verifier.is_valid( - body=request.get_data(), - timestamp=request.headers.get("X-Slack-Request-Timestamp"), - signature=request.headers.get("X-Slack-Signature")): - return make_response("invalid request", 403) - - # Handle a slash command invocation - if "command" in request.form \ - and request.form["command"] == "/reply-this": - response_url = request.form["response_url"] - text = request.form["text"] - webhook = WebhookClient(response_url) - # Send a reply in the channel - response = webhook.send(text=f"You said '{text}'") - # Acknowledge this request - return make_response("", 200) - - return make_response("", 404) - -AsyncWebhookClient -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The webhook client is available in asynchronous programming using the standard `asyncio `_ library, too. You use ``AsyncWebhookClient`` instead for it. - -``AsyncWebhookClient`` internally relies on `AIOHTTP `_ library but it is an optional dependency. So, to use this class, run ``pip install aiohttp`` beforehand. - -.. code-block:: python - - import asyncio - # requires: pip install aiohttp - from slack_sdk.webhook.async_client import AsyncWebhookClient - url = "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX" - - async def send_message_via_webhook(url: str): - webhook = AsyncWebhookClient(url) - response = await webhook.send(text="Hello!") - assert response.status_code == 200 - assert response.body == "ok" - - # This is the simplest way to run the async method - # but you can go with any ways to run it - asyncio.run(send_message_via_webhook(url)) - --------- - -RetryHandler -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -With the default settings, only ``ConnectionErrorRetryHandler`` with its default configuration (=only one retry in the manner of `exponential backoff and jitter `_) is enabled. The retry handler retries if an API client encounters a connectivity-related failure (e.g., Connection reset by peer). - -To use other retry handlers, you can pass a list of ``RetryHandler`` to the client constructor. For instance, you can add the built-in ``RateLimitErrorRetryHandler`` this way: - -.. code-block:: python - - from slack_sdk.webhook import WebhookClient - url = "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX" - webhook = WebhookClient(url=url) - - # This handler does retries when HTTP status 429 is returned - from slack_sdk.http_retry.builtin_handlers import RateLimitErrorRetryHandler - rate_limit_handler = RateLimitErrorRetryHandler(max_retry_count=1) - - # Enable rate limited error retries as well - client.retry_handlers.append(rate_limit_handler) - -Creating your own ones is also quite simple. Defining a new class that inherits ``slack_sdk.http_retry.RetryHandler`` (``AsyncRetryHandler`` for asyncio apps) and implements required methods (internals of ``can_retry`` / ``prepare_for_next_retry``). Check the built-in ones' source code for learning how to properly implement. - -.. code-block:: python - - import socket - from typing import Optional - from slack_sdk.http_retry import (RetryHandler, RetryState, HttpRequest, HttpResponse) - from slack_sdk.http_retry.builtin_interval_calculators import BackoffRetryIntervalCalculator - from slack_sdk.http_retry.jitter import RandomJitter - - class MyRetryHandler(RetryHandler): - def _can_retry( - self, - *, - state: RetryState, - request: HttpRequest, - response: Optional[HttpResponse] = None, - error: Optional[Exception] = None - ) -> bool: - # [Errno 104] Connection reset by peer - return error is not None and isinstance(error, socket.error) and error.errno == 104 - - webhook = WebhookClient( - url=url, - retry_handlers=[MyRetryHandler( - max_retry_count=1, - interval_calculator=BackoffRetryIntervalCalculator( - backoff_factor=0.5, - jitter=RandomJitter(), - ), - )], - ) - -For asyncio apps, ``Async`` prefixed corresponding modules are available. All the methods in those methods are async/await compatible. Check `the source code `_ and `tests `_ for more details. - -.. include:: ../metadata.rst diff --git a/docs-v2/.buildinfo b/docs-v2/.buildinfo deleted file mode 100644 index 1862a8620..000000000 --- a/docs-v2/.buildinfo +++ /dev/null @@ -1,4 +0,0 @@ -# Sphinx build info version 1 -# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. -config: 5aeb40b1c45c7bb97acf441950bfceb8 -tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs-v2/about.html b/docs-v2/about.html deleted file mode 100644 index 0d80bc871..000000000 --- a/docs-v2/about.html +++ /dev/null @@ -1,287 +0,0 @@ - - - - - - About — slackclient (Legacy Python Slack SDK) - - - - - - - - - - - - - - - - - - -
- - - - - - - slackclient (Legacy Python Slack SDK) - - -
- - -
-
- - - - - -
- -
-
-

Aboutยถ

-
-

slackclient (Legacy Python Slack SDK)ยถ

-

Access the Slack Platform from your Python app. slackclient (Legacy Python Slack SDK) lets you build on the Slack Web APIs pythonically.

-

slackclient (Legacy Python Slack SDK) is proudly maintained with ๐Ÿ’– by the Slack Developer Tools team

- -
-
- - -
-
-
- -
-
- -
-

- ยฉ 2015- Slack Technologies, LLC and contributors -

-
- - - - - \ No newline at end of file diff --git a/docs-v2/assets/basic.css b/docs-v2/assets/basic.css deleted file mode 100644 index be19270e4..000000000 --- a/docs-v2/assets/basic.css +++ /dev/null @@ -1,856 +0,0 @@ -/* - * basic.css - * ~~~~~~~~~ - * - * Sphinx stylesheet -- basic theme. - * - * :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * - */ - -/* -- main layout ----------------------------------------------------------- */ - -div.clearer { - clear: both; -} - -div.section::after { - display: block; - content: ''; - clear: left; -} - -/* -- relbar ---------------------------------------------------------------- */ - -div.related { - width: 100%; - font-size: 90%; -} - -div.related h3 { - display: none; -} - -div.related ul { - margin: 0; - padding: 0 0 0 10px; - list-style: none; -} - -div.related li { - display: inline; -} - -div.related li.right { - float: right; - margin-right: 5px; -} - -/* -- sidebar --------------------------------------------------------------- */ - -div.sphinxsidebarwrapper { - padding: 10px 5px 0 10px; -} - -div.sphinxsidebar { - float: left; - width: 230px; - margin-left: -100%; - font-size: 90%; - word-wrap: break-word; - overflow-wrap : break-word; -} - -div.sphinxsidebar ul { - list-style: none; -} - -div.sphinxsidebar ul ul, -div.sphinxsidebar ul.want-points { - margin-left: 20px; - list-style: square; -} - -div.sphinxsidebar ul ul { - margin-top: 0; - margin-bottom: 0; -} - -div.sphinxsidebar form { - margin-top: 10px; -} - -div.sphinxsidebar input { - border: 1px solid #98dbcc; - font-family: sans-serif; - font-size: 1em; -} - -div.sphinxsidebar #searchbox form.search { - overflow: hidden; -} - -div.sphinxsidebar #searchbox input[type="text"] { - float: left; - width: 80%; - padding: 0.25em; - box-sizing: border-box; -} - -div.sphinxsidebar #searchbox input[type="submit"] { - float: left; - width: 20%; - border-left: none; - padding: 0.25em; - box-sizing: border-box; -} - - -img { - border: 0; - max-width: 100%; -} - -/* -- search page ----------------------------------------------------------- */ - -ul.search { - margin: 10px 0 0 20px; - padding: 0; -} - -ul.search li { - padding: 5px 0 5px 20px; - background-image: url(file.png); - background-repeat: no-repeat; - background-position: 0 7px; -} - -ul.search li a { - font-weight: bold; -} - -ul.search li div.context { - color: #888; - margin: 2px 0 0 30px; - text-align: left; -} - -ul.keywordmatches li.goodmatch a { - font-weight: bold; -} - -/* -- index page ------------------------------------------------------------ */ - -table.contentstable { - width: 90%; - margin-left: auto; - margin-right: auto; -} - -table.contentstable p.biglink { - line-height: 150%; -} - -a.biglink { - font-size: 1.3em; -} - -span.linkdescr { - font-style: italic; - padding-top: 5px; - font-size: 90%; -} - -/* -- general index --------------------------------------------------------- */ - -table.indextable { - width: 100%; -} - -table.indextable td { - text-align: left; - vertical-align: top; -} - -table.indextable ul { - margin-top: 0; - margin-bottom: 0; - list-style-type: none; -} - -table.indextable > tbody > tr > td > ul { - padding-left: 0em; -} - -table.indextable tr.pcap { - height: 10px; -} - -table.indextable tr.cap { - margin-top: 10px; - background-color: #f2f2f2; -} - -img.toggler { - margin-right: 3px; - margin-top: 3px; - cursor: pointer; -} - -div.modindex-jumpbox { - border-top: 1px solid #ddd; - border-bottom: 1px solid #ddd; - margin: 1em 0 1em 0; - padding: 0.4em; -} - -div.genindex-jumpbox { - border-top: 1px solid #ddd; - border-bottom: 1px solid #ddd; - margin: 1em 0 1em 0; - padding: 0.4em; -} - -/* -- domain module index --------------------------------------------------- */ - -table.modindextable td { - padding: 2px; - border-collapse: collapse; -} - -/* -- general body styles --------------------------------------------------- */ - -div.body { - min-width: 450px; - max-width: 800px; -} - -div.body p, div.body dd, div.body li, div.body blockquote { - -moz-hyphens: auto; - -ms-hyphens: auto; - -webkit-hyphens: auto; - hyphens: auto; -} - -a.headerlink { - visibility: hidden; -} - -a.brackets:before, -span.brackets > a:before{ - content: "["; -} - -a.brackets:after, -span.brackets > a:after { - content: "]"; -} - -h1:hover > a.headerlink, -h2:hover > a.headerlink, -h3:hover > a.headerlink, -h4:hover > a.headerlink, -h5:hover > a.headerlink, -h6:hover > a.headerlink, -dt:hover > a.headerlink, -caption:hover > a.headerlink, -p.caption:hover > a.headerlink, -div.code-block-caption:hover > a.headerlink { - visibility: visible; -} - -div.body p.caption { - text-align: inherit; -} - -div.body td { - text-align: left; -} - -.first { - margin-top: 0 !important; -} - -p.rubric { - margin-top: 30px; - font-weight: bold; -} - -img.align-left, .figure.align-left, object.align-left { - clear: left; - float: left; - margin-right: 1em; -} - -img.align-right, .figure.align-right, object.align-right { - clear: right; - float: right; - margin-left: 1em; -} - -img.align-center, .figure.align-center, object.align-center { - display: block; - margin-left: auto; - margin-right: auto; -} - -img.align-default, .figure.align-default { - display: block; - margin-left: auto; - margin-right: auto; -} - -.align-left { - text-align: left; -} - -.align-center { - text-align: center; -} - -.align-default { - text-align: center; -} - -.align-right { - text-align: right; -} - -/* -- sidebars -------------------------------------------------------------- */ - -div.sidebar { - margin: 0 0 0.5em 1em; - border: 1px solid #ddb; - padding: 7px; - background-color: #ffe; - width: 40%; - float: right; - clear: right; - overflow-x: auto; -} - -p.sidebar-title { - font-weight: bold; -} - -div.admonition, div.topic, blockquote { - clear: left; -} - -/* -- topics ---------------------------------------------------------------- */ - -div.topic { - border: 1px solid #ccc; - padding: 7px; - margin: 10px 0 10px 0; -} - -p.topic-title { - font-size: 1.1em; - font-weight: bold; - margin-top: 10px; -} - -/* -- admonitions ----------------------------------------------------------- */ - -div.admonition { - margin-top: 10px; - margin-bottom: 10px; - padding: 7px; -} - -div.admonition dt { - font-weight: bold; -} - -p.admonition-title { - margin: 0px 10px 5px 0px; - font-weight: bold; -} - -div.body p.centered { - text-align: center; - margin-top: 25px; -} - -/* -- content of sidebars/topics/admonitions -------------------------------- */ - -div.sidebar > :last-child, -div.topic > :last-child, -div.admonition > :last-child { - margin-bottom: 0; -} - -div.sidebar::after, -div.topic::after, -div.admonition::after, -blockquote::after { - display: block; - content: ''; - clear: both; -} - -/* -- tables ---------------------------------------------------------------- */ - -table.docutils { - margin-top: 10px; - margin-bottom: 10px; - border: 0; - border-collapse: collapse; -} - -table.align-center { - margin-left: auto; - margin-right: auto; -} - -table.align-default { - margin-left: auto; - margin-right: auto; -} - -table caption span.caption-number { - font-style: italic; -} - -table caption span.caption-text { -} - -table.docutils td, table.docutils th { - padding: 1px 8px 1px 5px; - border-top: 0; - border-left: 0; - border-right: 0; - border-bottom: 1px solid #aaa; -} - -table.footnote td, table.footnote th { - border: 0 !important; -} - -th { - text-align: left; - padding-right: 5px; -} - -table.citation { - border-left: solid 1px gray; - margin-left: 1px; -} - -table.citation td { - border-bottom: none; -} - -th > :first-child, -td > :first-child { - margin-top: 0px; -} - -th > :last-child, -td > :last-child { - margin-bottom: 0px; -} - -/* -- figures --------------------------------------------------------------- */ - -div.figure { - margin: 0.5em; - padding: 0.5em; -} - -div.figure p.caption { - padding: 0.3em; -} - -div.figure p.caption span.caption-number { - font-style: italic; -} - -div.figure p.caption span.caption-text { -} - -/* -- field list styles ----------------------------------------------------- */ - -table.field-list td, table.field-list th { - border: 0 !important; -} - -.field-list ul { - margin: 0; - padding-left: 1em; -} - -.field-list p { - margin: 0; -} - -.field-name { - -moz-hyphens: manual; - -ms-hyphens: manual; - -webkit-hyphens: manual; - hyphens: manual; -} - -/* -- hlist styles ---------------------------------------------------------- */ - -table.hlist { - margin: 1em 0; -} - -table.hlist td { - vertical-align: top; -} - - -/* -- other body styles ----------------------------------------------------- */ - -ol.arabic { - list-style: decimal; -} - -ol.loweralpha { - list-style: lower-alpha; -} - -ol.upperalpha { - list-style: upper-alpha; -} - -ol.lowerroman { - list-style: lower-roman; -} - -ol.upperroman { - list-style: upper-roman; -} - -:not(li) > ol > li:first-child > :first-child, -:not(li) > ul > li:first-child > :first-child { - margin-top: 0px; -} - -:not(li) > ol > li:last-child > :last-child, -:not(li) > ul > li:last-child > :last-child { - margin-bottom: 0px; -} - -ol.simple ol p, -ol.simple ul p, -ul.simple ol p, -ul.simple ul p { - margin-top: 0; -} - -ol.simple > li:not(:first-child) > p, -ul.simple > li:not(:first-child) > p { - margin-top: 0; -} - -ol.simple p, -ul.simple p { - margin-bottom: 0; -} - -dl.footnote > dt, -dl.citation > dt { - float: left; - margin-right: 0.5em; -} - -dl.footnote > dd, -dl.citation > dd { - margin-bottom: 0em; -} - -dl.footnote > dd:after, -dl.citation > dd:after { - content: ""; - clear: both; -} - -dl.field-list { - display: grid; - grid-template-columns: fit-content(30%) auto; -} - -dl.field-list > dt { - font-weight: bold; - word-break: break-word; - padding-left: 0.5em; - padding-right: 5px; -} - -dl.field-list > dt:after { - content: ":"; -} - -dl.field-list > dd { - padding-left: 0.5em; - margin-top: 0em; - margin-left: 0em; - margin-bottom: 0em; -} - -dl { - margin-bottom: 15px; -} - -dd > :first-child { - margin-top: 0px; -} - -dd ul, dd table { - margin-bottom: 10px; -} - -dd { - margin-top: 3px; - margin-bottom: 10px; - margin-left: 30px; -} - -dl > dd:last-child, -dl > dd:last-child > :last-child { - margin-bottom: 0; -} - -dt:target, span.highlighted { - background-color: #fbe54e; -} - -rect.highlighted { - fill: #fbe54e; -} - -dl.glossary dt { - font-weight: bold; - font-size: 1.1em; -} - -.optional { - font-size: 1.3em; -} - -.sig-paren { - font-size: larger; -} - -.versionmodified { - font-style: italic; -} - -.system-message { - background-color: #fda; - padding: 5px; - border: 3px solid red; -} - -.footnote:target { - background-color: #ffa; -} - -.line-block { - display: block; - margin-top: 1em; - margin-bottom: 1em; -} - -.line-block .line-block { - margin-top: 0; - margin-bottom: 0; - margin-left: 1.5em; -} - -.guilabel, .menuselection { - font-family: sans-serif; -} - -.accelerator { - text-decoration: underline; -} - -.classifier { - font-style: oblique; -} - -.classifier:before { - font-style: normal; - margin: 0.5em; - content: ":"; -} - -abbr, acronym { - border-bottom: dotted 1px; - cursor: help; -} - -/* -- code displays --------------------------------------------------------- */ - -pre { - overflow: auto; - overflow-y: hidden; /* fixes display issues on Chrome browsers */ -} - -pre, div[class*="highlight-"] { - clear: both; -} - -span.pre { - -moz-hyphens: none; - -ms-hyphens: none; - -webkit-hyphens: none; - hyphens: none; -} - -div[class*="highlight-"] { - margin: 1em 0; -} - -td.linenos pre { - border: 0; - background-color: transparent; - color: #aaa; -} - -table.highlighttable { - display: block; -} - -table.highlighttable tbody { - display: block; -} - -table.highlighttable tr { - display: flex; -} - -table.highlighttable td { - margin: 0; - padding: 0; -} - -table.highlighttable td.linenos { - padding-right: 0.5em; -} - -table.highlighttable td.code { - flex: 1; - overflow: hidden; -} - -.highlight .hll { - display: block; -} - -div.highlight pre, -table.highlighttable pre { - margin: 0; -} - -div.code-block-caption + div { - margin-top: 0; -} - -div.code-block-caption { - margin-top: 1em; - padding: 2px 5px; - font-size: small; -} - -div.code-block-caption code { - background-color: transparent; -} - -table.highlighttable td.linenos, -span.linenos, -div.doctest > div.highlight span.gp { /* gp: Generic.Prompt */ - user-select: none; -} - -div.code-block-caption span.caption-number { - padding: 0.1em 0.3em; - font-style: italic; -} - -div.code-block-caption span.caption-text { -} - -div.literal-block-wrapper { - margin: 1em 0; -} - -code.descname { - background-color: transparent; - font-weight: bold; - font-size: 1.2em; -} - -code.descclassname { - background-color: transparent; -} - -code.xref, a code { - background-color: transparent; - font-weight: bold; -} - -h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { - background-color: transparent; -} - -.viewcode-link { - float: right; -} - -.viewcode-back { - float: right; - font-family: sans-serif; -} - -div.viewcode-block:target { - margin: -1px -10px; - padding: 0 10px; -} - -/* -- math display ---------------------------------------------------------- */ - -img.math { - vertical-align: middle; -} - -div.body div.math p { - text-align: center; -} - -span.eqno { - float: right; -} - -span.eqno a.headerlink { - position: absolute; - z-index: 1; -} - -div.math:hover a.headerlink { - visibility: visible; -} - -/* -- printout stylesheet --------------------------------------------------- */ - -@media print { - div.document, - div.documentwrapper, - div.bodywrapper { - margin: 0 !important; - width: 100%; - } - - div.sphinxsidebar, - div.related, - div.footer, - #top-link { - display: none; - } -} \ No newline at end of file diff --git a/docs-v2/assets/classic.css b/docs-v2/assets/classic.css deleted file mode 100644 index dcae94623..000000000 --- a/docs-v2/assets/classic.css +++ /dev/null @@ -1,266 +0,0 @@ -/* - * classic.css_t - * ~~~~~~~~~~~~~ - * - * Sphinx stylesheet -- classic theme. - * - * :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * - */ - -@import url("basic.css"); - -/* -- page layout ----------------------------------------------------------- */ - -html { - /* CSS hack for macOS's scrollbar (see #1125) */ - background-color: #FFFFFF; -} - -body { - font-family: sans-serif; - font-size: 100%; - background-color: #11303d; - color: #000; - margin: 0; - padding: 0; -} - -div.document { - background-color: #1c4e63; -} - -div.documentwrapper { - float: left; - width: 100%; -} - -div.bodywrapper { - margin: 0 0 0 230px; -} - -div.body { - background-color: #ffffff; - color: #000000; - padding: 0 20px 30px 20px; -} - -div.footer { - color: #ffffff; - width: 100%; - padding: 9px 0 9px 0; - text-align: center; - font-size: 75%; -} - -div.footer a { - color: #ffffff; - text-decoration: underline; -} - -div.related { - background-color: #133f52; - line-height: 30px; - color: #ffffff; -} - -div.related a { - color: #ffffff; -} - -div.sphinxsidebar { -} - -div.sphinxsidebar h3 { - font-family: 'Trebuchet MS', sans-serif; - color: #ffffff; - font-size: 1.4em; - font-weight: normal; - margin: 0; - padding: 0; -} - -div.sphinxsidebar h3 a { - color: #ffffff; -} - -div.sphinxsidebar h4 { - font-family: 'Trebuchet MS', sans-serif; - color: #ffffff; - font-size: 1.3em; - font-weight: normal; - margin: 5px 0 0 0; - padding: 0; -} - -div.sphinxsidebar p { - color: #ffffff; -} - -div.sphinxsidebar p.topless { - margin: 5px 10px 10px 10px; -} - -div.sphinxsidebar ul { - margin: 10px; - padding: 0; - color: #ffffff; -} - -div.sphinxsidebar a { - color: #98dbcc; -} - -div.sphinxsidebar input { - border: 1px solid #98dbcc; - font-family: sans-serif; - font-size: 1em; -} - - - -/* -- hyperlink styles ------------------------------------------------------ */ - -a { - color: #355f7c; - text-decoration: none; -} - -a:visited { - color: #355f7c; - text-decoration: none; -} - -a:hover { - text-decoration: underline; -} - - - -/* -- body styles ----------------------------------------------------------- */ - -div.body h1, -div.body h2, -div.body h3, -div.body h4, -div.body h5, -div.body h6 { - font-family: 'Trebuchet MS', sans-serif; - background-color: #f2f2f2; - font-weight: normal; - color: #20435c; - border-bottom: 1px solid #ccc; - margin: 20px -20px 10px -20px; - padding: 3px 0 3px 10px; -} - -div.body h1 { margin-top: 0; font-size: 200%; } -div.body h2 { font-size: 160%; } -div.body h3 { font-size: 140%; } -div.body h4 { font-size: 120%; } -div.body h5 { font-size: 110%; } -div.body h6 { font-size: 100%; } - -a.headerlink { - color: #c60f0f; - font-size: 0.8em; - padding: 0 4px 0 4px; - text-decoration: none; -} - -a.headerlink:hover { - background-color: #c60f0f; - color: white; -} - -div.body p, div.body dd, div.body li, div.body blockquote { - text-align: justify; - line-height: 130%; -} - -div.admonition p.admonition-title + p { - display: inline; -} - -div.admonition p { - margin-bottom: 5px; -} - -div.admonition pre { - margin-bottom: 5px; -} - -div.admonition ul, div.admonition ol { - margin-bottom: 5px; -} - -div.note { - background-color: #eee; - border: 1px solid #ccc; -} - -div.seealso { - background-color: #ffc; - border: 1px solid #ff6; -} - -div.topic { - background-color: #eee; -} - -div.warning { - background-color: #ffe4e4; - border: 1px solid #f66; -} - -p.admonition-title { - display: inline; -} - -p.admonition-title:after { - content: ":"; -} - -pre { - padding: 5px; - background-color: unset; - color: unset; - line-height: 120%; - border: 1px solid #ac9; - border-left: none; - border-right: none; -} - -code { - background-color: #ecf0f3; - padding: 0 1px 0 1px; - font-size: 0.95em; -} - -th, dl.field-list > dt { - background-color: #ede; -} - -.warning code { - background: #efc2c2; -} - -.note code { - background: #d6d6d6; -} - -.viewcode-back { - font-family: sans-serif; -} - -div.viewcode-block:target { - background-color: #f4debf; - border-top: 1px solid #ac9; - border-bottom: 1px solid #ac9; -} - -div.code-block-caption { - color: #efefef; - background-color: #1c4e63; -} \ No newline at end of file diff --git a/docs-v2/assets/default.css b/docs-v2/assets/default.css deleted file mode 100644 index 80043f544..000000000 --- a/docs-v2/assets/default.css +++ /dev/null @@ -1,79 +0,0 @@ -a.headerlink { - display: none !important; -} - -h2 { - margin-top: -120px; - padding-top: 120px; -} - -.section-title { - font-size: 2rem; - line-height: 2.5rem; - letter-spacing: -1px; - font-weight: 700; - margin: 0 0 1rem; -} - -nav#api_nav .toctree-l1 { - margin-bottom: 1.5rem; -} - -nav#api_nav #api_sections ul { - list-style: none; - margin: 0; - padding: 0; -} - -nav#api_nav #api_sections ul li.toctree-l1>a { - color: #1264a3; - letter-spacing: 0; - font-size: .8rem; - font-weight: 800; - text-transform: uppercase; - border: none; - padding: 0; -} - -nav#api_nav #api_sections ul li.toctree-l2 { - margin: 0; - padding: 0; -} - -nav#api_nav #api_sections ul li.toctree-l2 a { - color: #1d1c1d; - text-transform: none; - font-weight: inherit; - padding: 0; - display: block; - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - font-size: 15px!important; - line-height:15px; - padding: 4px 8px; - border: 1px solid transparent; - border-radius: 4px; -} - -nav#api_nav #api_sections ul li.toctree-l2 a:hover { - cursor: pointer; - text-decoration: none; - background-color:#e8f5fa; - border-color:#dcf0fb; -} - -nav#api_nav #footer #footer_nav { - font-size: .9375rem; -} - -nav#api_nav #footer #footer_nav a { - border: none; - padding: 0; - color: #616061; -} - -nav#api_nav #footer #footer_nav a:hover { - text-decoration: none; - color: #1c1c1c; -} \ No newline at end of file diff --git a/docs-v2/assets/docs.css b/docs-v2/assets/docs.css deleted file mode 100644 index 7f360ac66..000000000 --- a/docs-v2/assets/docs.css +++ /dev/null @@ -1,34 +0,0 @@ -/* Updates body font */ -body { - font-family: Slack-Lato,appleLogo,sans-serif; -} - -/* Replaces old sidebar styled links */ -.sidebar_menu h5 { - font-size: 0.8rem; - font-weight: 800; - margin-bottom: 3px; -} - -/* Aligns footer navigation to the left of the sidebar */ -.footer_nav { - margin: 0 !important; -} - -/* Styles the signature all nice and pretty <3 */ -#footer_signature { - color:#e01e5a; - font-size:.9rem; - margin-top: 10px; -} - -/* Fixes link hover state */ -a:hover { - text-decoration: underline; -} - -/* Makes footer consistent */ -footer { - background-color: transparent; - border: 0; -} \ No newline at end of file diff --git a/docs-v2/assets/doctools.js b/docs-v2/assets/doctools.js deleted file mode 100644 index 144884ea6..000000000 --- a/docs-v2/assets/doctools.js +++ /dev/null @@ -1,316 +0,0 @@ -/* - * doctools.js - * ~~~~~~~~~~~ - * - * Sphinx JavaScript utilities for all documentation. - * - * :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * - */ - -/** - * select a different prefix for underscore - */ -$u = _.noConflict(); - -/** - * make the code below compatible with browsers without - * an installed firebug like debugger -if (!window.console || !console.firebug) { - var names = ["log", "debug", "info", "warn", "error", "assert", "dir", - "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", - "profile", "profileEnd"]; - window.console = {}; - for (var i = 0; i < names.length; ++i) - window.console[names[i]] = function() {}; -} - */ - -/** - * small helper function to urldecode strings - */ -jQuery.urldecode = function(x) { - return decodeURIComponent(x).replace(/\+/g, ' '); -}; - -/** - * small helper function to urlencode strings - */ -jQuery.urlencode = encodeURIComponent; - -/** - * This function returns the parsed url parameters of the - * current request. Multiple values per key are supported, - * it will always return arrays of strings for the value parts. - */ -jQuery.getQueryParameters = function(s) { - if (typeof s === 'undefined') - s = document.location.search; - var parts = s.substr(s.indexOf('?') + 1).split('&'); - var result = {}; - for (var i = 0; i < parts.length; i++) { - var tmp = parts[i].split('=', 2); - var key = jQuery.urldecode(tmp[0]); - var value = jQuery.urldecode(tmp[1]); - if (key in result) - result[key].push(value); - else - result[key] = [value]; - } - return result; -}; - -/** - * highlight a given string on a jquery object by wrapping it in - * span elements with the given class name. - */ -jQuery.fn.highlightText = function(text, className) { - function highlight(node, addItems) { - if (node.nodeType === 3) { - var val = node.nodeValue; - var pos = val.toLowerCase().indexOf(text); - if (pos >= 0 && - !jQuery(node.parentNode).hasClass(className) && - !jQuery(node.parentNode).hasClass("nohighlight")) { - var span; - var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); - if (isInSVG) { - span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); - } else { - span = document.createElement("span"); - span.className = className; - } - span.appendChild(document.createTextNode(val.substr(pos, text.length))); - node.parentNode.insertBefore(span, node.parentNode.insertBefore( - document.createTextNode(val.substr(pos + text.length)), - node.nextSibling)); - node.nodeValue = val.substr(0, pos); - if (isInSVG) { - var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); - var bbox = node.parentElement.getBBox(); - rect.x.baseVal.value = bbox.x; - rect.y.baseVal.value = bbox.y; - rect.width.baseVal.value = bbox.width; - rect.height.baseVal.value = bbox.height; - rect.setAttribute('class', className); - addItems.push({ - "parent": node.parentNode, - "target": rect}); - } - } - } - else if (!jQuery(node).is("button, select, textarea")) { - jQuery.each(node.childNodes, function() { - highlight(this, addItems); - }); - } - } - var addItems = []; - var result = this.each(function() { - highlight(this, addItems); - }); - for (var i = 0; i < addItems.length; ++i) { - jQuery(addItems[i].parent).before(addItems[i].target); - } - return result; -}; - -/* - * backward compatibility for jQuery.browser - * This will be supported until firefox bug is fixed. - */ -if (!jQuery.browser) { - jQuery.uaMatch = function(ua) { - ua = ua.toLowerCase(); - - var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || - /(webkit)[ \/]([\w.]+)/.exec(ua) || - /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || - /(msie) ([\w.]+)/.exec(ua) || - ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || - []; - - return { - browser: match[ 1 ] || "", - version: match[ 2 ] || "0" - }; - }; - jQuery.browser = {}; - jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; -} - -/** - * Small JavaScript module for the documentation. - */ -var Documentation = { - - init : function() { - this.fixFirefoxAnchorBug(); - this.highlightSearchWords(); - this.initIndexTable(); - if (DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) { - this.initOnKeyListeners(); - } - }, - - /** - * i18n support - */ - TRANSLATIONS : {}, - PLURAL_EXPR : function(n) { return n === 1 ? 0 : 1; }, - LOCALE : 'unknown', - - // gettext and ngettext don't access this so that the functions - // can safely bound to a different name (_ = Documentation.gettext) - gettext : function(string) { - var translated = Documentation.TRANSLATIONS[string]; - if (typeof translated === 'undefined') - return string; - return (typeof translated === 'string') ? translated : translated[0]; - }, - - ngettext : function(singular, plural, n) { - var translated = Documentation.TRANSLATIONS[singular]; - if (typeof translated === 'undefined') - return (n == 1) ? singular : plural; - return translated[Documentation.PLURALEXPR(n)]; - }, - - addTranslations : function(catalog) { - for (var key in catalog.messages) - this.TRANSLATIONS[key] = catalog.messages[key]; - this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); - this.LOCALE = catalog.locale; - }, - - /** - * add context elements like header anchor links - */ - addContextElements : function() { - $('div[id] > :header:first').each(function() { - $('\u00B6'). - attr('href', '#' + this.id). - attr('title', _('Permalink to this headline')). - appendTo(this); - }); - $('dt[id]').each(function() { - $('\u00B6'). - attr('href', '#' + this.id). - attr('title', _('Permalink to this definition')). - appendTo(this); - }); - }, - - /** - * workaround a firefox stupidity - * see: https://bugzilla.mozilla.org/show_bug.cgi?id=645075 - */ - fixFirefoxAnchorBug : function() { - if (document.location.hash && $.browser.mozilla) - window.setTimeout(function() { - document.location.href += ''; - }, 10); - }, - - /** - * highlight the search words provided in the url in the text - */ - highlightSearchWords : function() { - var params = $.getQueryParameters(); - var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; - if (terms.length) { - var body = $('div.body'); - if (!body.length) { - body = $('body'); - } - window.setTimeout(function() { - $.each(terms, function() { - body.highlightText(this.toLowerCase(), 'highlighted'); - }); - }, 10); - $('') - .appendTo($('#searchbox')); - } - }, - - /** - * init the domain index toggle buttons - */ - initIndexTable : function() { - var togglers = $('img.toggler').click(function() { - var src = $(this).attr('src'); - var idnum = $(this).attr('id').substr(7); - $('tr.cg-' + idnum).toggle(); - if (src.substr(-9) === 'minus.png') - $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); - else - $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); - }).css('display', ''); - if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { - togglers.click(); - } - }, - - /** - * helper function to hide the search marks again - */ - hideSearchWords : function() { - $('#searchbox .highlight-link').fadeOut(300); - $('span.highlighted').removeClass('highlighted'); - }, - - /** - * make the url absolute - */ - makeURL : function(relativeURL) { - return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; - }, - - /** - * get the current relative url - */ - getCurrentURL : function() { - var path = document.location.pathname; - var parts = path.split(/\//); - $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { - if (this === '..') - parts.pop(); - }); - var url = parts.join('/'); - return path.substring(url.lastIndexOf('/') + 1, path.length - 1); - }, - - initOnKeyListeners: function() { - $(document).keydown(function(event) { - var activeElementType = document.activeElement.tagName; - // don't navigate when in search box, textarea, dropdown or button - if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT' - && activeElementType !== 'BUTTON' && !event.altKey && !event.ctrlKey && !event.metaKey - && !event.shiftKey) { - switch (event.keyCode) { - case 37: // left - var prevHref = $('link[rel="prev"]').prop('href'); - if (prevHref) { - window.location.href = prevHref; - return false; - } - case 39: // right - var nextHref = $('link[rel="next"]').prop('href'); - if (nextHref) { - window.location.href = nextHref; - return false; - } - } - } - }); - } -}; - -// quick alias for translations -_ = Documentation.gettext; - -$(document).ready(function() { - Documentation.init(); -}); diff --git a/docs-v2/assets/documentation_options.js b/docs-v2/assets/documentation_options.js deleted file mode 100644 index 9fd31e439..000000000 --- a/docs-v2/assets/documentation_options.js +++ /dev/null @@ -1,12 +0,0 @@ -var DOCUMENTATION_OPTIONS = { - URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), - VERSION: '1.0.1', - LANGUAGE: 'None', - COLLAPSE_INDEX: false, - BUILDER: 'html', - FILE_SUFFIX: '.html', - LINK_SUFFIX: '.html', - HAS_SOURCE: true, - SOURCELINK_SUFFIX: '.txt', - NAVIGATION_WITH_KEYS: false -}; \ No newline at end of file diff --git a/docs-v2/assets/file.png b/docs-v2/assets/file.png deleted file mode 100644 index a858a410e..000000000 Binary files a/docs-v2/assets/file.png and /dev/null differ diff --git a/docs-v2/assets/jquery-3.2.1.js b/docs-v2/assets/jquery-3.2.1.js deleted file mode 100644 index d2d8ca479..000000000 --- a/docs-v2/assets/jquery-3.2.1.js +++ /dev/null @@ -1,10253 +0,0 @@ -/*! - * jQuery JavaScript Library v3.2.1 - * https://jquery.com/ - * - * Includes Sizzle.js - * https://sizzlejs.com/ - * - * Copyright JS Foundation and other contributors - * Released under the MIT license - * https://jquery.org/license - * - * Date: 2017-03-20T18:59Z - */ -( function( global, factory ) { - - "use strict"; - - if ( typeof module === "object" && typeof module.exports === "object" ) { - - // For CommonJS and CommonJS-like environments where a proper `window` - // is present, execute the factory and get jQuery. - // For environments that do not have a `window` with a `document` - // (such as Node.js), expose a factory as module.exports. - // This accentuates the need for the creation of a real `window`. - // e.g. var jQuery = require("jquery")(window); - // See ticket #14549 for more info. - module.exports = global.document ? - factory( global, true ) : - function( w ) { - if ( !w.document ) { - throw new Error( "jQuery requires a window with a document" ); - } - return factory( w ); - }; - } else { - factory( global ); - } - -// Pass this if window is not defined yet -} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { - -// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 -// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode -// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common -// enough that all such attempts are guarded in a try block. -"use strict"; - -var arr = []; - -var document = window.document; - -var getProto = Object.getPrototypeOf; - -var slice = arr.slice; - -var concat = arr.concat; - -var push = arr.push; - -var indexOf = arr.indexOf; - -var class2type = {}; - -var toString = class2type.toString; - -var hasOwn = class2type.hasOwnProperty; - -var fnToString = hasOwn.toString; - -var ObjectFunctionString = fnToString.call( Object ); - -var support = {}; - - - - function DOMEval( code, doc ) { - doc = doc || document; - - var script = doc.createElement( "script" ); - - script.text = code; - doc.head.appendChild( script ).parentNode.removeChild( script ); - } -/* global Symbol */ -// Defining this global in .eslintrc.json would create a danger of using the global -// unguarded in another place, it seems safer to define global only for this module - - - -var - version = "3.2.1", - - // Define a local copy of jQuery - jQuery = function( selector, context ) { - - // The jQuery object is actually just the init constructor 'enhanced' - // Need init if jQuery is called (just allow error to be thrown if not included) - return new jQuery.fn.init( selector, context ); - }, - - // Support: Android <=4.0 only - // Make sure we trim BOM and NBSP - rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, - - // Matches dashed string for camelizing - rmsPrefix = /^-ms-/, - rdashAlpha = /-([a-z])/g, - - // Used by jQuery.camelCase as callback to replace() - fcamelCase = function( all, letter ) { - return letter.toUpperCase(); - }; - -jQuery.fn = jQuery.prototype = { - - // The current version of jQuery being used - jquery: version, - - constructor: jQuery, - - // The default length of a jQuery object is 0 - length: 0, - - toArray: function() { - return slice.call( this ); - }, - - // Get the Nth element in the matched element set OR - // Get the whole matched element set as a clean array - get: function( num ) { - - // Return all the elements in a clean array - if ( num == null ) { - return slice.call( this ); - } - - // Return just the one element from the set - return num < 0 ? this[ num + this.length ] : this[ num ]; - }, - - // Take an array of elements and push it onto the stack - // (returning the new matched element set) - pushStack: function( elems ) { - - // Build a new jQuery matched element set - var ret = jQuery.merge( this.constructor(), elems ); - - // Add the old object onto the stack (as a reference) - ret.prevObject = this; - - // Return the newly-formed element set - return ret; - }, - - // Execute a callback for every element in the matched set. - each: function( callback ) { - return jQuery.each( this, callback ); - }, - - map: function( callback ) { - return this.pushStack( jQuery.map( this, function( elem, i ) { - return callback.call( elem, i, elem ); - } ) ); - }, - - slice: function() { - return this.pushStack( slice.apply( this, arguments ) ); - }, - - first: function() { - return this.eq( 0 ); - }, - - last: function() { - return this.eq( -1 ); - }, - - eq: function( i ) { - var len = this.length, - j = +i + ( i < 0 ? len : 0 ); - return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); - }, - - end: function() { - return this.prevObject || this.constructor(); - }, - - // For internal use only. - // Behaves like an Array's method, not like a jQuery method. - push: push, - sort: arr.sort, - splice: arr.splice -}; - -jQuery.extend = jQuery.fn.extend = function() { - var options, name, src, copy, copyIsArray, clone, - target = arguments[ 0 ] || {}, - i = 1, - length = arguments.length, - deep = false; - - // Handle a deep copy situation - if ( typeof target === "boolean" ) { - deep = target; - - // Skip the boolean and the target - target = arguments[ i ] || {}; - i++; - } - - // Handle case when target is a string or something (possible in deep copy) - if ( typeof target !== "object" && !jQuery.isFunction( target ) ) { - target = {}; - } - - // Extend jQuery itself if only one argument is passed - if ( i === length ) { - target = this; - i--; - } - - for ( ; i < length; i++ ) { - - // Only deal with non-null/undefined values - if ( ( options = arguments[ i ] ) != null ) { - - // Extend the base object - for ( name in options ) { - src = target[ name ]; - copy = options[ name ]; - - // Prevent never-ending loop - if ( target === copy ) { - continue; - } - - // Recurse if we're merging plain objects or arrays - if ( deep && copy && ( jQuery.isPlainObject( copy ) || - ( copyIsArray = Array.isArray( copy ) ) ) ) { - - if ( copyIsArray ) { - copyIsArray = false; - clone = src && Array.isArray( src ) ? src : []; - - } else { - clone = src && jQuery.isPlainObject( src ) ? src : {}; - } - - // Never move original objects, clone them - target[ name ] = jQuery.extend( deep, clone, copy ); - - // Don't bring in undefined values - } else if ( copy !== undefined ) { - target[ name ] = copy; - } - } - } - } - - // Return the modified object - return target; -}; - -jQuery.extend( { - - // Unique for each copy of jQuery on the page - expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), - - // Assume jQuery is ready without the ready module - isReady: true, - - error: function( msg ) { - throw new Error( msg ); - }, - - noop: function() {}, - - isFunction: function( obj ) { - return jQuery.type( obj ) === "function"; - }, - - isWindow: function( obj ) { - return obj != null && obj === obj.window; - }, - - isNumeric: function( obj ) { - - // As of jQuery 3.0, isNumeric is limited to - // strings and numbers (primitives or objects) - // that can be coerced to finite numbers (gh-2662) - var type = jQuery.type( obj ); - return ( type === "number" || type === "string" ) && - - // parseFloat NaNs numeric-cast false positives ("") - // ...but misinterprets leading-number strings, particularly hex literals ("0x...") - // subtraction forces infinities to NaN - !isNaN( obj - parseFloat( obj ) ); - }, - - isPlainObject: function( obj ) { - var proto, Ctor; - - // Detect obvious negatives - // Use toString instead of jQuery.type to catch host objects - if ( !obj || toString.call( obj ) !== "[object Object]" ) { - return false; - } - - proto = getProto( obj ); - - // Objects with no prototype (e.g., `Object.create( null )`) are plain - if ( !proto ) { - return true; - } - - // Objects with prototype are plain iff they were constructed by a global Object function - Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; - return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; - }, - - isEmptyObject: function( obj ) { - - /* eslint-disable no-unused-vars */ - // See https://github.com/eslint/eslint/issues/6125 - var name; - - for ( name in obj ) { - return false; - } - return true; - }, - - type: function( obj ) { - if ( obj == null ) { - return obj + ""; - } - - // Support: Android <=2.3 only (functionish RegExp) - return typeof obj === "object" || typeof obj === "function" ? - class2type[ toString.call( obj ) ] || "object" : - typeof obj; - }, - - // Evaluates a script in a global context - globalEval: function( code ) { - DOMEval( code ); - }, - - // Convert dashed to camelCase; used by the css and data modules - // Support: IE <=9 - 11, Edge 12 - 13 - // Microsoft forgot to hump their vendor prefix (#9572) - camelCase: function( string ) { - return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); - }, - - each: function( obj, callback ) { - var length, i = 0; - - if ( isArrayLike( obj ) ) { - length = obj.length; - for ( ; i < length; i++ ) { - if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { - break; - } - } - } else { - for ( i in obj ) { - if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { - break; - } - } - } - - return obj; - }, - - // Support: Android <=4.0 only - trim: function( text ) { - return text == null ? - "" : - ( text + "" ).replace( rtrim, "" ); - }, - - // results is for internal usage only - makeArray: function( arr, results ) { - var ret = results || []; - - if ( arr != null ) { - if ( isArrayLike( Object( arr ) ) ) { - jQuery.merge( ret, - typeof arr === "string" ? - [ arr ] : arr - ); - } else { - push.call( ret, arr ); - } - } - - return ret; - }, - - inArray: function( elem, arr, i ) { - return arr == null ? -1 : indexOf.call( arr, elem, i ); - }, - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - merge: function( first, second ) { - var len = +second.length, - j = 0, - i = first.length; - - for ( ; j < len; j++ ) { - first[ i++ ] = second[ j ]; - } - - first.length = i; - - return first; - }, - - grep: function( elems, callback, invert ) { - var callbackInverse, - matches = [], - i = 0, - length = elems.length, - callbackExpect = !invert; - - // Go through the array, only saving the items - // that pass the validator function - for ( ; i < length; i++ ) { - callbackInverse = !callback( elems[ i ], i ); - if ( callbackInverse !== callbackExpect ) { - matches.push( elems[ i ] ); - } - } - - return matches; - }, - - // arg is for internal usage only - map: function( elems, callback, arg ) { - var length, value, - i = 0, - ret = []; - - // Go through the array, translating each of the items to their new values - if ( isArrayLike( elems ) ) { - length = elems.length; - for ( ; i < length; i++ ) { - value = callback( elems[ i ], i, arg ); - - if ( value != null ) { - ret.push( value ); - } - } - - // Go through every key on the object, - } else { - for ( i in elems ) { - value = callback( elems[ i ], i, arg ); - - if ( value != null ) { - ret.push( value ); - } - } - } - - // Flatten any nested arrays - return concat.apply( [], ret ); - }, - - // A global GUID counter for objects - guid: 1, - - // Bind a function to a context, optionally partially applying any - // arguments. - proxy: function( fn, context ) { - var tmp, args, proxy; - - if ( typeof context === "string" ) { - tmp = fn[ context ]; - context = fn; - fn = tmp; - } - - // Quick check to determine if target is callable, in the spec - // this throws a TypeError, but we will just return undefined. - if ( !jQuery.isFunction( fn ) ) { - return undefined; - } - - // Simulated bind - args = slice.call( arguments, 2 ); - proxy = function() { - return fn.apply( context || this, args.concat( slice.call( arguments ) ) ); - }; - - // Set the guid of unique handler to the same of original handler, so it can be removed - proxy.guid = fn.guid = fn.guid || jQuery.guid++; - - return proxy; - }, - - now: Date.now, - - // jQuery.support is not used in Core but other projects attach their - // properties to it so it needs to exist. - support: support -} ); - -if ( typeof Symbol === "function" ) { - jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; -} - -// Populate the class2type map -jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), -function( i, name ) { - class2type[ "[object " + name + "]" ] = name.toLowerCase(); -} ); - -function isArrayLike( obj ) { - - // Support: real iOS 8.2 only (not reproducible in simulator) - // `in` check used to prevent JIT error (gh-2145) - // hasOwn isn't used here due to false negatives - // regarding Nodelist length in IE - var length = !!obj && "length" in obj && obj.length, - type = jQuery.type( obj ); - - if ( type === "function" || jQuery.isWindow( obj ) ) { - return false; - } - - return type === "array" || length === 0 || - typeof length === "number" && length > 0 && ( length - 1 ) in obj; -} -var Sizzle = -/*! - * Sizzle CSS Selector Engine v2.3.3 - * https://sizzlejs.com/ - * - * Copyright jQuery Foundation and other contributors - * Released under the MIT license - * http://jquery.org/license - * - * Date: 2016-08-08 - */ -(function( window ) { - -var i, - support, - Expr, - getText, - isXML, - tokenize, - compile, - select, - outermostContext, - sortInput, - hasDuplicate, - - // Local document vars - setDocument, - document, - docElem, - documentIsHTML, - rbuggyQSA, - rbuggyMatches, - matches, - contains, - - // Instance-specific data - expando = "sizzle" + 1 * new Date(), - preferredDoc = window.document, - dirruns = 0, - done = 0, - classCache = createCache(), - tokenCache = createCache(), - compilerCache = createCache(), - sortOrder = function( a, b ) { - if ( a === b ) { - hasDuplicate = true; - } - return 0; - }, - - // Instance methods - hasOwn = ({}).hasOwnProperty, - arr = [], - pop = arr.pop, - push_native = arr.push, - push = arr.push, - slice = arr.slice, - // Use a stripped-down indexOf as it's faster than native - // https://jsperf.com/thor-indexof-vs-for/5 - indexOf = function( list, elem ) { - var i = 0, - len = list.length; - for ( ; i < len; i++ ) { - if ( list[i] === elem ) { - return i; - } - } - return -1; - }, - - booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", - - // Regular expressions - - // http://www.w3.org/TR/css3-selectors/#whitespace - whitespace = "[\\x20\\t\\r\\n\\f]", - - // http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier - identifier = "(?:\\\\.|[\\w-]|[^\0-\\xa0])+", - - // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors - attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + - // Operator (capture 2) - "*([*^$|!~]?=)" + whitespace + - // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" - "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + - "*\\]", - - pseudos = ":(" + identifier + ")(?:\\((" + - // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: - // 1. quoted (capture 3; capture 4 or capture 5) - "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + - // 2. simple (capture 6) - "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + - // 3. anything else (capture 2) - ".*" + - ")\\)|)", - - // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter - rwhitespace = new RegExp( whitespace + "+", "g" ), - rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), - - rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), - rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), - - rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ), - - rpseudo = new RegExp( pseudos ), - ridentifier = new RegExp( "^" + identifier + "$" ), - - matchExpr = { - "ID": new RegExp( "^#(" + identifier + ")" ), - "CLASS": new RegExp( "^\\.(" + identifier + ")" ), - "TAG": new RegExp( "^(" + identifier + "|[*])" ), - "ATTR": new RegExp( "^" + attributes ), - "PSEUDO": new RegExp( "^" + pseudos ), - "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + - "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + - "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), - "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), - // For use in libraries implementing .is() - // We use this for POS matching in `select` - "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + - whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) - }, - - rinputs = /^(?:input|select|textarea|button)$/i, - rheader = /^h\d$/i, - - rnative = /^[^{]+\{\s*\[native \w/, - - // Easily-parseable/retrievable ID or TAG or CLASS selectors - rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, - - rsibling = /[+~]/, - - // CSS escapes - // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters - runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ), - funescape = function( _, escaped, escapedWhitespace ) { - var high = "0x" + escaped - 0x10000; - // NaN means non-codepoint - // Support: Firefox<24 - // Workaround erroneous numeric interpretation of +"0x" - return high !== high || escapedWhitespace ? - escaped : - high < 0 ? - // BMP codepoint - String.fromCharCode( high + 0x10000 ) : - // Supplemental Plane codepoint (surrogate pair) - String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); - }, - - // CSS string/identifier serialization - // https://drafts.csswg.org/cssom/#common-serializing-idioms - rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, - fcssescape = function( ch, asCodePoint ) { - if ( asCodePoint ) { - - // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER - if ( ch === "\0" ) { - return "\uFFFD"; - } - - // Control characters and (dependent upon position) numbers get escaped as code points - return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; - } - - // Other potentially-special ASCII characters get backslash-escaped - return "\\" + ch; - }, - - // Used for iframes - // See setDocument() - // Removing the function wrapper causes a "Permission Denied" - // error in IE - unloadHandler = function() { - setDocument(); - }, - - disabledAncestor = addCombinator( - function( elem ) { - return elem.disabled === true && ("form" in elem || "label" in elem); - }, - { dir: "parentNode", next: "legend" } - ); - -// Optimize for push.apply( _, NodeList ) -try { - push.apply( - (arr = slice.call( preferredDoc.childNodes )), - preferredDoc.childNodes - ); - // Support: Android<4.0 - // Detect silently failing push.apply - arr[ preferredDoc.childNodes.length ].nodeType; -} catch ( e ) { - push = { apply: arr.length ? - - // Leverage slice if possible - function( target, els ) { - push_native.apply( target, slice.call(els) ); - } : - - // Support: IE<9 - // Otherwise append directly - function( target, els ) { - var j = target.length, - i = 0; - // Can't trust NodeList.length - while ( (target[j++] = els[i++]) ) {} - target.length = j - 1; - } - }; -} - -function Sizzle( selector, context, results, seed ) { - var m, i, elem, nid, match, groups, newSelector, - newContext = context && context.ownerDocument, - - // nodeType defaults to 9, since context defaults to document - nodeType = context ? context.nodeType : 9; - - results = results || []; - - // Return early from calls with invalid selector or context - if ( typeof selector !== "string" || !selector || - nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { - - return results; - } - - // Try to shortcut find operations (as opposed to filters) in HTML documents - if ( !seed ) { - - if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { - setDocument( context ); - } - context = context || document; - - if ( documentIsHTML ) { - - // If the selector is sufficiently simple, try using a "get*By*" DOM method - // (excepting DocumentFragment context, where the methods don't exist) - if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) { - - // ID selector - if ( (m = match[1]) ) { - - // Document context - if ( nodeType === 9 ) { - if ( (elem = context.getElementById( m )) ) { - - // Support: IE, Opera, Webkit - // TODO: identify versions - // getElementById can match elements by name instead of ID - if ( elem.id === m ) { - results.push( elem ); - return results; - } - } else { - return results; - } - - // Element context - } else { - - // Support: IE, Opera, Webkit - // TODO: identify versions - // getElementById can match elements by name instead of ID - if ( newContext && (elem = newContext.getElementById( m )) && - contains( context, elem ) && - elem.id === m ) { - - results.push( elem ); - return results; - } - } - - // Type selector - } else if ( match[2] ) { - push.apply( results, context.getElementsByTagName( selector ) ); - return results; - - // Class selector - } else if ( (m = match[3]) && support.getElementsByClassName && - context.getElementsByClassName ) { - - push.apply( results, context.getElementsByClassName( m ) ); - return results; - } - } - - // Take advantage of querySelectorAll - if ( support.qsa && - !compilerCache[ selector + " " ] && - (!rbuggyQSA || !rbuggyQSA.test( selector )) ) { - - if ( nodeType !== 1 ) { - newContext = context; - newSelector = selector; - - // qSA looks outside Element context, which is not what we want - // Thanks to Andrew Dupont for this workaround technique - // Support: IE <=8 - // Exclude object elements - } else if ( context.nodeName.toLowerCase() !== "object" ) { - - // Capture the context ID, setting it first if necessary - if ( (nid = context.getAttribute( "id" )) ) { - nid = nid.replace( rcssescape, fcssescape ); - } else { - context.setAttribute( "id", (nid = expando) ); - } - - // Prefix every selector in the list - groups = tokenize( selector ); - i = groups.length; - while ( i-- ) { - groups[i] = "#" + nid + " " + toSelector( groups[i] ); - } - newSelector = groups.join( "," ); - - // Expand context for sibling selectors - newContext = rsibling.test( selector ) && testContext( context.parentNode ) || - context; - } - - if ( newSelector ) { - try { - push.apply( results, - newContext.querySelectorAll( newSelector ) - ); - return results; - } catch ( qsaError ) { - } finally { - if ( nid === expando ) { - context.removeAttribute( "id" ); - } - } - } - } - } - } - - // All others - return select( selector.replace( rtrim, "$1" ), context, results, seed ); -} - -/** - * Create key-value caches of limited size - * @returns {function(string, object)} Returns the Object data after storing it on itself with - * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) - * deleting the oldest entry - */ -function createCache() { - var keys = []; - - function cache( key, value ) { - // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) - if ( keys.push( key + " " ) > Expr.cacheLength ) { - // Only keep the most recent entries - delete cache[ keys.shift() ]; - } - return (cache[ key + " " ] = value); - } - return cache; -} - -/** - * Mark a function for special use by Sizzle - * @param {Function} fn The function to mark - */ -function markFunction( fn ) { - fn[ expando ] = true; - return fn; -} - -/** - * Support testing using an element - * @param {Function} fn Passed the created element and returns a boolean result - */ -function assert( fn ) { - var el = document.createElement("fieldset"); - - try { - return !!fn( el ); - } catch (e) { - return false; - } finally { - // Remove from its parent by default - if ( el.parentNode ) { - el.parentNode.removeChild( el ); - } - // release memory in IE - el = null; - } -} - -/** - * Adds the same handler for all of the specified attrs - * @param {String} attrs Pipe-separated list of attributes - * @param {Function} handler The method that will be applied - */ -function addHandle( attrs, handler ) { - var arr = attrs.split("|"), - i = arr.length; - - while ( i-- ) { - Expr.attrHandle[ arr[i] ] = handler; - } -} - -/** - * Checks document order of two siblings - * @param {Element} a - * @param {Element} b - * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b - */ -function siblingCheck( a, b ) { - var cur = b && a, - diff = cur && a.nodeType === 1 && b.nodeType === 1 && - a.sourceIndex - b.sourceIndex; - - // Use IE sourceIndex if available on both nodes - if ( diff ) { - return diff; - } - - // Check if b follows a - if ( cur ) { - while ( (cur = cur.nextSibling) ) { - if ( cur === b ) { - return -1; - } - } - } - - return a ? 1 : -1; -} - -/** - * Returns a function to use in pseudos for input types - * @param {String} type - */ -function createInputPseudo( type ) { - return function( elem ) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && elem.type === type; - }; -} - -/** - * Returns a function to use in pseudos for buttons - * @param {String} type - */ -function createButtonPseudo( type ) { - return function( elem ) { - var name = elem.nodeName.toLowerCase(); - return (name === "input" || name === "button") && elem.type === type; - }; -} - -/** - * Returns a function to use in pseudos for :enabled/:disabled - * @param {Boolean} disabled true for :disabled; false for :enabled - */ -function createDisabledPseudo( disabled ) { - - // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable - return function( elem ) { - - // Only certain elements can match :enabled or :disabled - // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled - // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled - if ( "form" in elem ) { - - // Check for inherited disabledness on relevant non-disabled elements: - // * listed form-associated elements in a disabled fieldset - // https://html.spec.whatwg.org/multipage/forms.html#category-listed - // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled - // * option elements in a disabled optgroup - // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled - // All such elements have a "form" property. - if ( elem.parentNode && elem.disabled === false ) { - - // Option elements defer to a parent optgroup if present - if ( "label" in elem ) { - if ( "label" in elem.parentNode ) { - return elem.parentNode.disabled === disabled; - } else { - return elem.disabled === disabled; - } - } - - // Support: IE 6 - 11 - // Use the isDisabled shortcut property to check for disabled fieldset ancestors - return elem.isDisabled === disabled || - - // Where there is no isDisabled, check manually - /* jshint -W018 */ - elem.isDisabled !== !disabled && - disabledAncestor( elem ) === disabled; - } - - return elem.disabled === disabled; - - // Try to winnow out elements that can't be disabled before trusting the disabled property. - // Some victims get caught in our net (label, legend, menu, track), but it shouldn't - // even exist on them, let alone have a boolean value. - } else if ( "label" in elem ) { - return elem.disabled === disabled; - } - - // Remaining elements are neither :enabled nor :disabled - return false; - }; -} - -/** - * Returns a function to use in pseudos for positionals - * @param {Function} fn - */ -function createPositionalPseudo( fn ) { - return markFunction(function( argument ) { - argument = +argument; - return markFunction(function( seed, matches ) { - var j, - matchIndexes = fn( [], seed.length, argument ), - i = matchIndexes.length; - - // Match elements found at the specified indexes - while ( i-- ) { - if ( seed[ (j = matchIndexes[i]) ] ) { - seed[j] = !(matches[j] = seed[j]); - } - } - }); - }); -} - -/** - * Checks a node for validity as a Sizzle context - * @param {Element|Object=} context - * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value - */ -function testContext( context ) { - return context && typeof context.getElementsByTagName !== "undefined" && context; -} - -// Expose support vars for convenience -support = Sizzle.support = {}; - -/** - * Detects XML nodes - * @param {Element|Object} elem An element or a document - * @returns {Boolean} True iff elem is a non-HTML XML node - */ -isXML = Sizzle.isXML = function( elem ) { - // documentElement is verified for cases where it doesn't yet exist - // (such as loading iframes in IE - #4833) - var documentElement = elem && (elem.ownerDocument || elem).documentElement; - return documentElement ? documentElement.nodeName !== "HTML" : false; -}; - -/** - * Sets document-related variables once based on the current document - * @param {Element|Object} [doc] An element or document object to use to set the document - * @returns {Object} Returns the current document - */ -setDocument = Sizzle.setDocument = function( node ) { - var hasCompare, subWindow, - doc = node ? node.ownerDocument || node : preferredDoc; - - // Return early if doc is invalid or already selected - if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { - return document; - } - - // Update global variables - document = doc; - docElem = document.documentElement; - documentIsHTML = !isXML( document ); - - // Support: IE 9-11, Edge - // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) - if ( preferredDoc !== document && - (subWindow = document.defaultView) && subWindow.top !== subWindow ) { - - // Support: IE 11, Edge - if ( subWindow.addEventListener ) { - subWindow.addEventListener( "unload", unloadHandler, false ); - - // Support: IE 9 - 10 only - } else if ( subWindow.attachEvent ) { - subWindow.attachEvent( "onunload", unloadHandler ); - } - } - - /* Attributes - ---------------------------------------------------------------------- */ - - // Support: IE<8 - // Verify that getAttribute really returns attributes and not properties - // (excepting IE8 booleans) - support.attributes = assert(function( el ) { - el.className = "i"; - return !el.getAttribute("className"); - }); - - /* getElement(s)By* - ---------------------------------------------------------------------- */ - - // Check if getElementsByTagName("*") returns only elements - support.getElementsByTagName = assert(function( el ) { - el.appendChild( document.createComment("") ); - return !el.getElementsByTagName("*").length; - }); - - // Support: IE<9 - support.getElementsByClassName = rnative.test( document.getElementsByClassName ); - - // Support: IE<10 - // Check if getElementById returns elements by name - // The broken getElementById methods don't pick up programmatically-set names, - // so use a roundabout getElementsByName test - support.getById = assert(function( el ) { - docElem.appendChild( el ).id = expando; - return !document.getElementsByName || !document.getElementsByName( expando ).length; - }); - - // ID filter and find - if ( support.getById ) { - Expr.filter["ID"] = function( id ) { - var attrId = id.replace( runescape, funescape ); - return function( elem ) { - return elem.getAttribute("id") === attrId; - }; - }; - Expr.find["ID"] = function( id, context ) { - if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { - var elem = context.getElementById( id ); - return elem ? [ elem ] : []; - } - }; - } else { - Expr.filter["ID"] = function( id ) { - var attrId = id.replace( runescape, funescape ); - return function( elem ) { - var node = typeof elem.getAttributeNode !== "undefined" && - elem.getAttributeNode("id"); - return node && node.value === attrId; - }; - }; - - // Support: IE 6 - 7 only - // getElementById is not reliable as a find shortcut - Expr.find["ID"] = function( id, context ) { - if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { - var node, i, elems, - elem = context.getElementById( id ); - - if ( elem ) { - - // Verify the id attribute - node = elem.getAttributeNode("id"); - if ( node && node.value === id ) { - return [ elem ]; - } - - // Fall back on getElementsByName - elems = context.getElementsByName( id ); - i = 0; - while ( (elem = elems[i++]) ) { - node = elem.getAttributeNode("id"); - if ( node && node.value === id ) { - return [ elem ]; - } - } - } - - return []; - } - }; - } - - // Tag - Expr.find["TAG"] = support.getElementsByTagName ? - function( tag, context ) { - if ( typeof context.getElementsByTagName !== "undefined" ) { - return context.getElementsByTagName( tag ); - - // DocumentFragment nodes don't have gEBTN - } else if ( support.qsa ) { - return context.querySelectorAll( tag ); - } - } : - - function( tag, context ) { - var elem, - tmp = [], - i = 0, - // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too - results = context.getElementsByTagName( tag ); - - // Filter out possible comments - if ( tag === "*" ) { - while ( (elem = results[i++]) ) { - if ( elem.nodeType === 1 ) { - tmp.push( elem ); - } - } - - return tmp; - } - return results; - }; - - // Class - Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) { - if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { - return context.getElementsByClassName( className ); - } - }; - - /* QSA/matchesSelector - ---------------------------------------------------------------------- */ - - // QSA and matchesSelector support - - // matchesSelector(:active) reports false when true (IE9/Opera 11.5) - rbuggyMatches = []; - - // qSa(:focus) reports false when true (Chrome 21) - // We allow this because of a bug in IE8/9 that throws an error - // whenever `document.activeElement` is accessed on an iframe - // So, we allow :focus to pass through QSA all the time to avoid the IE error - // See https://bugs.jquery.com/ticket/13378 - rbuggyQSA = []; - - if ( (support.qsa = rnative.test( document.querySelectorAll )) ) { - // Build QSA regex - // Regex strategy adopted from Diego Perini - assert(function( el ) { - // Select is set to empty string on purpose - // This is to test IE's treatment of not explicitly - // setting a boolean content attribute, - // since its presence should be enough - // https://bugs.jquery.com/ticket/12359 - docElem.appendChild( el ).innerHTML = "" + - ""; - - // Support: IE8, Opera 11-12.16 - // Nothing should be selected when empty strings follow ^= or $= or *= - // The test attribute must be unknown in Opera but "safe" for WinRT - // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section - if ( el.querySelectorAll("[msallowcapture^='']").length ) { - rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); - } - - // Support: IE8 - // Boolean attributes and "value" are not treated correctly - if ( !el.querySelectorAll("[selected]").length ) { - rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); - } - - // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ - if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { - rbuggyQSA.push("~="); - } - - // Webkit/Opera - :checked should return selected option elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - // IE8 throws error here and will not see later tests - if ( !el.querySelectorAll(":checked").length ) { - rbuggyQSA.push(":checked"); - } - - // Support: Safari 8+, iOS 8+ - // https://bugs.webkit.org/show_bug.cgi?id=136851 - // In-page `selector#id sibling-combinator selector` fails - if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { - rbuggyQSA.push(".#.+[+~]"); - } - }); - - assert(function( el ) { - el.innerHTML = "" + - ""; - - // Support: Windows 8 Native Apps - // The type and name attributes are restricted during .innerHTML assignment - var input = document.createElement("input"); - input.setAttribute( "type", "hidden" ); - el.appendChild( input ).setAttribute( "name", "D" ); - - // Support: IE8 - // Enforce case-sensitivity of name attribute - if ( el.querySelectorAll("[name=d]").length ) { - rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); - } - - // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) - // IE8 throws error here and will not see later tests - if ( el.querySelectorAll(":enabled").length !== 2 ) { - rbuggyQSA.push( ":enabled", ":disabled" ); - } - - // Support: IE9-11+ - // IE's :disabled selector does not pick up the children of disabled fieldsets - docElem.appendChild( el ).disabled = true; - if ( el.querySelectorAll(":disabled").length !== 2 ) { - rbuggyQSA.push( ":enabled", ":disabled" ); - } - - // Opera 10-11 does not throw on post-comma invalid pseudos - el.querySelectorAll("*,:x"); - rbuggyQSA.push(",.*:"); - }); - } - - if ( (support.matchesSelector = rnative.test( (matches = docElem.matches || - docElem.webkitMatchesSelector || - docElem.mozMatchesSelector || - docElem.oMatchesSelector || - docElem.msMatchesSelector) )) ) { - - assert(function( el ) { - // Check to see if it's possible to do matchesSelector - // on a disconnected node (IE 9) - support.disconnectedMatch = matches.call( el, "*" ); - - // This should fail with an exception - // Gecko does not error, returns false instead - matches.call( el, "[s!='']:x" ); - rbuggyMatches.push( "!=", pseudos ); - }); - } - - rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") ); - rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") ); - - /* Contains - ---------------------------------------------------------------------- */ - hasCompare = rnative.test( docElem.compareDocumentPosition ); - - // Element contains another - // Purposefully self-exclusive - // As in, an element does not contain itself - contains = hasCompare || rnative.test( docElem.contains ) ? - function( a, b ) { - var adown = a.nodeType === 9 ? a.documentElement : a, - bup = b && b.parentNode; - return a === bup || !!( bup && bup.nodeType === 1 && ( - adown.contains ? - adown.contains( bup ) : - a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 - )); - } : - function( a, b ) { - if ( b ) { - while ( (b = b.parentNode) ) { - if ( b === a ) { - return true; - } - } - } - return false; - }; - - /* Sorting - ---------------------------------------------------------------------- */ - - // Document order sorting - sortOrder = hasCompare ? - function( a, b ) { - - // Flag for duplicate removal - if ( a === b ) { - hasDuplicate = true; - return 0; - } - - // Sort on method existence if only one input has compareDocumentPosition - var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; - if ( compare ) { - return compare; - } - - // Calculate position if both inputs belong to the same document - compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ? - a.compareDocumentPosition( b ) : - - // Otherwise we know they are disconnected - 1; - - // Disconnected nodes - if ( compare & 1 || - (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) { - - // Choose the first element that is related to our preferred document - if ( a === document || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) { - return -1; - } - if ( b === document || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) { - return 1; - } - - // Maintain original order - return sortInput ? - ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : - 0; - } - - return compare & 4 ? -1 : 1; - } : - function( a, b ) { - // Exit early if the nodes are identical - if ( a === b ) { - hasDuplicate = true; - return 0; - } - - var cur, - i = 0, - aup = a.parentNode, - bup = b.parentNode, - ap = [ a ], - bp = [ b ]; - - // Parentless nodes are either documents or disconnected - if ( !aup || !bup ) { - return a === document ? -1 : - b === document ? 1 : - aup ? -1 : - bup ? 1 : - sortInput ? - ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : - 0; - - // If the nodes are siblings, we can do a quick check - } else if ( aup === bup ) { - return siblingCheck( a, b ); - } - - // Otherwise we need full lists of their ancestors for comparison - cur = a; - while ( (cur = cur.parentNode) ) { - ap.unshift( cur ); - } - cur = b; - while ( (cur = cur.parentNode) ) { - bp.unshift( cur ); - } - - // Walk down the tree looking for a discrepancy - while ( ap[i] === bp[i] ) { - i++; - } - - return i ? - // Do a sibling check if the nodes have a common ancestor - siblingCheck( ap[i], bp[i] ) : - - // Otherwise nodes in our document sort first - ap[i] === preferredDoc ? -1 : - bp[i] === preferredDoc ? 1 : - 0; - }; - - return document; -}; - -Sizzle.matches = function( expr, elements ) { - return Sizzle( expr, null, null, elements ); -}; - -Sizzle.matchesSelector = function( elem, expr ) { - // Set document vars if needed - if ( ( elem.ownerDocument || elem ) !== document ) { - setDocument( elem ); - } - - // Make sure that attribute selectors are quoted - expr = expr.replace( rattributeQuotes, "='$1']" ); - - if ( support.matchesSelector && documentIsHTML && - !compilerCache[ expr + " " ] && - ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && - ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { - - try { - var ret = matches.call( elem, expr ); - - // IE 9's matchesSelector returns false on disconnected nodes - if ( ret || support.disconnectedMatch || - // As well, disconnected nodes are said to be in a document - // fragment in IE 9 - elem.document && elem.document.nodeType !== 11 ) { - return ret; - } - } catch (e) {} - } - - return Sizzle( expr, document, null, [ elem ] ).length > 0; -}; - -Sizzle.contains = function( context, elem ) { - // Set document vars if needed - if ( ( context.ownerDocument || context ) !== document ) { - setDocument( context ); - } - return contains( context, elem ); -}; - -Sizzle.attr = function( elem, name ) { - // Set document vars if needed - if ( ( elem.ownerDocument || elem ) !== document ) { - setDocument( elem ); - } - - var fn = Expr.attrHandle[ name.toLowerCase() ], - // Don't get fooled by Object.prototype properties (jQuery #13807) - val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? - fn( elem, name, !documentIsHTML ) : - undefined; - - return val !== undefined ? - val : - support.attributes || !documentIsHTML ? - elem.getAttribute( name ) : - (val = elem.getAttributeNode(name)) && val.specified ? - val.value : - null; -}; - -Sizzle.escape = function( sel ) { - return (sel + "").replace( rcssescape, fcssescape ); -}; - -Sizzle.error = function( msg ) { - throw new Error( "Syntax error, unrecognized expression: " + msg ); -}; - -/** - * Document sorting and removing duplicates - * @param {ArrayLike} results - */ -Sizzle.uniqueSort = function( results ) { - var elem, - duplicates = [], - j = 0, - i = 0; - - // Unless we *know* we can detect duplicates, assume their presence - hasDuplicate = !support.detectDuplicates; - sortInput = !support.sortStable && results.slice( 0 ); - results.sort( sortOrder ); - - if ( hasDuplicate ) { - while ( (elem = results[i++]) ) { - if ( elem === results[ i ] ) { - j = duplicates.push( i ); - } - } - while ( j-- ) { - results.splice( duplicates[ j ], 1 ); - } - } - - // Clear input after sorting to release objects - // See https://github.com/jquery/sizzle/pull/225 - sortInput = null; - - return results; -}; - -/** - * Utility function for retrieving the text value of an array of DOM nodes - * @param {Array|Element} elem - */ -getText = Sizzle.getText = function( elem ) { - var node, - ret = "", - i = 0, - nodeType = elem.nodeType; - - if ( !nodeType ) { - // If no nodeType, this is expected to be an array - while ( (node = elem[i++]) ) { - // Do not traverse comment nodes - ret += getText( node ); - } - } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { - // Use textContent for elements - // innerText usage removed for consistency of new lines (jQuery #11153) - if ( typeof elem.textContent === "string" ) { - return elem.textContent; - } else { - // Traverse its children - for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { - ret += getText( elem ); - } - } - } else if ( nodeType === 3 || nodeType === 4 ) { - return elem.nodeValue; - } - // Do not include comment or processing instruction nodes - - return ret; -}; - -Expr = Sizzle.selectors = { - - // Can be adjusted by the user - cacheLength: 50, - - createPseudo: markFunction, - - match: matchExpr, - - attrHandle: {}, - - find: {}, - - relative: { - ">": { dir: "parentNode", first: true }, - " ": { dir: "parentNode" }, - "+": { dir: "previousSibling", first: true }, - "~": { dir: "previousSibling" } - }, - - preFilter: { - "ATTR": function( match ) { - match[1] = match[1].replace( runescape, funescape ); - - // Move the given value to match[3] whether quoted or unquoted - match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape ); - - if ( match[2] === "~=" ) { - match[3] = " " + match[3] + " "; - } - - return match.slice( 0, 4 ); - }, - - "CHILD": function( match ) { - /* matches from matchExpr["CHILD"] - 1 type (only|nth|...) - 2 what (child|of-type) - 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) - 4 xn-component of xn+y argument ([+-]?\d*n|) - 5 sign of xn-component - 6 x of xn-component - 7 sign of y-component - 8 y of y-component - */ - match[1] = match[1].toLowerCase(); - - if ( match[1].slice( 0, 3 ) === "nth" ) { - // nth-* requires argument - if ( !match[3] ) { - Sizzle.error( match[0] ); - } - - // numeric x and y parameters for Expr.filter.CHILD - // remember that false/true cast respectively to 0/1 - match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); - match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); - - // other types prohibit arguments - } else if ( match[3] ) { - Sizzle.error( match[0] ); - } - - return match; - }, - - "PSEUDO": function( match ) { - var excess, - unquoted = !match[6] && match[2]; - - if ( matchExpr["CHILD"].test( match[0] ) ) { - return null; - } - - // Accept quoted arguments as-is - if ( match[3] ) { - match[2] = match[4] || match[5] || ""; - - // Strip excess characters from unquoted arguments - } else if ( unquoted && rpseudo.test( unquoted ) && - // Get excess from tokenize (recursively) - (excess = tokenize( unquoted, true )) && - // advance to the next closing parenthesis - (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { - - // excess is a negative index - match[0] = match[0].slice( 0, excess ); - match[2] = unquoted.slice( 0, excess ); - } - - // Return only captures needed by the pseudo filter method (type and argument) - return match.slice( 0, 3 ); - } - }, - - filter: { - - "TAG": function( nodeNameSelector ) { - var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); - return nodeNameSelector === "*" ? - function() { return true; } : - function( elem ) { - return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; - }; - }, - - "CLASS": function( className ) { - var pattern = classCache[ className + " " ]; - - return pattern || - (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && - classCache( className, function( elem ) { - return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute("class") || "" ); - }); - }, - - "ATTR": function( name, operator, check ) { - return function( elem ) { - var result = Sizzle.attr( elem, name ); - - if ( result == null ) { - return operator === "!="; - } - if ( !operator ) { - return true; - } - - result += ""; - - return operator === "=" ? result === check : - operator === "!=" ? result !== check : - operator === "^=" ? check && result.indexOf( check ) === 0 : - operator === "*=" ? check && result.indexOf( check ) > -1 : - operator === "$=" ? check && result.slice( -check.length ) === check : - operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : - operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : - false; - }; - }, - - "CHILD": function( type, what, argument, first, last ) { - var simple = type.slice( 0, 3 ) !== "nth", - forward = type.slice( -4 ) !== "last", - ofType = what === "of-type"; - - return first === 1 && last === 0 ? - - // Shortcut for :nth-*(n) - function( elem ) { - return !!elem.parentNode; - } : - - function( elem, context, xml ) { - var cache, uniqueCache, outerCache, node, nodeIndex, start, - dir = simple !== forward ? "nextSibling" : "previousSibling", - parent = elem.parentNode, - name = ofType && elem.nodeName.toLowerCase(), - useCache = !xml && !ofType, - diff = false; - - if ( parent ) { - - // :(first|last|only)-(child|of-type) - if ( simple ) { - while ( dir ) { - node = elem; - while ( (node = node[ dir ]) ) { - if ( ofType ? - node.nodeName.toLowerCase() === name : - node.nodeType === 1 ) { - - return false; - } - } - // Reverse direction for :only-* (if we haven't yet done so) - start = dir = type === "only" && !start && "nextSibling"; - } - return true; - } - - start = [ forward ? parent.firstChild : parent.lastChild ]; - - // non-xml :nth-child(...) stores cache data on `parent` - if ( forward && useCache ) { - - // Seek `elem` from a previously-cached index - - // ...in a gzip-friendly way - node = parent; - outerCache = node[ expando ] || (node[ expando ] = {}); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ node.uniqueID ] || - (outerCache[ node.uniqueID ] = {}); - - cache = uniqueCache[ type ] || []; - nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; - diff = nodeIndex && cache[ 2 ]; - node = nodeIndex && parent.childNodes[ nodeIndex ]; - - while ( (node = ++nodeIndex && node && node[ dir ] || - - // Fallback to seeking `elem` from the start - (diff = nodeIndex = 0) || start.pop()) ) { - - // When found, cache indexes on `parent` and break - if ( node.nodeType === 1 && ++diff && node === elem ) { - uniqueCache[ type ] = [ dirruns, nodeIndex, diff ]; - break; - } - } - - } else { - // Use previously-cached element index if available - if ( useCache ) { - // ...in a gzip-friendly way - node = elem; - outerCache = node[ expando ] || (node[ expando ] = {}); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ node.uniqueID ] || - (outerCache[ node.uniqueID ] = {}); - - cache = uniqueCache[ type ] || []; - nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; - diff = nodeIndex; - } - - // xml :nth-child(...) - // or :nth-last-child(...) or :nth(-last)?-of-type(...) - if ( diff === false ) { - // Use the same loop as above to seek `elem` from the start - while ( (node = ++nodeIndex && node && node[ dir ] || - (diff = nodeIndex = 0) || start.pop()) ) { - - if ( ( ofType ? - node.nodeName.toLowerCase() === name : - node.nodeType === 1 ) && - ++diff ) { - - // Cache the index of each encountered element - if ( useCache ) { - outerCache = node[ expando ] || (node[ expando ] = {}); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ node.uniqueID ] || - (outerCache[ node.uniqueID ] = {}); - - uniqueCache[ type ] = [ dirruns, diff ]; - } - - if ( node === elem ) { - break; - } - } - } - } - } - - // Incorporate the offset, then check against cycle size - diff -= last; - return diff === first || ( diff % first === 0 && diff / first >= 0 ); - } - }; - }, - - "PSEUDO": function( pseudo, argument ) { - // pseudo-class names are case-insensitive - // http://www.w3.org/TR/selectors/#pseudo-classes - // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters - // Remember that setFilters inherits from pseudos - var args, - fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || - Sizzle.error( "unsupported pseudo: " + pseudo ); - - // The user may use createPseudo to indicate that - // arguments are needed to create the filter function - // just as Sizzle does - if ( fn[ expando ] ) { - return fn( argument ); - } - - // But maintain support for old signatures - if ( fn.length > 1 ) { - args = [ pseudo, pseudo, "", argument ]; - return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? - markFunction(function( seed, matches ) { - var idx, - matched = fn( seed, argument ), - i = matched.length; - while ( i-- ) { - idx = indexOf( seed, matched[i] ); - seed[ idx ] = !( matches[ idx ] = matched[i] ); - } - }) : - function( elem ) { - return fn( elem, 0, args ); - }; - } - - return fn; - } - }, - - pseudos: { - // Potentially complex pseudos - "not": markFunction(function( selector ) { - // Trim the selector passed to compile - // to avoid treating leading and trailing - // spaces as combinators - var input = [], - results = [], - matcher = compile( selector.replace( rtrim, "$1" ) ); - - return matcher[ expando ] ? - markFunction(function( seed, matches, context, xml ) { - var elem, - unmatched = matcher( seed, null, xml, [] ), - i = seed.length; - - // Match elements unmatched by `matcher` - while ( i-- ) { - if ( (elem = unmatched[i]) ) { - seed[i] = !(matches[i] = elem); - } - } - }) : - function( elem, context, xml ) { - input[0] = elem; - matcher( input, null, xml, results ); - // Don't keep the element (issue #299) - input[0] = null; - return !results.pop(); - }; - }), - - "has": markFunction(function( selector ) { - return function( elem ) { - return Sizzle( selector, elem ).length > 0; - }; - }), - - "contains": markFunction(function( text ) { - text = text.replace( runescape, funescape ); - return function( elem ) { - return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; - }; - }), - - // "Whether an element is represented by a :lang() selector - // is based solely on the element's language value - // being equal to the identifier C, - // or beginning with the identifier C immediately followed by "-". - // The matching of C against the element's language value is performed case-insensitively. - // The identifier C does not have to be a valid language name." - // http://www.w3.org/TR/selectors/#lang-pseudo - "lang": markFunction( function( lang ) { - // lang value must be a valid identifier - if ( !ridentifier.test(lang || "") ) { - Sizzle.error( "unsupported lang: " + lang ); - } - lang = lang.replace( runescape, funescape ).toLowerCase(); - return function( elem ) { - var elemLang; - do { - if ( (elemLang = documentIsHTML ? - elem.lang : - elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) { - - elemLang = elemLang.toLowerCase(); - return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; - } - } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); - return false; - }; - }), - - // Miscellaneous - "target": function( elem ) { - var hash = window.location && window.location.hash; - return hash && hash.slice( 1 ) === elem.id; - }, - - "root": function( elem ) { - return elem === docElem; - }, - - "focus": function( elem ) { - return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); - }, - - // Boolean properties - "enabled": createDisabledPseudo( false ), - "disabled": createDisabledPseudo( true ), - - "checked": function( elem ) { - // In CSS3, :checked should return both checked and selected elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - var nodeName = elem.nodeName.toLowerCase(); - return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); - }, - - "selected": function( elem ) { - // Accessing this property makes selected-by-default - // options in Safari work properly - if ( elem.parentNode ) { - elem.parentNode.selectedIndex; - } - - return elem.selected === true; - }, - - // Contents - "empty": function( elem ) { - // http://www.w3.org/TR/selectors/#empty-pseudo - // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), - // but not by others (comment: 8; processing instruction: 7; etc.) - // nodeType < 6 works because attributes (2) do not appear as children - for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { - if ( elem.nodeType < 6 ) { - return false; - } - } - return true; - }, - - "parent": function( elem ) { - return !Expr.pseudos["empty"]( elem ); - }, - - // Element/input types - "header": function( elem ) { - return rheader.test( elem.nodeName ); - }, - - "input": function( elem ) { - return rinputs.test( elem.nodeName ); - }, - - "button": function( elem ) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && elem.type === "button" || name === "button"; - }, - - "text": function( elem ) { - var attr; - return elem.nodeName.toLowerCase() === "input" && - elem.type === "text" && - - // Support: IE<8 - // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" - ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" ); - }, - - // Position-in-collection - "first": createPositionalPseudo(function() { - return [ 0 ]; - }), - - "last": createPositionalPseudo(function( matchIndexes, length ) { - return [ length - 1 ]; - }), - - "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { - return [ argument < 0 ? argument + length : argument ]; - }), - - "even": createPositionalPseudo(function( matchIndexes, length ) { - var i = 0; - for ( ; i < length; i += 2 ) { - matchIndexes.push( i ); - } - return matchIndexes; - }), - - "odd": createPositionalPseudo(function( matchIndexes, length ) { - var i = 1; - for ( ; i < length; i += 2 ) { - matchIndexes.push( i ); - } - return matchIndexes; - }), - - "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { - var i = argument < 0 ? argument + length : argument; - for ( ; --i >= 0; ) { - matchIndexes.push( i ); - } - return matchIndexes; - }), - - "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { - var i = argument < 0 ? argument + length : argument; - for ( ; ++i < length; ) { - matchIndexes.push( i ); - } - return matchIndexes; - }) - } -}; - -Expr.pseudos["nth"] = Expr.pseudos["eq"]; - -// Add button/input type pseudos -for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { - Expr.pseudos[ i ] = createInputPseudo( i ); -} -for ( i in { submit: true, reset: true } ) { - Expr.pseudos[ i ] = createButtonPseudo( i ); -} - -// Easy API for creating new setFilters -function setFilters() {} -setFilters.prototype = Expr.filters = Expr.pseudos; -Expr.setFilters = new setFilters(); - -tokenize = Sizzle.tokenize = function( selector, parseOnly ) { - var matched, match, tokens, type, - soFar, groups, preFilters, - cached = tokenCache[ selector + " " ]; - - if ( cached ) { - return parseOnly ? 0 : cached.slice( 0 ); - } - - soFar = selector; - groups = []; - preFilters = Expr.preFilter; - - while ( soFar ) { - - // Comma and first run - if ( !matched || (match = rcomma.exec( soFar )) ) { - if ( match ) { - // Don't consume trailing commas as valid - soFar = soFar.slice( match[0].length ) || soFar; - } - groups.push( (tokens = []) ); - } - - matched = false; - - // Combinators - if ( (match = rcombinators.exec( soFar )) ) { - matched = match.shift(); - tokens.push({ - value: matched, - // Cast descendant combinators to space - type: match[0].replace( rtrim, " " ) - }); - soFar = soFar.slice( matched.length ); - } - - // Filters - for ( type in Expr.filter ) { - if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || - (match = preFilters[ type ]( match ))) ) { - matched = match.shift(); - tokens.push({ - value: matched, - type: type, - matches: match - }); - soFar = soFar.slice( matched.length ); - } - } - - if ( !matched ) { - break; - } - } - - // Return the length of the invalid excess - // if we're just parsing - // Otherwise, throw an error or return tokens - return parseOnly ? - soFar.length : - soFar ? - Sizzle.error( selector ) : - // Cache the tokens - tokenCache( selector, groups ).slice( 0 ); -}; - -function toSelector( tokens ) { - var i = 0, - len = tokens.length, - selector = ""; - for ( ; i < len; i++ ) { - selector += tokens[i].value; - } - return selector; -} - -function addCombinator( matcher, combinator, base ) { - var dir = combinator.dir, - skip = combinator.next, - key = skip || dir, - checkNonElements = base && key === "parentNode", - doneName = done++; - - return combinator.first ? - // Check against closest ancestor/preceding element - function( elem, context, xml ) { - while ( (elem = elem[ dir ]) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - return matcher( elem, context, xml ); - } - } - return false; - } : - - // Check against all ancestor/preceding elements - function( elem, context, xml ) { - var oldCache, uniqueCache, outerCache, - newCache = [ dirruns, doneName ]; - - // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching - if ( xml ) { - while ( (elem = elem[ dir ]) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - if ( matcher( elem, context, xml ) ) { - return true; - } - } - } - } else { - while ( (elem = elem[ dir ]) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - outerCache = elem[ expando ] || (elem[ expando ] = {}); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ elem.uniqueID ] || (outerCache[ elem.uniqueID ] = {}); - - if ( skip && skip === elem.nodeName.toLowerCase() ) { - elem = elem[ dir ] || elem; - } else if ( (oldCache = uniqueCache[ key ]) && - oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { - - // Assign to newCache so results back-propagate to previous elements - return (newCache[ 2 ] = oldCache[ 2 ]); - } else { - // Reuse newcache so results back-propagate to previous elements - uniqueCache[ key ] = newCache; - - // A match means we're done; a fail means we have to keep checking - if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) { - return true; - } - } - } - } - } - return false; - }; -} - -function elementMatcher( matchers ) { - return matchers.length > 1 ? - function( elem, context, xml ) { - var i = matchers.length; - while ( i-- ) { - if ( !matchers[i]( elem, context, xml ) ) { - return false; - } - } - return true; - } : - matchers[0]; -} - -function multipleContexts( selector, contexts, results ) { - var i = 0, - len = contexts.length; - for ( ; i < len; i++ ) { - Sizzle( selector, contexts[i], results ); - } - return results; -} - -function condense( unmatched, map, filter, context, xml ) { - var elem, - newUnmatched = [], - i = 0, - len = unmatched.length, - mapped = map != null; - - for ( ; i < len; i++ ) { - if ( (elem = unmatched[i]) ) { - if ( !filter || filter( elem, context, xml ) ) { - newUnmatched.push( elem ); - if ( mapped ) { - map.push( i ); - } - } - } - } - - return newUnmatched; -} - -function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { - if ( postFilter && !postFilter[ expando ] ) { - postFilter = setMatcher( postFilter ); - } - if ( postFinder && !postFinder[ expando ] ) { - postFinder = setMatcher( postFinder, postSelector ); - } - return markFunction(function( seed, results, context, xml ) { - var temp, i, elem, - preMap = [], - postMap = [], - preexisting = results.length, - - // Get initial elements from seed or context - elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), - - // Prefilter to get matcher input, preserving a map for seed-results synchronization - matcherIn = preFilter && ( seed || !selector ) ? - condense( elems, preMap, preFilter, context, xml ) : - elems, - - matcherOut = matcher ? - // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, - postFinder || ( seed ? preFilter : preexisting || postFilter ) ? - - // ...intermediate processing is necessary - [] : - - // ...otherwise use results directly - results : - matcherIn; - - // Find primary matches - if ( matcher ) { - matcher( matcherIn, matcherOut, context, xml ); - } - - // Apply postFilter - if ( postFilter ) { - temp = condense( matcherOut, postMap ); - postFilter( temp, [], context, xml ); - - // Un-match failing elements by moving them back to matcherIn - i = temp.length; - while ( i-- ) { - if ( (elem = temp[i]) ) { - matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); - } - } - } - - if ( seed ) { - if ( postFinder || preFilter ) { - if ( postFinder ) { - // Get the final matcherOut by condensing this intermediate into postFinder contexts - temp = []; - i = matcherOut.length; - while ( i-- ) { - if ( (elem = matcherOut[i]) ) { - // Restore matcherIn since elem is not yet a final match - temp.push( (matcherIn[i] = elem) ); - } - } - postFinder( null, (matcherOut = []), temp, xml ); - } - - // Move matched elements from seed to results to keep them synchronized - i = matcherOut.length; - while ( i-- ) { - if ( (elem = matcherOut[i]) && - (temp = postFinder ? indexOf( seed, elem ) : preMap[i]) > -1 ) { - - seed[temp] = !(results[temp] = elem); - } - } - } - - // Add elements to results, through postFinder if defined - } else { - matcherOut = condense( - matcherOut === results ? - matcherOut.splice( preexisting, matcherOut.length ) : - matcherOut - ); - if ( postFinder ) { - postFinder( null, results, matcherOut, xml ); - } else { - push.apply( results, matcherOut ); - } - } - }); -} - -function matcherFromTokens( tokens ) { - var checkContext, matcher, j, - len = tokens.length, - leadingRelative = Expr.relative[ tokens[0].type ], - implicitRelative = leadingRelative || Expr.relative[" "], - i = leadingRelative ? 1 : 0, - - // The foundational matcher ensures that elements are reachable from top-level context(s) - matchContext = addCombinator( function( elem ) { - return elem === checkContext; - }, implicitRelative, true ), - matchAnyContext = addCombinator( function( elem ) { - return indexOf( checkContext, elem ) > -1; - }, implicitRelative, true ), - matchers = [ function( elem, context, xml ) { - var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( - (checkContext = context).nodeType ? - matchContext( elem, context, xml ) : - matchAnyContext( elem, context, xml ) ); - // Avoid hanging onto element (issue #299) - checkContext = null; - return ret; - } ]; - - for ( ; i < len; i++ ) { - if ( (matcher = Expr.relative[ tokens[i].type ]) ) { - matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; - } else { - matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); - - // Return special upon seeing a positional matcher - if ( matcher[ expando ] ) { - // Find the next relative operator (if any) for proper handling - j = ++i; - for ( ; j < len; j++ ) { - if ( Expr.relative[ tokens[j].type ] ) { - break; - } - } - return setMatcher( - i > 1 && elementMatcher( matchers ), - i > 1 && toSelector( - // If the preceding token was a descendant combinator, insert an implicit any-element `*` - tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" }) - ).replace( rtrim, "$1" ), - matcher, - i < j && matcherFromTokens( tokens.slice( i, j ) ), - j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), - j < len && toSelector( tokens ) - ); - } - matchers.push( matcher ); - } - } - - return elementMatcher( matchers ); -} - -function matcherFromGroupMatchers( elementMatchers, setMatchers ) { - var bySet = setMatchers.length > 0, - byElement = elementMatchers.length > 0, - superMatcher = function( seed, context, xml, results, outermost ) { - var elem, j, matcher, - matchedCount = 0, - i = "0", - unmatched = seed && [], - setMatched = [], - contextBackup = outermostContext, - // We must always have either seed elements or outermost context - elems = seed || byElement && Expr.find["TAG"]( "*", outermost ), - // Use integer dirruns iff this is the outermost matcher - dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1), - len = elems.length; - - if ( outermost ) { - outermostContext = context === document || context || outermost; - } - - // Add elements passing elementMatchers directly to results - // Support: IE<9, Safari - // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id - for ( ; i !== len && (elem = elems[i]) != null; i++ ) { - if ( byElement && elem ) { - j = 0; - if ( !context && elem.ownerDocument !== document ) { - setDocument( elem ); - xml = !documentIsHTML; - } - while ( (matcher = elementMatchers[j++]) ) { - if ( matcher( elem, context || document, xml) ) { - results.push( elem ); - break; - } - } - if ( outermost ) { - dirruns = dirrunsUnique; - } - } - - // Track unmatched elements for set filters - if ( bySet ) { - // They will have gone through all possible matchers - if ( (elem = !matcher && elem) ) { - matchedCount--; - } - - // Lengthen the array for every element, matched or not - if ( seed ) { - unmatched.push( elem ); - } - } - } - - // `i` is now the count of elements visited above, and adding it to `matchedCount` - // makes the latter nonnegative. - matchedCount += i; - - // Apply set filters to unmatched elements - // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` - // equals `i`), unless we didn't visit _any_ elements in the above loop because we have - // no element matchers and no seed. - // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that - // case, which will result in a "00" `matchedCount` that differs from `i` but is also - // numerically zero. - if ( bySet && i !== matchedCount ) { - j = 0; - while ( (matcher = setMatchers[j++]) ) { - matcher( unmatched, setMatched, context, xml ); - } - - if ( seed ) { - // Reintegrate element matches to eliminate the need for sorting - if ( matchedCount > 0 ) { - while ( i-- ) { - if ( !(unmatched[i] || setMatched[i]) ) { - setMatched[i] = pop.call( results ); - } - } - } - - // Discard index placeholder values to get only actual matches - setMatched = condense( setMatched ); - } - - // Add matches to results - push.apply( results, setMatched ); - - // Seedless set matches succeeding multiple successful matchers stipulate sorting - if ( outermost && !seed && setMatched.length > 0 && - ( matchedCount + setMatchers.length ) > 1 ) { - - Sizzle.uniqueSort( results ); - } - } - - // Override manipulation of globals by nested matchers - if ( outermost ) { - dirruns = dirrunsUnique; - outermostContext = contextBackup; - } - - return unmatched; - }; - - return bySet ? - markFunction( superMatcher ) : - superMatcher; -} - -compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { - var i, - setMatchers = [], - elementMatchers = [], - cached = compilerCache[ selector + " " ]; - - if ( !cached ) { - // Generate a function of recursive functions that can be used to check each element - if ( !match ) { - match = tokenize( selector ); - } - i = match.length; - while ( i-- ) { - cached = matcherFromTokens( match[i] ); - if ( cached[ expando ] ) { - setMatchers.push( cached ); - } else { - elementMatchers.push( cached ); - } - } - - // Cache the compiled function - cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); - - // Save selector and tokenization - cached.selector = selector; - } - return cached; -}; - -/** - * A low-level selection function that works with Sizzle's compiled - * selector functions - * @param {String|Function} selector A selector or a pre-compiled - * selector function built with Sizzle.compile - * @param {Element} context - * @param {Array} [results] - * @param {Array} [seed] A set of elements to match against - */ -select = Sizzle.select = function( selector, context, results, seed ) { - var i, tokens, token, type, find, - compiled = typeof selector === "function" && selector, - match = !seed && tokenize( (selector = compiled.selector || selector) ); - - results = results || []; - - // Try to minimize operations if there is only one selector in the list and no seed - // (the latter of which guarantees us context) - if ( match.length === 1 ) { - - // Reduce context if the leading compound selector is an ID - tokens = match[0] = match[0].slice( 0 ); - if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && - context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[1].type ] ) { - - context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0]; - if ( !context ) { - return results; - - // Precompiled matchers will still verify ancestry, so step up a level - } else if ( compiled ) { - context = context.parentNode; - } - - selector = selector.slice( tokens.shift().value.length ); - } - - // Fetch a seed set for right-to-left matching - i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; - while ( i-- ) { - token = tokens[i]; - - // Abort if we hit a combinator - if ( Expr.relative[ (type = token.type) ] ) { - break; - } - if ( (find = Expr.find[ type ]) ) { - // Search, expanding context for leading sibling combinators - if ( (seed = find( - token.matches[0].replace( runescape, funescape ), - rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context - )) ) { - - // If seed is empty or no tokens remain, we can return early - tokens.splice( i, 1 ); - selector = seed.length && toSelector( tokens ); - if ( !selector ) { - push.apply( results, seed ); - return results; - } - - break; - } - } - } - } - - // Compile and execute a filtering function if one is not provided - // Provide `match` to avoid retokenization if we modified the selector above - ( compiled || compile( selector, match ) )( - seed, - context, - !documentIsHTML, - results, - !context || rsibling.test( selector ) && testContext( context.parentNode ) || context - ); - return results; -}; - -// One-time assignments - -// Sort stability -support.sortStable = expando.split("").sort( sortOrder ).join("") === expando; - -// Support: Chrome 14-35+ -// Always assume duplicates if they aren't passed to the comparison function -support.detectDuplicates = !!hasDuplicate; - -// Initialize against the default document -setDocument(); - -// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) -// Detached nodes confoundingly follow *each other* -support.sortDetached = assert(function( el ) { - // Should return 1, but returns 4 (following) - return el.compareDocumentPosition( document.createElement("fieldset") ) & 1; -}); - -// Support: IE<8 -// Prevent attribute/property "interpolation" -// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx -if ( !assert(function( el ) { - el.innerHTML = ""; - return el.firstChild.getAttribute("href") === "#" ; -}) ) { - addHandle( "type|href|height|width", function( elem, name, isXML ) { - if ( !isXML ) { - return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); - } - }); -} - -// Support: IE<9 -// Use defaultValue in place of getAttribute("value") -if ( !support.attributes || !assert(function( el ) { - el.innerHTML = ""; - el.firstChild.setAttribute( "value", "" ); - return el.firstChild.getAttribute( "value" ) === ""; -}) ) { - addHandle( "value", function( elem, name, isXML ) { - if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { - return elem.defaultValue; - } - }); -} - -// Support: IE<9 -// Use getAttributeNode to fetch booleans when getAttribute lies -if ( !assert(function( el ) { - return el.getAttribute("disabled") == null; -}) ) { - addHandle( booleans, function( elem, name, isXML ) { - var val; - if ( !isXML ) { - return elem[ name ] === true ? name.toLowerCase() : - (val = elem.getAttributeNode( name )) && val.specified ? - val.value : - null; - } - }); -} - -return Sizzle; - -})( window ); - - - -jQuery.find = Sizzle; -jQuery.expr = Sizzle.selectors; - -// Deprecated -jQuery.expr[ ":" ] = jQuery.expr.pseudos; -jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort; -jQuery.text = Sizzle.getText; -jQuery.isXMLDoc = Sizzle.isXML; -jQuery.contains = Sizzle.contains; -jQuery.escapeSelector = Sizzle.escape; - - - - -var dir = function( elem, dir, until ) { - var matched = [], - truncate = until !== undefined; - - while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { - if ( elem.nodeType === 1 ) { - if ( truncate && jQuery( elem ).is( until ) ) { - break; - } - matched.push( elem ); - } - } - return matched; -}; - - -var siblings = function( n, elem ) { - var matched = []; - - for ( ; n; n = n.nextSibling ) { - if ( n.nodeType === 1 && n !== elem ) { - matched.push( n ); - } - } - - return matched; -}; - - -var rneedsContext = jQuery.expr.match.needsContext; - - - -function nodeName( elem, name ) { - - return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); - -}; -var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); - - - -var risSimple = /^.[^:#\[\.,]*$/; - -// Implement the identical functionality for filter and not -function winnow( elements, qualifier, not ) { - if ( jQuery.isFunction( qualifier ) ) { - return jQuery.grep( elements, function( elem, i ) { - return !!qualifier.call( elem, i, elem ) !== not; - } ); - } - - // Single element - if ( qualifier.nodeType ) { - return jQuery.grep( elements, function( elem ) { - return ( elem === qualifier ) !== not; - } ); - } - - // Arraylike of elements (jQuery, arguments, Array) - if ( typeof qualifier !== "string" ) { - return jQuery.grep( elements, function( elem ) { - return ( indexOf.call( qualifier, elem ) > -1 ) !== not; - } ); - } - - // Simple selector that can be filtered directly, removing non-Elements - if ( risSimple.test( qualifier ) ) { - return jQuery.filter( qualifier, elements, not ); - } - - // Complex selector, compare the two sets, removing non-Elements - qualifier = jQuery.filter( qualifier, elements ); - return jQuery.grep( elements, function( elem ) { - return ( indexOf.call( qualifier, elem ) > -1 ) !== not && elem.nodeType === 1; - } ); -} - -jQuery.filter = function( expr, elems, not ) { - var elem = elems[ 0 ]; - - if ( not ) { - expr = ":not(" + expr + ")"; - } - - if ( elems.length === 1 && elem.nodeType === 1 ) { - return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : []; - } - - return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { - return elem.nodeType === 1; - } ) ); -}; - -jQuery.fn.extend( { - find: function( selector ) { - var i, ret, - len = this.length, - self = this; - - if ( typeof selector !== "string" ) { - return this.pushStack( jQuery( selector ).filter( function() { - for ( i = 0; i < len; i++ ) { - if ( jQuery.contains( self[ i ], this ) ) { - return true; - } - } - } ) ); - } - - ret = this.pushStack( [] ); - - for ( i = 0; i < len; i++ ) { - jQuery.find( selector, self[ i ], ret ); - } - - return len > 1 ? jQuery.uniqueSort( ret ) : ret; - }, - filter: function( selector ) { - return this.pushStack( winnow( this, selector || [], false ) ); - }, - not: function( selector ) { - return this.pushStack( winnow( this, selector || [], true ) ); - }, - is: function( selector ) { - return !!winnow( - this, - - // If this is a positional/relative selector, check membership in the returned set - // so $("p:first").is("p:last") won't return true for a doc with two "p". - typeof selector === "string" && rneedsContext.test( selector ) ? - jQuery( selector ) : - selector || [], - false - ).length; - } -} ); - - -// Initialize a jQuery object - - -// A central reference to the root jQuery(document) -var rootjQuery, - - // A simple way to check for HTML strings - // Prioritize #id over to avoid XSS via location.hash (#9521) - // Strict HTML recognition (#11290: must start with <) - // Shortcut simple #id case for speed - rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, - - init = jQuery.fn.init = function( selector, context, root ) { - var match, elem; - - // HANDLE: $(""), $(null), $(undefined), $(false) - if ( !selector ) { - return this; - } - - // Method init() accepts an alternate rootjQuery - // so migrate can support jQuery.sub (gh-2101) - root = root || rootjQuery; - - // Handle HTML strings - if ( typeof selector === "string" ) { - if ( selector[ 0 ] === "<" && - selector[ selector.length - 1 ] === ">" && - selector.length >= 3 ) { - - // Assume that strings that start and end with <> are HTML and skip the regex check - match = [ null, selector, null ]; - - } else { - match = rquickExpr.exec( selector ); - } - - // Match html or make sure no context is specified for #id - if ( match && ( match[ 1 ] || !context ) ) { - - // HANDLE: $(html) -> $(array) - if ( match[ 1 ] ) { - context = context instanceof jQuery ? context[ 0 ] : context; - - // Option to run scripts is true for back-compat - // Intentionally let the error be thrown if parseHTML is not present - jQuery.merge( this, jQuery.parseHTML( - match[ 1 ], - context && context.nodeType ? context.ownerDocument || context : document, - true - ) ); - - // HANDLE: $(html, props) - if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { - for ( match in context ) { - - // Properties of context are called as methods if possible - if ( jQuery.isFunction( this[ match ] ) ) { - this[ match ]( context[ match ] ); - - // ...and otherwise set as attributes - } else { - this.attr( match, context[ match ] ); - } - } - } - - return this; - - // HANDLE: $(#id) - } else { - elem = document.getElementById( match[ 2 ] ); - - if ( elem ) { - - // Inject the element directly into the jQuery object - this[ 0 ] = elem; - this.length = 1; - } - return this; - } - - // HANDLE: $(expr, $(...)) - } else if ( !context || context.jquery ) { - return ( context || root ).find( selector ); - - // HANDLE: $(expr, context) - // (which is just equivalent to: $(context).find(expr) - } else { - return this.constructor( context ).find( selector ); - } - - // HANDLE: $(DOMElement) - } else if ( selector.nodeType ) { - this[ 0 ] = selector; - this.length = 1; - return this; - - // HANDLE: $(function) - // Shortcut for document ready - } else if ( jQuery.isFunction( selector ) ) { - return root.ready !== undefined ? - root.ready( selector ) : - - // Execute immediately if ready is not present - selector( jQuery ); - } - - return jQuery.makeArray( selector, this ); - }; - -// Give the init function the jQuery prototype for later instantiation -init.prototype = jQuery.fn; - -// Initialize central reference -rootjQuery = jQuery( document ); - - -var rparentsprev = /^(?:parents|prev(?:Until|All))/, - - // Methods guaranteed to produce a unique set when starting from a unique set - guaranteedUnique = { - children: true, - contents: true, - next: true, - prev: true - }; - -jQuery.fn.extend( { - has: function( target ) { - var targets = jQuery( target, this ), - l = targets.length; - - return this.filter( function() { - var i = 0; - for ( ; i < l; i++ ) { - if ( jQuery.contains( this, targets[ i ] ) ) { - return true; - } - } - } ); - }, - - closest: function( selectors, context ) { - var cur, - i = 0, - l = this.length, - matched = [], - targets = typeof selectors !== "string" && jQuery( selectors ); - - // Positional selectors never match, since there's no _selection_ context - if ( !rneedsContext.test( selectors ) ) { - for ( ; i < l; i++ ) { - for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { - - // Always skip document fragments - if ( cur.nodeType < 11 && ( targets ? - targets.index( cur ) > -1 : - - // Don't pass non-elements to Sizzle - cur.nodeType === 1 && - jQuery.find.matchesSelector( cur, selectors ) ) ) { - - matched.push( cur ); - break; - } - } - } - } - - return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); - }, - - // Determine the position of an element within the set - index: function( elem ) { - - // No argument, return index in parent - if ( !elem ) { - return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; - } - - // Index in selector - if ( typeof elem === "string" ) { - return indexOf.call( jQuery( elem ), this[ 0 ] ); - } - - // Locate the position of the desired element - return indexOf.call( this, - - // If it receives a jQuery object, the first element is used - elem.jquery ? elem[ 0 ] : elem - ); - }, - - add: function( selector, context ) { - return this.pushStack( - jQuery.uniqueSort( - jQuery.merge( this.get(), jQuery( selector, context ) ) - ) - ); - }, - - addBack: function( selector ) { - return this.add( selector == null ? - this.prevObject : this.prevObject.filter( selector ) - ); - } -} ); - -function sibling( cur, dir ) { - while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {} - return cur; -} - -jQuery.each( { - parent: function( elem ) { - var parent = elem.parentNode; - return parent && parent.nodeType !== 11 ? parent : null; - }, - parents: function( elem ) { - return dir( elem, "parentNode" ); - }, - parentsUntil: function( elem, i, until ) { - return dir( elem, "parentNode", until ); - }, - next: function( elem ) { - return sibling( elem, "nextSibling" ); - }, - prev: function( elem ) { - return sibling( elem, "previousSibling" ); - }, - nextAll: function( elem ) { - return dir( elem, "nextSibling" ); - }, - prevAll: function( elem ) { - return dir( elem, "previousSibling" ); - }, - nextUntil: function( elem, i, until ) { - return dir( elem, "nextSibling", until ); - }, - prevUntil: function( elem, i, until ) { - return dir( elem, "previousSibling", until ); - }, - siblings: function( elem ) { - return siblings( ( elem.parentNode || {} ).firstChild, elem ); - }, - children: function( elem ) { - return siblings( elem.firstChild ); - }, - contents: function( elem ) { - if ( nodeName( elem, "iframe" ) ) { - return elem.contentDocument; - } - - // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only - // Treat the template element as a regular one in browsers that - // don't support it. - if ( nodeName( elem, "template" ) ) { - elem = elem.content || elem; - } - - return jQuery.merge( [], elem.childNodes ); - } -}, function( name, fn ) { - jQuery.fn[ name ] = function( until, selector ) { - var matched = jQuery.map( this, fn, until ); - - if ( name.slice( -5 ) !== "Until" ) { - selector = until; - } - - if ( selector && typeof selector === "string" ) { - matched = jQuery.filter( selector, matched ); - } - - if ( this.length > 1 ) { - - // Remove duplicates - if ( !guaranteedUnique[ name ] ) { - jQuery.uniqueSort( matched ); - } - - // Reverse order for parents* and prev-derivatives - if ( rparentsprev.test( name ) ) { - matched.reverse(); - } - } - - return this.pushStack( matched ); - }; -} ); -var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); - - - -// Convert String-formatted options into Object-formatted ones -function createOptions( options ) { - var object = {}; - jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { - object[ flag ] = true; - } ); - return object; -} - -/* - * Create a callback list using the following parameters: - * - * options: an optional list of space-separated options that will change how - * the callback list behaves or a more traditional option object - * - * By default a callback list will act like an event callback list and can be - * "fired" multiple times. - * - * Possible options: - * - * once: will ensure the callback list can only be fired once (like a Deferred) - * - * memory: will keep track of previous values and will call any callback added - * after the list has been fired right away with the latest "memorized" - * values (like a Deferred) - * - * unique: will ensure a callback can only be added once (no duplicate in the list) - * - * stopOnFalse: interrupt callings when a callback returns false - * - */ -jQuery.Callbacks = function( options ) { - - // Convert options from String-formatted to Object-formatted if needed - // (we check in cache first) - options = typeof options === "string" ? - createOptions( options ) : - jQuery.extend( {}, options ); - - var // Flag to know if list is currently firing - firing, - - // Last fire value for non-forgettable lists - memory, - - // Flag to know if list was already fired - fired, - - // Flag to prevent firing - locked, - - // Actual callback list - list = [], - - // Queue of execution data for repeatable lists - queue = [], - - // Index of currently firing callback (modified by add/remove as needed) - firingIndex = -1, - - // Fire callbacks - fire = function() { - - // Enforce single-firing - locked = locked || options.once; - - // Execute callbacks for all pending executions, - // respecting firingIndex overrides and runtime changes - fired = firing = true; - for ( ; queue.length; firingIndex = -1 ) { - memory = queue.shift(); - while ( ++firingIndex < list.length ) { - - // Run callback and check for early termination - if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && - options.stopOnFalse ) { - - // Jump to end and forget the data so .add doesn't re-fire - firingIndex = list.length; - memory = false; - } - } - } - - // Forget the data if we're done with it - if ( !options.memory ) { - memory = false; - } - - firing = false; - - // Clean up if we're done firing for good - if ( locked ) { - - // Keep an empty list if we have data for future add calls - if ( memory ) { - list = []; - - // Otherwise, this object is spent - } else { - list = ""; - } - } - }, - - // Actual Callbacks object - self = { - - // Add a callback or a collection of callbacks to the list - add: function() { - if ( list ) { - - // If we have memory from a past run, we should fire after adding - if ( memory && !firing ) { - firingIndex = list.length - 1; - queue.push( memory ); - } - - ( function add( args ) { - jQuery.each( args, function( _, arg ) { - if ( jQuery.isFunction( arg ) ) { - if ( !options.unique || !self.has( arg ) ) { - list.push( arg ); - } - } else if ( arg && arg.length && jQuery.type( arg ) !== "string" ) { - - // Inspect recursively - add( arg ); - } - } ); - } )( arguments ); - - if ( memory && !firing ) { - fire(); - } - } - return this; - }, - - // Remove a callback from the list - remove: function() { - jQuery.each( arguments, function( _, arg ) { - var index; - while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { - list.splice( index, 1 ); - - // Handle firing indexes - if ( index <= firingIndex ) { - firingIndex--; - } - } - } ); - return this; - }, - - // Check if a given callback is in the list. - // If no argument is given, return whether or not list has callbacks attached. - has: function( fn ) { - return fn ? - jQuery.inArray( fn, list ) > -1 : - list.length > 0; - }, - - // Remove all callbacks from the list - empty: function() { - if ( list ) { - list = []; - } - return this; - }, - - // Disable .fire and .add - // Abort any current/pending executions - // Clear all callbacks and values - disable: function() { - locked = queue = []; - list = memory = ""; - return this; - }, - disabled: function() { - return !list; - }, - - // Disable .fire - // Also disable .add unless we have memory (since it would have no effect) - // Abort any pending executions - lock: function() { - locked = queue = []; - if ( !memory && !firing ) { - list = memory = ""; - } - return this; - }, - locked: function() { - return !!locked; - }, - - // Call all callbacks with the given context and arguments - fireWith: function( context, args ) { - if ( !locked ) { - args = args || []; - args = [ context, args.slice ? args.slice() : args ]; - queue.push( args ); - if ( !firing ) { - fire(); - } - } - return this; - }, - - // Call all the callbacks with the given arguments - fire: function() { - self.fireWith( this, arguments ); - return this; - }, - - // To know if the callbacks have already been called at least once - fired: function() { - return !!fired; - } - }; - - return self; -}; - - -function Identity( v ) { - return v; -} -function Thrower( ex ) { - throw ex; -} - -function adoptValue( value, resolve, reject, noValue ) { - var method; - - try { - - // Check for promise aspect first to privilege synchronous behavior - if ( value && jQuery.isFunction( ( method = value.promise ) ) ) { - method.call( value ).done( resolve ).fail( reject ); - - // Other thenables - } else if ( value && jQuery.isFunction( ( method = value.then ) ) ) { - method.call( value, resolve, reject ); - - // Other non-thenables - } else { - - // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer: - // * false: [ value ].slice( 0 ) => resolve( value ) - // * true: [ value ].slice( 1 ) => resolve() - resolve.apply( undefined, [ value ].slice( noValue ) ); - } - - // For Promises/A+, convert exceptions into rejections - // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in - // Deferred#then to conditionally suppress rejection. - } catch ( value ) { - - // Support: Android 4.0 only - // Strict mode functions invoked without .call/.apply get global-object context - reject.apply( undefined, [ value ] ); - } -} - -jQuery.extend( { - - Deferred: function( func ) { - var tuples = [ - - // action, add listener, callbacks, - // ... .then handlers, argument index, [final state] - [ "notify", "progress", jQuery.Callbacks( "memory" ), - jQuery.Callbacks( "memory" ), 2 ], - [ "resolve", "done", jQuery.Callbacks( "once memory" ), - jQuery.Callbacks( "once memory" ), 0, "resolved" ], - [ "reject", "fail", jQuery.Callbacks( "once memory" ), - jQuery.Callbacks( "once memory" ), 1, "rejected" ] - ], - state = "pending", - promise = { - state: function() { - return state; - }, - always: function() { - deferred.done( arguments ).fail( arguments ); - return this; - }, - "catch": function( fn ) { - return promise.then( null, fn ); - }, - - // Keep pipe for back-compat - pipe: function( /* fnDone, fnFail, fnProgress */ ) { - var fns = arguments; - - return jQuery.Deferred( function( newDefer ) { - jQuery.each( tuples, function( i, tuple ) { - - // Map tuples (progress, done, fail) to arguments (done, fail, progress) - var fn = jQuery.isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; - - // deferred.progress(function() { bind to newDefer or newDefer.notify }) - // deferred.done(function() { bind to newDefer or newDefer.resolve }) - // deferred.fail(function() { bind to newDefer or newDefer.reject }) - deferred[ tuple[ 1 ] ]( function() { - var returned = fn && fn.apply( this, arguments ); - if ( returned && jQuery.isFunction( returned.promise ) ) { - returned.promise() - .progress( newDefer.notify ) - .done( newDefer.resolve ) - .fail( newDefer.reject ); - } else { - newDefer[ tuple[ 0 ] + "With" ]( - this, - fn ? [ returned ] : arguments - ); - } - } ); - } ); - fns = null; - } ).promise(); - }, - then: function( onFulfilled, onRejected, onProgress ) { - var maxDepth = 0; - function resolve( depth, deferred, handler, special ) { - return function() { - var that = this, - args = arguments, - mightThrow = function() { - var returned, then; - - // Support: Promises/A+ section 2.3.3.3.3 - // https://promisesaplus.com/#point-59 - // Ignore double-resolution attempts - if ( depth < maxDepth ) { - return; - } - - returned = handler.apply( that, args ); - - // Support: Promises/A+ section 2.3.1 - // https://promisesaplus.com/#point-48 - if ( returned === deferred.promise() ) { - throw new TypeError( "Thenable self-resolution" ); - } - - // Support: Promises/A+ sections 2.3.3.1, 3.5 - // https://promisesaplus.com/#point-54 - // https://promisesaplus.com/#point-75 - // Retrieve `then` only once - then = returned && - - // Support: Promises/A+ section 2.3.4 - // https://promisesaplus.com/#point-64 - // Only check objects and functions for thenability - ( typeof returned === "object" || - typeof returned === "function" ) && - returned.then; - - // Handle a returned thenable - if ( jQuery.isFunction( then ) ) { - - // Special processors (notify) just wait for resolution - if ( special ) { - then.call( - returned, - resolve( maxDepth, deferred, Identity, special ), - resolve( maxDepth, deferred, Thrower, special ) - ); - - // Normal processors (resolve) also hook into progress - } else { - - // ...and disregard older resolution values - maxDepth++; - - then.call( - returned, - resolve( maxDepth, deferred, Identity, special ), - resolve( maxDepth, deferred, Thrower, special ), - resolve( maxDepth, deferred, Identity, - deferred.notifyWith ) - ); - } - - // Handle all other returned values - } else { - - // Only substitute handlers pass on context - // and multiple values (non-spec behavior) - if ( handler !== Identity ) { - that = undefined; - args = [ returned ]; - } - - // Process the value(s) - // Default process is resolve - ( special || deferred.resolveWith )( that, args ); - } - }, - - // Only normal processors (resolve) catch and reject exceptions - process = special ? - mightThrow : - function() { - try { - mightThrow(); - } catch ( e ) { - - if ( jQuery.Deferred.exceptionHook ) { - jQuery.Deferred.exceptionHook( e, - process.stackTrace ); - } - - // Support: Promises/A+ section 2.3.3.3.4.1 - // https://promisesaplus.com/#point-61 - // Ignore post-resolution exceptions - if ( depth + 1 >= maxDepth ) { - - // Only substitute handlers pass on context - // and multiple values (non-spec behavior) - if ( handler !== Thrower ) { - that = undefined; - args = [ e ]; - } - - deferred.rejectWith( that, args ); - } - } - }; - - // Support: Promises/A+ section 2.3.3.3.1 - // https://promisesaplus.com/#point-57 - // Re-resolve promises immediately to dodge false rejection from - // subsequent errors - if ( depth ) { - process(); - } else { - - // Call an optional hook to record the stack, in case of exception - // since it's otherwise lost when execution goes async - if ( jQuery.Deferred.getStackHook ) { - process.stackTrace = jQuery.Deferred.getStackHook(); - } - window.setTimeout( process ); - } - }; - } - - return jQuery.Deferred( function( newDefer ) { - - // progress_handlers.add( ... ) - tuples[ 0 ][ 3 ].add( - resolve( - 0, - newDefer, - jQuery.isFunction( onProgress ) ? - onProgress : - Identity, - newDefer.notifyWith - ) - ); - - // fulfilled_handlers.add( ... ) - tuples[ 1 ][ 3 ].add( - resolve( - 0, - newDefer, - jQuery.isFunction( onFulfilled ) ? - onFulfilled : - Identity - ) - ); - - // rejected_handlers.add( ... ) - tuples[ 2 ][ 3 ].add( - resolve( - 0, - newDefer, - jQuery.isFunction( onRejected ) ? - onRejected : - Thrower - ) - ); - } ).promise(); - }, - - // Get a promise for this deferred - // If obj is provided, the promise aspect is added to the object - promise: function( obj ) { - return obj != null ? jQuery.extend( obj, promise ) : promise; - } - }, - deferred = {}; - - // Add list-specific methods - jQuery.each( tuples, function( i, tuple ) { - var list = tuple[ 2 ], - stateString = tuple[ 5 ]; - - // promise.progress = list.add - // promise.done = list.add - // promise.fail = list.add - promise[ tuple[ 1 ] ] = list.add; - - // Handle state - if ( stateString ) { - list.add( - function() { - - // state = "resolved" (i.e., fulfilled) - // state = "rejected" - state = stateString; - }, - - // rejected_callbacks.disable - // fulfilled_callbacks.disable - tuples[ 3 - i ][ 2 ].disable, - - // progress_callbacks.lock - tuples[ 0 ][ 2 ].lock - ); - } - - // progress_handlers.fire - // fulfilled_handlers.fire - // rejected_handlers.fire - list.add( tuple[ 3 ].fire ); - - // deferred.notify = function() { deferred.notifyWith(...) } - // deferred.resolve = function() { deferred.resolveWith(...) } - // deferred.reject = function() { deferred.rejectWith(...) } - deferred[ tuple[ 0 ] ] = function() { - deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); - return this; - }; - - // deferred.notifyWith = list.fireWith - // deferred.resolveWith = list.fireWith - // deferred.rejectWith = list.fireWith - deferred[ tuple[ 0 ] + "With" ] = list.fireWith; - } ); - - // Make the deferred a promise - promise.promise( deferred ); - - // Call given func if any - if ( func ) { - func.call( deferred, deferred ); - } - - // All done! - return deferred; - }, - - // Deferred helper - when: function( singleValue ) { - var - - // count of uncompleted subordinates - remaining = arguments.length, - - // count of unprocessed arguments - i = remaining, - - // subordinate fulfillment data - resolveContexts = Array( i ), - resolveValues = slice.call( arguments ), - - // the master Deferred - master = jQuery.Deferred(), - - // subordinate callback factory - updateFunc = function( i ) { - return function( value ) { - resolveContexts[ i ] = this; - resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; - if ( !( --remaining ) ) { - master.resolveWith( resolveContexts, resolveValues ); - } - }; - }; - - // Single- and empty arguments are adopted like Promise.resolve - if ( remaining <= 1 ) { - adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject, - !remaining ); - - // Use .then() to unwrap secondary thenables (cf. gh-3000) - if ( master.state() === "pending" || - jQuery.isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { - - return master.then(); - } - } - - // Multiple arguments are aggregated like Promise.all array elements - while ( i-- ) { - adoptValue( resolveValues[ i ], updateFunc( i ), master.reject ); - } - - return master.promise(); - } -} ); - - -// These usually indicate a programmer mistake during development, -// warn about them ASAP rather than swallowing them by default. -var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; - -jQuery.Deferred.exceptionHook = function( error, stack ) { - - // Support: IE 8 - 9 only - // Console exists when dev tools are open, which can happen at any time - if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { - window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack ); - } -}; - - - - -jQuery.readyException = function( error ) { - window.setTimeout( function() { - throw error; - } ); -}; - - - - -// The deferred used on DOM ready -var readyList = jQuery.Deferred(); - -jQuery.fn.ready = function( fn ) { - - readyList - .then( fn ) - - // Wrap jQuery.readyException in a function so that the lookup - // happens at the time of error handling instead of callback - // registration. - .catch( function( error ) { - jQuery.readyException( error ); - } ); - - return this; -}; - -jQuery.extend( { - - // Is the DOM ready to be used? Set to true once it occurs. - isReady: false, - - // A counter to track how many items to wait for before - // the ready event fires. See #6781 - readyWait: 1, - - // Handle when the DOM is ready - ready: function( wait ) { - - // Abort if there are pending holds or we're already ready - if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { - return; - } - - // Remember that the DOM is ready - jQuery.isReady = true; - - // If a normal DOM Ready event fired, decrement, and wait if need be - if ( wait !== true && --jQuery.readyWait > 0 ) { - return; - } - - // If there are functions bound, to execute - readyList.resolveWith( document, [ jQuery ] ); - } -} ); - -jQuery.ready.then = readyList.then; - -// The ready event handler and self cleanup method -function completed() { - document.removeEventListener( "DOMContentLoaded", completed ); - window.removeEventListener( "load", completed ); - jQuery.ready(); -} - -// Catch cases where $(document).ready() is called -// after the browser event has already occurred. -// Support: IE <=9 - 10 only -// Older IE sometimes signals "interactive" too soon -if ( document.readyState === "complete" || - ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { - - // Handle it asynchronously to allow scripts the opportunity to delay ready - window.setTimeout( jQuery.ready ); - -} else { - - // Use the handy event callback - document.addEventListener( "DOMContentLoaded", completed ); - - // A fallback to window.onload, that will always work - window.addEventListener( "load", completed ); -} - - - - -// Multifunctional method to get and set values of a collection -// The value/s can optionally be executed if it's a function -var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { - var i = 0, - len = elems.length, - bulk = key == null; - - // Sets many values - if ( jQuery.type( key ) === "object" ) { - chainable = true; - for ( i in key ) { - access( elems, fn, i, key[ i ], true, emptyGet, raw ); - } - - // Sets one value - } else if ( value !== undefined ) { - chainable = true; - - if ( !jQuery.isFunction( value ) ) { - raw = true; - } - - if ( bulk ) { - - // Bulk operations run against the entire set - if ( raw ) { - fn.call( elems, value ); - fn = null; - - // ...except when executing function values - } else { - bulk = fn; - fn = function( elem, key, value ) { - return bulk.call( jQuery( elem ), value ); - }; - } - } - - if ( fn ) { - for ( ; i < len; i++ ) { - fn( - elems[ i ], key, raw ? - value : - value.call( elems[ i ], i, fn( elems[ i ], key ) ) - ); - } - } - } - - if ( chainable ) { - return elems; - } - - // Gets - if ( bulk ) { - return fn.call( elems ); - } - - return len ? fn( elems[ 0 ], key ) : emptyGet; -}; -var acceptData = function( owner ) { - - // Accepts only: - // - Node - // - Node.ELEMENT_NODE - // - Node.DOCUMENT_NODE - // - Object - // - Any - return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); -}; - - - - -function Data() { - this.expando = jQuery.expando + Data.uid++; -} - -Data.uid = 1; - -Data.prototype = { - - cache: function( owner ) { - - // Check if the owner object already has a cache - var value = owner[ this.expando ]; - - // If not, create one - if ( !value ) { - value = {}; - - // We can accept data for non-element nodes in modern browsers, - // but we should not, see #8335. - // Always return an empty object. - if ( acceptData( owner ) ) { - - // If it is a node unlikely to be stringify-ed or looped over - // use plain assignment - if ( owner.nodeType ) { - owner[ this.expando ] = value; - - // Otherwise secure it in a non-enumerable property - // configurable must be true to allow the property to be - // deleted when data is removed - } else { - Object.defineProperty( owner, this.expando, { - value: value, - configurable: true - } ); - } - } - } - - return value; - }, - set: function( owner, data, value ) { - var prop, - cache = this.cache( owner ); - - // Handle: [ owner, key, value ] args - // Always use camelCase key (gh-2257) - if ( typeof data === "string" ) { - cache[ jQuery.camelCase( data ) ] = value; - - // Handle: [ owner, { properties } ] args - } else { - - // Copy the properties one-by-one to the cache object - for ( prop in data ) { - cache[ jQuery.camelCase( prop ) ] = data[ prop ]; - } - } - return cache; - }, - get: function( owner, key ) { - return key === undefined ? - this.cache( owner ) : - - // Always use camelCase key (gh-2257) - owner[ this.expando ] && owner[ this.expando ][ jQuery.camelCase( key ) ]; - }, - access: function( owner, key, value ) { - - // In cases where either: - // - // 1. No key was specified - // 2. A string key was specified, but no value provided - // - // Take the "read" path and allow the get method to determine - // which value to return, respectively either: - // - // 1. The entire cache object - // 2. The data stored at the key - // - if ( key === undefined || - ( ( key && typeof key === "string" ) && value === undefined ) ) { - - return this.get( owner, key ); - } - - // When the key is not a string, or both a key and value - // are specified, set or extend (existing objects) with either: - // - // 1. An object of properties - // 2. A key and value - // - this.set( owner, key, value ); - - // Since the "set" path can have two possible entry points - // return the expected data based on which path was taken[*] - return value !== undefined ? value : key; - }, - remove: function( owner, key ) { - var i, - cache = owner[ this.expando ]; - - if ( cache === undefined ) { - return; - } - - if ( key !== undefined ) { - - // Support array or space separated string of keys - if ( Array.isArray( key ) ) { - - // If key is an array of keys... - // We always set camelCase keys, so remove that. - key = key.map( jQuery.camelCase ); - } else { - key = jQuery.camelCase( key ); - - // If a key with the spaces exists, use it. - // Otherwise, create an array by matching non-whitespace - key = key in cache ? - [ key ] : - ( key.match( rnothtmlwhite ) || [] ); - } - - i = key.length; - - while ( i-- ) { - delete cache[ key[ i ] ]; - } - } - - // Remove the expando if there's no more data - if ( key === undefined || jQuery.isEmptyObject( cache ) ) { - - // Support: Chrome <=35 - 45 - // Webkit & Blink performance suffers when deleting properties - // from DOM nodes, so set to undefined instead - // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted) - if ( owner.nodeType ) { - owner[ this.expando ] = undefined; - } else { - delete owner[ this.expando ]; - } - } - }, - hasData: function( owner ) { - var cache = owner[ this.expando ]; - return cache !== undefined && !jQuery.isEmptyObject( cache ); - } -}; -var dataPriv = new Data(); - -var dataUser = new Data(); - - - -// Implementation Summary -// -// 1. Enforce API surface and semantic compatibility with 1.9.x branch -// 2. Improve the module's maintainability by reducing the storage -// paths to a single mechanism. -// 3. Use the same single mechanism to support "private" and "user" data. -// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) -// 5. Avoid exposing implementation details on user objects (eg. expando properties) -// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 - -var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, - rmultiDash = /[A-Z]/g; - -function getData( data ) { - if ( data === "true" ) { - return true; - } - - if ( data === "false" ) { - return false; - } - - if ( data === "null" ) { - return null; - } - - // Only convert to a number if it doesn't change the string - if ( data === +data + "" ) { - return +data; - } - - if ( rbrace.test( data ) ) { - return JSON.parse( data ); - } - - return data; -} - -function dataAttr( elem, key, data ) { - var name; - - // If nothing was found internally, try to fetch any - // data from the HTML5 data-* attribute - if ( data === undefined && elem.nodeType === 1 ) { - name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); - data = elem.getAttribute( name ); - - if ( typeof data === "string" ) { - try { - data = getData( data ); - } catch ( e ) {} - - // Make sure we set the data so it isn't changed later - dataUser.set( elem, key, data ); - } else { - data = undefined; - } - } - return data; -} - -jQuery.extend( { - hasData: function( elem ) { - return dataUser.hasData( elem ) || dataPriv.hasData( elem ); - }, - - data: function( elem, name, data ) { - return dataUser.access( elem, name, data ); - }, - - removeData: function( elem, name ) { - dataUser.remove( elem, name ); - }, - - // TODO: Now that all calls to _data and _removeData have been replaced - // with direct calls to dataPriv methods, these can be deprecated. - _data: function( elem, name, data ) { - return dataPriv.access( elem, name, data ); - }, - - _removeData: function( elem, name ) { - dataPriv.remove( elem, name ); - } -} ); - -jQuery.fn.extend( { - data: function( key, value ) { - var i, name, data, - elem = this[ 0 ], - attrs = elem && elem.attributes; - - // Gets all values - if ( key === undefined ) { - if ( this.length ) { - data = dataUser.get( elem ); - - if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { - i = attrs.length; - while ( i-- ) { - - // Support: IE 11 only - // The attrs elements can be null (#14894) - if ( attrs[ i ] ) { - name = attrs[ i ].name; - if ( name.indexOf( "data-" ) === 0 ) { - name = jQuery.camelCase( name.slice( 5 ) ); - dataAttr( elem, name, data[ name ] ); - } - } - } - dataPriv.set( elem, "hasDataAttrs", true ); - } - } - - return data; - } - - // Sets multiple values - if ( typeof key === "object" ) { - return this.each( function() { - dataUser.set( this, key ); - } ); - } - - return access( this, function( value ) { - var data; - - // The calling jQuery object (element matches) is not empty - // (and therefore has an element appears at this[ 0 ]) and the - // `value` parameter was not undefined. An empty jQuery object - // will result in `undefined` for elem = this[ 0 ] which will - // throw an exception if an attempt to read a data cache is made. - if ( elem && value === undefined ) { - - // Attempt to get data from the cache - // The key will always be camelCased in Data - data = dataUser.get( elem, key ); - if ( data !== undefined ) { - return data; - } - - // Attempt to "discover" the data in - // HTML5 custom data-* attrs - data = dataAttr( elem, key ); - if ( data !== undefined ) { - return data; - } - - // We tried really hard, but the data doesn't exist. - return; - } - - // Set the data... - this.each( function() { - - // We always store the camelCased key - dataUser.set( this, key, value ); - } ); - }, null, value, arguments.length > 1, null, true ); - }, - - removeData: function( key ) { - return this.each( function() { - dataUser.remove( this, key ); - } ); - } -} ); - - -jQuery.extend( { - queue: function( elem, type, data ) { - var queue; - - if ( elem ) { - type = ( type || "fx" ) + "queue"; - queue = dataPriv.get( elem, type ); - - // Speed up dequeue by getting out quickly if this is just a lookup - if ( data ) { - if ( !queue || Array.isArray( data ) ) { - queue = dataPriv.access( elem, type, jQuery.makeArray( data ) ); - } else { - queue.push( data ); - } - } - return queue || []; - } - }, - - dequeue: function( elem, type ) { - type = type || "fx"; - - var queue = jQuery.queue( elem, type ), - startLength = queue.length, - fn = queue.shift(), - hooks = jQuery._queueHooks( elem, type ), - next = function() { - jQuery.dequeue( elem, type ); - }; - - // If the fx queue is dequeued, always remove the progress sentinel - if ( fn === "inprogress" ) { - fn = queue.shift(); - startLength--; - } - - if ( fn ) { - - // Add a progress sentinel to prevent the fx queue from being - // automatically dequeued - if ( type === "fx" ) { - queue.unshift( "inprogress" ); - } - - // Clear up the last queue stop function - delete hooks.stop; - fn.call( elem, next, hooks ); - } - - if ( !startLength && hooks ) { - hooks.empty.fire(); - } - }, - - // Not public - generate a queueHooks object, or return the current one - _queueHooks: function( elem, type ) { - var key = type + "queueHooks"; - return dataPriv.get( elem, key ) || dataPriv.access( elem, key, { - empty: jQuery.Callbacks( "once memory" ).add( function() { - dataPriv.remove( elem, [ type + "queue", key ] ); - } ) - } ); - } -} ); - -jQuery.fn.extend( { - queue: function( type, data ) { - var setter = 2; - - if ( typeof type !== "string" ) { - data = type; - type = "fx"; - setter--; - } - - if ( arguments.length < setter ) { - return jQuery.queue( this[ 0 ], type ); - } - - return data === undefined ? - this : - this.each( function() { - var queue = jQuery.queue( this, type, data ); - - // Ensure a hooks for this queue - jQuery._queueHooks( this, type ); - - if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { - jQuery.dequeue( this, type ); - } - } ); - }, - dequeue: function( type ) { - return this.each( function() { - jQuery.dequeue( this, type ); - } ); - }, - clearQueue: function( type ) { - return this.queue( type || "fx", [] ); - }, - - // Get a promise resolved when queues of a certain type - // are emptied (fx is the type by default) - promise: function( type, obj ) { - var tmp, - count = 1, - defer = jQuery.Deferred(), - elements = this, - i = this.length, - resolve = function() { - if ( !( --count ) ) { - defer.resolveWith( elements, [ elements ] ); - } - }; - - if ( typeof type !== "string" ) { - obj = type; - type = undefined; - } - type = type || "fx"; - - while ( i-- ) { - tmp = dataPriv.get( elements[ i ], type + "queueHooks" ); - if ( tmp && tmp.empty ) { - count++; - tmp.empty.add( resolve ); - } - } - resolve(); - return defer.promise( obj ); - } -} ); -var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; - -var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); - - -var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; - -var isHiddenWithinTree = function( elem, el ) { - - // isHiddenWithinTree might be called from jQuery#filter function; - // in that case, element will be second argument - elem = el || elem; - - // Inline style trumps all - return elem.style.display === "none" || - elem.style.display === "" && - - // Otherwise, check computed style - // Support: Firefox <=43 - 45 - // Disconnected elements can have computed display: none, so first confirm that elem is - // in the document. - jQuery.contains( elem.ownerDocument, elem ) && - - jQuery.css( elem, "display" ) === "none"; - }; - -var swap = function( elem, options, callback, args ) { - var ret, name, - old = {}; - - // Remember the old values, and insert the new ones - for ( name in options ) { - old[ name ] = elem.style[ name ]; - elem.style[ name ] = options[ name ]; - } - - ret = callback.apply( elem, args || [] ); - - // Revert the old values - for ( name in options ) { - elem.style[ name ] = old[ name ]; - } - - return ret; -}; - - - - -function adjustCSS( elem, prop, valueParts, tween ) { - var adjusted, - scale = 1, - maxIterations = 20, - currentValue = tween ? - function() { - return tween.cur(); - } : - function() { - return jQuery.css( elem, prop, "" ); - }, - initial = currentValue(), - unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), - - // Starting value computation is required for potential unit mismatches - initialInUnit = ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && - rcssNum.exec( jQuery.css( elem, prop ) ); - - if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { - - // Trust units reported by jQuery.css - unit = unit || initialInUnit[ 3 ]; - - // Make sure we update the tween properties later on - valueParts = valueParts || []; - - // Iteratively approximate from a nonzero starting point - initialInUnit = +initial || 1; - - do { - - // If previous iteration zeroed out, double until we get *something*. - // Use string for doubling so we don't accidentally see scale as unchanged below - scale = scale || ".5"; - - // Adjust and apply - initialInUnit = initialInUnit / scale; - jQuery.style( elem, prop, initialInUnit + unit ); - - // Update scale, tolerating zero or NaN from tween.cur() - // Break the loop if scale is unchanged or perfect, or if we've just had enough. - } while ( - scale !== ( scale = currentValue() / initial ) && scale !== 1 && --maxIterations - ); - } - - if ( valueParts ) { - initialInUnit = +initialInUnit || +initial || 0; - - // Apply relative offset (+=/-=) if specified - adjusted = valueParts[ 1 ] ? - initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : - +valueParts[ 2 ]; - if ( tween ) { - tween.unit = unit; - tween.start = initialInUnit; - tween.end = adjusted; - } - } - return adjusted; -} - - -var defaultDisplayMap = {}; - -function getDefaultDisplay( elem ) { - var temp, - doc = elem.ownerDocument, - nodeName = elem.nodeName, - display = defaultDisplayMap[ nodeName ]; - - if ( display ) { - return display; - } - - temp = doc.body.appendChild( doc.createElement( nodeName ) ); - display = jQuery.css( temp, "display" ); - - temp.parentNode.removeChild( temp ); - - if ( display === "none" ) { - display = "block"; - } - defaultDisplayMap[ nodeName ] = display; - - return display; -} - -function showHide( elements, show ) { - var display, elem, - values = [], - index = 0, - length = elements.length; - - // Determine new display value for elements that need to change - for ( ; index < length; index++ ) { - elem = elements[ index ]; - if ( !elem.style ) { - continue; - } - - display = elem.style.display; - if ( show ) { - - // Since we force visibility upon cascade-hidden elements, an immediate (and slow) - // check is required in this first loop unless we have a nonempty display value (either - // inline or about-to-be-restored) - if ( display === "none" ) { - values[ index ] = dataPriv.get( elem, "display" ) || null; - if ( !values[ index ] ) { - elem.style.display = ""; - } - } - if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) { - values[ index ] = getDefaultDisplay( elem ); - } - } else { - if ( display !== "none" ) { - values[ index ] = "none"; - - // Remember what we're overwriting - dataPriv.set( elem, "display", display ); - } - } - } - - // Set the display of the elements in a second loop to avoid constant reflow - for ( index = 0; index < length; index++ ) { - if ( values[ index ] != null ) { - elements[ index ].style.display = values[ index ]; - } - } - - return elements; -} - -jQuery.fn.extend( { - show: function() { - return showHide( this, true ); - }, - hide: function() { - return showHide( this ); - }, - toggle: function( state ) { - if ( typeof state === "boolean" ) { - return state ? this.show() : this.hide(); - } - - return this.each( function() { - if ( isHiddenWithinTree( this ) ) { - jQuery( this ).show(); - } else { - jQuery( this ).hide(); - } - } ); - } -} ); -var rcheckableType = ( /^(?:checkbox|radio)$/i ); - -var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]+)/i ); - -var rscriptType = ( /^$|\/(?:java|ecma)script/i ); - - - -// We have to close these tags to support XHTML (#13200) -var wrapMap = { - - // Support: IE <=9 only - option: [ 1, "" ], - - // XHTML parsers do not magically insert elements in the - // same way that tag soup parsers do. So we cannot shorten - // this by omitting or other required elements. - thead: [ 1, "", "
" ], - col: [ 2, "", "
" ], - tr: [ 2, "", "
" ], - td: [ 3, "", "
" ], - - _default: [ 0, "", "" ] -}; - -// Support: IE <=9 only -wrapMap.optgroup = wrapMap.option; - -wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; -wrapMap.th = wrapMap.td; - - -function getAll( context, tag ) { - - // Support: IE <=9 - 11 only - // Use typeof to avoid zero-argument method invocation on host objects (#15151) - var ret; - - if ( typeof context.getElementsByTagName !== "undefined" ) { - ret = context.getElementsByTagName( tag || "*" ); - - } else if ( typeof context.querySelectorAll !== "undefined" ) { - ret = context.querySelectorAll( tag || "*" ); - - } else { - ret = []; - } - - if ( tag === undefined || tag && nodeName( context, tag ) ) { - return jQuery.merge( [ context ], ret ); - } - - return ret; -} - - -// Mark scripts as having already been evaluated -function setGlobalEval( elems, refElements ) { - var i = 0, - l = elems.length; - - for ( ; i < l; i++ ) { - dataPriv.set( - elems[ i ], - "globalEval", - !refElements || dataPriv.get( refElements[ i ], "globalEval" ) - ); - } -} - - -var rhtml = /<|&#?\w+;/; - -function buildFragment( elems, context, scripts, selection, ignored ) { - var elem, tmp, tag, wrap, contains, j, - fragment = context.createDocumentFragment(), - nodes = [], - i = 0, - l = elems.length; - - for ( ; i < l; i++ ) { - elem = elems[ i ]; - - if ( elem || elem === 0 ) { - - // Add nodes directly - if ( jQuery.type( elem ) === "object" ) { - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); - - // Convert non-html into a text node - } else if ( !rhtml.test( elem ) ) { - nodes.push( context.createTextNode( elem ) ); - - // Convert html into DOM nodes - } else { - tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); - - // Deserialize a standard representation - tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); - wrap = wrapMap[ tag ] || wrapMap._default; - tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; - - // Descend through wrappers to the right content - j = wrap[ 0 ]; - while ( j-- ) { - tmp = tmp.lastChild; - } - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - jQuery.merge( nodes, tmp.childNodes ); - - // Remember the top-level container - tmp = fragment.firstChild; - - // Ensure the created nodes are orphaned (#12392) - tmp.textContent = ""; - } - } - } - - // Remove wrapper from fragment - fragment.textContent = ""; - - i = 0; - while ( ( elem = nodes[ i++ ] ) ) { - - // Skip elements already in the context collection (trac-4087) - if ( selection && jQuery.inArray( elem, selection ) > -1 ) { - if ( ignored ) { - ignored.push( elem ); - } - continue; - } - - contains = jQuery.contains( elem.ownerDocument, elem ); - - // Append to fragment - tmp = getAll( fragment.appendChild( elem ), "script" ); - - // Preserve script evaluation history - if ( contains ) { - setGlobalEval( tmp ); - } - - // Capture executables - if ( scripts ) { - j = 0; - while ( ( elem = tmp[ j++ ] ) ) { - if ( rscriptType.test( elem.type || "" ) ) { - scripts.push( elem ); - } - } - } - } - - return fragment; -} - - -( function() { - var fragment = document.createDocumentFragment(), - div = fragment.appendChild( document.createElement( "div" ) ), - input = document.createElement( "input" ); - - // Support: Android 4.0 - 4.3 only - // Check state lost if the name is set (#11217) - // Support: Windows Web Apps (WWA) - // `name` and `type` must use .setAttribute for WWA (#14901) - input.setAttribute( "type", "radio" ); - input.setAttribute( "checked", "checked" ); - input.setAttribute( "name", "t" ); - - div.appendChild( input ); - - // Support: Android <=4.1 only - // Older WebKit doesn't clone checked state correctly in fragments - support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; - - // Support: IE <=11 only - // Make sure textarea (and checkbox) defaultValue is properly cloned - div.innerHTML = ""; - support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; -} )(); -var documentElement = document.documentElement; - - - -var - rkeyEvent = /^key/, - rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/, - rtypenamespace = /^([^.]*)(?:\.(.+)|)/; - -function returnTrue() { - return true; -} - -function returnFalse() { - return false; -} - -// Support: IE <=9 only -// See #13393 for more info -function safeActiveElement() { - try { - return document.activeElement; - } catch ( err ) { } -} - -function on( elem, types, selector, data, fn, one ) { - var origFn, type; - - // Types can be a map of types/handlers - if ( typeof types === "object" ) { - - // ( types-Object, selector, data ) - if ( typeof selector !== "string" ) { - - // ( types-Object, data ) - data = data || selector; - selector = undefined; - } - for ( type in types ) { - on( elem, type, selector, data, types[ type ], one ); - } - return elem; - } - - if ( data == null && fn == null ) { - - // ( types, fn ) - fn = selector; - data = selector = undefined; - } else if ( fn == null ) { - if ( typeof selector === "string" ) { - - // ( types, selector, fn ) - fn = data; - data = undefined; - } else { - - // ( types, data, fn ) - fn = data; - data = selector; - selector = undefined; - } - } - if ( fn === false ) { - fn = returnFalse; - } else if ( !fn ) { - return elem; - } - - if ( one === 1 ) { - origFn = fn; - fn = function( event ) { - - // Can use an empty set, since event contains the info - jQuery().off( event ); - return origFn.apply( this, arguments ); - }; - - // Use same guid so caller can remove using origFn - fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); - } - return elem.each( function() { - jQuery.event.add( this, types, fn, data, selector ); - } ); -} - -/* - * Helper functions for managing events -- not part of the public interface. - * Props to Dean Edwards' addEvent library for many of the ideas. - */ -jQuery.event = { - - global: {}, - - add: function( elem, types, handler, data, selector ) { - - var handleObjIn, eventHandle, tmp, - events, t, handleObj, - special, handlers, type, namespaces, origType, - elemData = dataPriv.get( elem ); - - // Don't attach events to noData or text/comment nodes (but allow plain objects) - if ( !elemData ) { - return; - } - - // Caller can pass in an object of custom data in lieu of the handler - if ( handler.handler ) { - handleObjIn = handler; - handler = handleObjIn.handler; - selector = handleObjIn.selector; - } - - // Ensure that invalid selectors throw exceptions at attach time - // Evaluate against documentElement in case elem is a non-element node (e.g., document) - if ( selector ) { - jQuery.find.matchesSelector( documentElement, selector ); - } - - // Make sure that the handler has a unique ID, used to find/remove it later - if ( !handler.guid ) { - handler.guid = jQuery.guid++; - } - - // Init the element's event structure and main handler, if this is the first - if ( !( events = elemData.events ) ) { - events = elemData.events = {}; - } - if ( !( eventHandle = elemData.handle ) ) { - eventHandle = elemData.handle = function( e ) { - - // Discard the second event of a jQuery.event.trigger() and - // when an event is called after a page has unloaded - return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? - jQuery.event.dispatch.apply( elem, arguments ) : undefined; - }; - } - - // Handle multiple events separated by a space - types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; - t = types.length; - while ( t-- ) { - tmp = rtypenamespace.exec( types[ t ] ) || []; - type = origType = tmp[ 1 ]; - namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); - - // There *must* be a type, no attaching namespace-only handlers - if ( !type ) { - continue; - } - - // If event changes its type, use the special event handlers for the changed type - special = jQuery.event.special[ type ] || {}; - - // If selector defined, determine special event api type, otherwise given type - type = ( selector ? special.delegateType : special.bindType ) || type; - - // Update special based on newly reset type - special = jQuery.event.special[ type ] || {}; - - // handleObj is passed to all event handlers - handleObj = jQuery.extend( { - type: type, - origType: origType, - data: data, - handler: handler, - guid: handler.guid, - selector: selector, - needsContext: selector && jQuery.expr.match.needsContext.test( selector ), - namespace: namespaces.join( "." ) - }, handleObjIn ); - - // Init the event handler queue if we're the first - if ( !( handlers = events[ type ] ) ) { - handlers = events[ type ] = []; - handlers.delegateCount = 0; - - // Only use addEventListener if the special events handler returns false - if ( !special.setup || - special.setup.call( elem, data, namespaces, eventHandle ) === false ) { - - if ( elem.addEventListener ) { - elem.addEventListener( type, eventHandle ); - } - } - } - - if ( special.add ) { - special.add.call( elem, handleObj ); - - if ( !handleObj.handler.guid ) { - handleObj.handler.guid = handler.guid; - } - } - - // Add to the element's handler list, delegates in front - if ( selector ) { - handlers.splice( handlers.delegateCount++, 0, handleObj ); - } else { - handlers.push( handleObj ); - } - - // Keep track of which events have ever been used, for event optimization - jQuery.event.global[ type ] = true; - } - - }, - - // Detach an event or set of events from an element - remove: function( elem, types, handler, selector, mappedTypes ) { - - var j, origCount, tmp, - events, t, handleObj, - special, handlers, type, namespaces, origType, - elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); - - if ( !elemData || !( events = elemData.events ) ) { - return; - } - - // Once for each type.namespace in types; type may be omitted - types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; - t = types.length; - while ( t-- ) { - tmp = rtypenamespace.exec( types[ t ] ) || []; - type = origType = tmp[ 1 ]; - namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); - - // Unbind all events (on this namespace, if provided) for the element - if ( !type ) { - for ( type in events ) { - jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); - } - continue; - } - - special = jQuery.event.special[ type ] || {}; - type = ( selector ? special.delegateType : special.bindType ) || type; - handlers = events[ type ] || []; - tmp = tmp[ 2 ] && - new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); - - // Remove matching events - origCount = j = handlers.length; - while ( j-- ) { - handleObj = handlers[ j ]; - - if ( ( mappedTypes || origType === handleObj.origType ) && - ( !handler || handler.guid === handleObj.guid ) && - ( !tmp || tmp.test( handleObj.namespace ) ) && - ( !selector || selector === handleObj.selector || - selector === "**" && handleObj.selector ) ) { - handlers.splice( j, 1 ); - - if ( handleObj.selector ) { - handlers.delegateCount--; - } - if ( special.remove ) { - special.remove.call( elem, handleObj ); - } - } - } - - // Remove generic event handler if we removed something and no more handlers exist - // (avoids potential for endless recursion during removal of special event handlers) - if ( origCount && !handlers.length ) { - if ( !special.teardown || - special.teardown.call( elem, namespaces, elemData.handle ) === false ) { - - jQuery.removeEvent( elem, type, elemData.handle ); - } - - delete events[ type ]; - } - } - - // Remove data and the expando if it's no longer used - if ( jQuery.isEmptyObject( events ) ) { - dataPriv.remove( elem, "handle events" ); - } - }, - - dispatch: function( nativeEvent ) { - - // Make a writable jQuery.Event from the native event object - var event = jQuery.event.fix( nativeEvent ); - - var i, j, ret, matched, handleObj, handlerQueue, - args = new Array( arguments.length ), - handlers = ( dataPriv.get( this, "events" ) || {} )[ event.type ] || [], - special = jQuery.event.special[ event.type ] || {}; - - // Use the fix-ed jQuery.Event rather than the (read-only) native event - args[ 0 ] = event; - - for ( i = 1; i < arguments.length; i++ ) { - args[ i ] = arguments[ i ]; - } - - event.delegateTarget = this; - - // Call the preDispatch hook for the mapped type, and let it bail if desired - if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { - return; - } - - // Determine handlers - handlerQueue = jQuery.event.handlers.call( this, event, handlers ); - - // Run delegates first; they may want to stop propagation beneath us - i = 0; - while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { - event.currentTarget = matched.elem; - - j = 0; - while ( ( handleObj = matched.handlers[ j++ ] ) && - !event.isImmediatePropagationStopped() ) { - - // Triggered event must either 1) have no namespace, or 2) have namespace(s) - // a subset or equal to those in the bound event (both can have no namespace). - if ( !event.rnamespace || event.rnamespace.test( handleObj.namespace ) ) { - - event.handleObj = handleObj; - event.data = handleObj.data; - - ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || - handleObj.handler ).apply( matched.elem, args ); - - if ( ret !== undefined ) { - if ( ( event.result = ret ) === false ) { - event.preventDefault(); - event.stopPropagation(); - } - } - } - } - } - - // Call the postDispatch hook for the mapped type - if ( special.postDispatch ) { - special.postDispatch.call( this, event ); - } - - return event.result; - }, - - handlers: function( event, handlers ) { - var i, handleObj, sel, matchedHandlers, matchedSelectors, - handlerQueue = [], - delegateCount = handlers.delegateCount, - cur = event.target; - - // Find delegate handlers - if ( delegateCount && - - // Support: IE <=9 - // Black-hole SVG instance trees (trac-13180) - cur.nodeType && - - // Support: Firefox <=42 - // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) - // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click - // Support: IE 11 only - // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) - !( event.type === "click" && event.button >= 1 ) ) { - - for ( ; cur !== this; cur = cur.parentNode || this ) { - - // Don't check non-elements (#13208) - // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) - if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { - matchedHandlers = []; - matchedSelectors = {}; - for ( i = 0; i < delegateCount; i++ ) { - handleObj = handlers[ i ]; - - // Don't conflict with Object.prototype properties (#13203) - sel = handleObj.selector + " "; - - if ( matchedSelectors[ sel ] === undefined ) { - matchedSelectors[ sel ] = handleObj.needsContext ? - jQuery( sel, this ).index( cur ) > -1 : - jQuery.find( sel, this, null, [ cur ] ).length; - } - if ( matchedSelectors[ sel ] ) { - matchedHandlers.push( handleObj ); - } - } - if ( matchedHandlers.length ) { - handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); - } - } - } - } - - // Add the remaining (directly-bound) handlers - cur = this; - if ( delegateCount < handlers.length ) { - handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); - } - - return handlerQueue; - }, - - addProp: function( name, hook ) { - Object.defineProperty( jQuery.Event.prototype, name, { - enumerable: true, - configurable: true, - - get: jQuery.isFunction( hook ) ? - function() { - if ( this.originalEvent ) { - return hook( this.originalEvent ); - } - } : - function() { - if ( this.originalEvent ) { - return this.originalEvent[ name ]; - } - }, - - set: function( value ) { - Object.defineProperty( this, name, { - enumerable: true, - configurable: true, - writable: true, - value: value - } ); - } - } ); - }, - - fix: function( originalEvent ) { - return originalEvent[ jQuery.expando ] ? - originalEvent : - new jQuery.Event( originalEvent ); - }, - - special: { - load: { - - // Prevent triggered image.load events from bubbling to window.load - noBubble: true - }, - focus: { - - // Fire native event if possible so blur/focus sequence is correct - trigger: function() { - if ( this !== safeActiveElement() && this.focus ) { - this.focus(); - return false; - } - }, - delegateType: "focusin" - }, - blur: { - trigger: function() { - if ( this === safeActiveElement() && this.blur ) { - this.blur(); - return false; - } - }, - delegateType: "focusout" - }, - click: { - - // For checkbox, fire native event so checked state will be right - trigger: function() { - if ( this.type === "checkbox" && this.click && nodeName( this, "input" ) ) { - this.click(); - return false; - } - }, - - // For cross-browser consistency, don't fire native .click() on links - _default: function( event ) { - return nodeName( event.target, "a" ); - } - }, - - beforeunload: { - postDispatch: function( event ) { - - // Support: Firefox 20+ - // Firefox doesn't alert if the returnValue field is not set. - if ( event.result !== undefined && event.originalEvent ) { - event.originalEvent.returnValue = event.result; - } - } - } - } -}; - -jQuery.removeEvent = function( elem, type, handle ) { - - // This "if" is needed for plain objects - if ( elem.removeEventListener ) { - elem.removeEventListener( type, handle ); - } -}; - -jQuery.Event = function( src, props ) { - - // Allow instantiation without the 'new' keyword - if ( !( this instanceof jQuery.Event ) ) { - return new jQuery.Event( src, props ); - } - - // Event object - if ( src && src.type ) { - this.originalEvent = src; - this.type = src.type; - - // Events bubbling up the document may have been marked as prevented - // by a handler lower down the tree; reflect the correct value. - this.isDefaultPrevented = src.defaultPrevented || - src.defaultPrevented === undefined && - - // Support: Android <=2.3 only - src.returnValue === false ? - returnTrue : - returnFalse; - - // Create target properties - // Support: Safari <=6 - 7 only - // Target should not be a text node (#504, #13143) - this.target = ( src.target && src.target.nodeType === 3 ) ? - src.target.parentNode : - src.target; - - this.currentTarget = src.currentTarget; - this.relatedTarget = src.relatedTarget; - - // Event type - } else { - this.type = src; - } - - // Put explicitly provided properties onto the event object - if ( props ) { - jQuery.extend( this, props ); - } - - // Create a timestamp if incoming event doesn't have one - this.timeStamp = src && src.timeStamp || jQuery.now(); - - // Mark it as fixed - this[ jQuery.expando ] = true; -}; - -// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding -// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html -jQuery.Event.prototype = { - constructor: jQuery.Event, - isDefaultPrevented: returnFalse, - isPropagationStopped: returnFalse, - isImmediatePropagationStopped: returnFalse, - isSimulated: false, - - preventDefault: function() { - var e = this.originalEvent; - - this.isDefaultPrevented = returnTrue; - - if ( e && !this.isSimulated ) { - e.preventDefault(); - } - }, - stopPropagation: function() { - var e = this.originalEvent; - - this.isPropagationStopped = returnTrue; - - if ( e && !this.isSimulated ) { - e.stopPropagation(); - } - }, - stopImmediatePropagation: function() { - var e = this.originalEvent; - - this.isImmediatePropagationStopped = returnTrue; - - if ( e && !this.isSimulated ) { - e.stopImmediatePropagation(); - } - - this.stopPropagation(); - } -}; - -// Includes all common event props including KeyEvent and MouseEvent specific props -jQuery.each( { - altKey: true, - bubbles: true, - cancelable: true, - changedTouches: true, - ctrlKey: true, - detail: true, - eventPhase: true, - metaKey: true, - pageX: true, - pageY: true, - shiftKey: true, - view: true, - "char": true, - charCode: true, - key: true, - keyCode: true, - button: true, - buttons: true, - clientX: true, - clientY: true, - offsetX: true, - offsetY: true, - pointerId: true, - pointerType: true, - screenX: true, - screenY: true, - targetTouches: true, - toElement: true, - touches: true, - - which: function( event ) { - var button = event.button; - - // Add which for key events - if ( event.which == null && rkeyEvent.test( event.type ) ) { - return event.charCode != null ? event.charCode : event.keyCode; - } - - // Add which for click: 1 === left; 2 === middle; 3 === right - if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) { - if ( button & 1 ) { - return 1; - } - - if ( button & 2 ) { - return 3; - } - - if ( button & 4 ) { - return 2; - } - - return 0; - } - - return event.which; - } -}, jQuery.event.addProp ); - -// Create mouseenter/leave events using mouseover/out and event-time checks -// so that event delegation works in jQuery. -// Do the same for pointerenter/pointerleave and pointerover/pointerout -// -// Support: Safari 7 only -// Safari sends mouseenter too often; see: -// https://bugs.chromium.org/p/chromium/issues/detail?id=470258 -// for the description of the bug (it existed in older Chrome versions as well). -jQuery.each( { - mouseenter: "mouseover", - mouseleave: "mouseout", - pointerenter: "pointerover", - pointerleave: "pointerout" -}, function( orig, fix ) { - jQuery.event.special[ orig ] = { - delegateType: fix, - bindType: fix, - - handle: function( event ) { - var ret, - target = this, - related = event.relatedTarget, - handleObj = event.handleObj; - - // For mouseenter/leave call the handler if related is outside the target. - // NB: No relatedTarget if the mouse left/entered the browser window - if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { - event.type = handleObj.origType; - ret = handleObj.handler.apply( this, arguments ); - event.type = fix; - } - return ret; - } - }; -} ); - -jQuery.fn.extend( { - - on: function( types, selector, data, fn ) { - return on( this, types, selector, data, fn ); - }, - one: function( types, selector, data, fn ) { - return on( this, types, selector, data, fn, 1 ); - }, - off: function( types, selector, fn ) { - var handleObj, type; - if ( types && types.preventDefault && types.handleObj ) { - - // ( event ) dispatched jQuery.Event - handleObj = types.handleObj; - jQuery( types.delegateTarget ).off( - handleObj.namespace ? - handleObj.origType + "." + handleObj.namespace : - handleObj.origType, - handleObj.selector, - handleObj.handler - ); - return this; - } - if ( typeof types === "object" ) { - - // ( types-object [, selector] ) - for ( type in types ) { - this.off( type, selector, types[ type ] ); - } - return this; - } - if ( selector === false || typeof selector === "function" ) { - - // ( types [, fn] ) - fn = selector; - selector = undefined; - } - if ( fn === false ) { - fn = returnFalse; - } - return this.each( function() { - jQuery.event.remove( this, types, fn, selector ); - } ); - } -} ); - - -var - - /* eslint-disable max-len */ - - // See https://github.com/eslint/eslint/issues/3229 - rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\/\0>\x20\t\r\n\f]*)[^>]*)\/>/gi, - - /* eslint-enable */ - - // Support: IE <=10 - 11, Edge 12 - 13 - // In IE/Edge using regex groups here causes severe slowdowns. - // See https://connect.microsoft.com/IE/feedback/details/1736512/ - rnoInnerhtml = /\s*$/g; - -// Prefer a tbody over its parent table for containing new rows -function manipulationTarget( elem, content ) { - if ( nodeName( elem, "table" ) && - nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { - - return jQuery( ">tbody", elem )[ 0 ] || elem; - } - - return elem; -} - -// Replace/restore the type attribute of script elements for safe DOM manipulation -function disableScript( elem ) { - elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; - return elem; -} -function restoreScript( elem ) { - var match = rscriptTypeMasked.exec( elem.type ); - - if ( match ) { - elem.type = match[ 1 ]; - } else { - elem.removeAttribute( "type" ); - } - - return elem; -} - -function cloneCopyEvent( src, dest ) { - var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events; - - if ( dest.nodeType !== 1 ) { - return; - } - - // 1. Copy private data: events, handlers, etc. - if ( dataPriv.hasData( src ) ) { - pdataOld = dataPriv.access( src ); - pdataCur = dataPriv.set( dest, pdataOld ); - events = pdataOld.events; - - if ( events ) { - delete pdataCur.handle; - pdataCur.events = {}; - - for ( type in events ) { - for ( i = 0, l = events[ type ].length; i < l; i++ ) { - jQuery.event.add( dest, type, events[ type ][ i ] ); - } - } - } - } - - // 2. Copy user data - if ( dataUser.hasData( src ) ) { - udataOld = dataUser.access( src ); - udataCur = jQuery.extend( {}, udataOld ); - - dataUser.set( dest, udataCur ); - } -} - -// Fix IE bugs, see support tests -function fixInput( src, dest ) { - var nodeName = dest.nodeName.toLowerCase(); - - // Fails to persist the checked state of a cloned checkbox or radio button. - if ( nodeName === "input" && rcheckableType.test( src.type ) ) { - dest.checked = src.checked; - - // Fails to return the selected option to the default selected state when cloning options - } else if ( nodeName === "input" || nodeName === "textarea" ) { - dest.defaultValue = src.defaultValue; - } -} - -function domManip( collection, args, callback, ignored ) { - - // Flatten any nested arrays - args = concat.apply( [], args ); - - var fragment, first, scripts, hasScripts, node, doc, - i = 0, - l = collection.length, - iNoClone = l - 1, - value = args[ 0 ], - isFunction = jQuery.isFunction( value ); - - // We can't cloneNode fragments that contain checked, in WebKit - if ( isFunction || - ( l > 1 && typeof value === "string" && - !support.checkClone && rchecked.test( value ) ) ) { - return collection.each( function( index ) { - var self = collection.eq( index ); - if ( isFunction ) { - args[ 0 ] = value.call( this, index, self.html() ); - } - domManip( self, args, callback, ignored ); - } ); - } - - if ( l ) { - fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); - first = fragment.firstChild; - - if ( fragment.childNodes.length === 1 ) { - fragment = first; - } - - // Require either new content or an interest in ignored elements to invoke the callback - if ( first || ignored ) { - scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); - hasScripts = scripts.length; - - // Use the original fragment for the last item - // instead of the first because it can end up - // being emptied incorrectly in certain situations (#8070). - for ( ; i < l; i++ ) { - node = fragment; - - if ( i !== iNoClone ) { - node = jQuery.clone( node, true, true ); - - // Keep references to cloned scripts for later restoration - if ( hasScripts ) { - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - jQuery.merge( scripts, getAll( node, "script" ) ); - } - } - - callback.call( collection[ i ], node, i ); - } - - if ( hasScripts ) { - doc = scripts[ scripts.length - 1 ].ownerDocument; - - // Reenable scripts - jQuery.map( scripts, restoreScript ); - - // Evaluate executable scripts on first document insertion - for ( i = 0; i < hasScripts; i++ ) { - node = scripts[ i ]; - if ( rscriptType.test( node.type || "" ) && - !dataPriv.access( node, "globalEval" ) && - jQuery.contains( doc, node ) ) { - - if ( node.src ) { - - // Optional AJAX dependency, but won't run scripts if not present - if ( jQuery._evalUrl ) { - jQuery._evalUrl( node.src ); - } - } else { - DOMEval( node.textContent.replace( rcleanScript, "" ), doc ); - } - } - } - } - } - } - - return collection; -} - -function remove( elem, selector, keepData ) { - var node, - nodes = selector ? jQuery.filter( selector, elem ) : elem, - i = 0; - - for ( ; ( node = nodes[ i ] ) != null; i++ ) { - if ( !keepData && node.nodeType === 1 ) { - jQuery.cleanData( getAll( node ) ); - } - - if ( node.parentNode ) { - if ( keepData && jQuery.contains( node.ownerDocument, node ) ) { - setGlobalEval( getAll( node, "script" ) ); - } - node.parentNode.removeChild( node ); - } - } - - return elem; -} - -jQuery.extend( { - htmlPrefilter: function( html ) { - return html.replace( rxhtmlTag, "<$1>" ); - }, - - clone: function( elem, dataAndEvents, deepDataAndEvents ) { - var i, l, srcElements, destElements, - clone = elem.cloneNode( true ), - inPage = jQuery.contains( elem.ownerDocument, elem ); - - // Fix IE cloning issues - if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && - !jQuery.isXMLDoc( elem ) ) { - - // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2 - destElements = getAll( clone ); - srcElements = getAll( elem ); - - for ( i = 0, l = srcElements.length; i < l; i++ ) { - fixInput( srcElements[ i ], destElements[ i ] ); - } - } - - // Copy the events from the original to the clone - if ( dataAndEvents ) { - if ( deepDataAndEvents ) { - srcElements = srcElements || getAll( elem ); - destElements = destElements || getAll( clone ); - - for ( i = 0, l = srcElements.length; i < l; i++ ) { - cloneCopyEvent( srcElements[ i ], destElements[ i ] ); - } - } else { - cloneCopyEvent( elem, clone ); - } - } - - // Preserve script evaluation history - destElements = getAll( clone, "script" ); - if ( destElements.length > 0 ) { - setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); - } - - // Return the cloned set - return clone; - }, - - cleanData: function( elems ) { - var data, elem, type, - special = jQuery.event.special, - i = 0; - - for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { - if ( acceptData( elem ) ) { - if ( ( data = elem[ dataPriv.expando ] ) ) { - if ( data.events ) { - for ( type in data.events ) { - if ( special[ type ] ) { - jQuery.event.remove( elem, type ); - - // This is a shortcut to avoid jQuery.event.remove's overhead - } else { - jQuery.removeEvent( elem, type, data.handle ); - } - } - } - - // Support: Chrome <=35 - 45+ - // Assign undefined instead of using delete, see Data#remove - elem[ dataPriv.expando ] = undefined; - } - if ( elem[ dataUser.expando ] ) { - - // Support: Chrome <=35 - 45+ - // Assign undefined instead of using delete, see Data#remove - elem[ dataUser.expando ] = undefined; - } - } - } - } -} ); - -jQuery.fn.extend( { - detach: function( selector ) { - return remove( this, selector, true ); - }, - - remove: function( selector ) { - return remove( this, selector ); - }, - - text: function( value ) { - return access( this, function( value ) { - return value === undefined ? - jQuery.text( this ) : - this.empty().each( function() { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - this.textContent = value; - } - } ); - }, null, value, arguments.length ); - }, - - append: function() { - return domManip( this, arguments, function( elem ) { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - var target = manipulationTarget( this, elem ); - target.appendChild( elem ); - } - } ); - }, - - prepend: function() { - return domManip( this, arguments, function( elem ) { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - var target = manipulationTarget( this, elem ); - target.insertBefore( elem, target.firstChild ); - } - } ); - }, - - before: function() { - return domManip( this, arguments, function( elem ) { - if ( this.parentNode ) { - this.parentNode.insertBefore( elem, this ); - } - } ); - }, - - after: function() { - return domManip( this, arguments, function( elem ) { - if ( this.parentNode ) { - this.parentNode.insertBefore( elem, this.nextSibling ); - } - } ); - }, - - empty: function() { - var elem, - i = 0; - - for ( ; ( elem = this[ i ] ) != null; i++ ) { - if ( elem.nodeType === 1 ) { - - // Prevent memory leaks - jQuery.cleanData( getAll( elem, false ) ); - - // Remove any remaining nodes - elem.textContent = ""; - } - } - - return this; - }, - - clone: function( dataAndEvents, deepDataAndEvents ) { - dataAndEvents = dataAndEvents == null ? false : dataAndEvents; - deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; - - return this.map( function() { - return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); - } ); - }, - - html: function( value ) { - return access( this, function( value ) { - var elem = this[ 0 ] || {}, - i = 0, - l = this.length; - - if ( value === undefined && elem.nodeType === 1 ) { - return elem.innerHTML; - } - - // See if we can take a shortcut and just use innerHTML - if ( typeof value === "string" && !rnoInnerhtml.test( value ) && - !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { - - value = jQuery.htmlPrefilter( value ); - - try { - for ( ; i < l; i++ ) { - elem = this[ i ] || {}; - - // Remove element nodes and prevent memory leaks - if ( elem.nodeType === 1 ) { - jQuery.cleanData( getAll( elem, false ) ); - elem.innerHTML = value; - } - } - - elem = 0; - - // If using innerHTML throws an exception, use the fallback method - } catch ( e ) {} - } - - if ( elem ) { - this.empty().append( value ); - } - }, null, value, arguments.length ); - }, - - replaceWith: function() { - var ignored = []; - - // Make the changes, replacing each non-ignored context element with the new content - return domManip( this, arguments, function( elem ) { - var parent = this.parentNode; - - if ( jQuery.inArray( this, ignored ) < 0 ) { - jQuery.cleanData( getAll( this ) ); - if ( parent ) { - parent.replaceChild( elem, this ); - } - } - - // Force callback invocation - }, ignored ); - } -} ); - -jQuery.each( { - appendTo: "append", - prependTo: "prepend", - insertBefore: "before", - insertAfter: "after", - replaceAll: "replaceWith" -}, function( name, original ) { - jQuery.fn[ name ] = function( selector ) { - var elems, - ret = [], - insert = jQuery( selector ), - last = insert.length - 1, - i = 0; - - for ( ; i <= last; i++ ) { - elems = i === last ? this : this.clone( true ); - jQuery( insert[ i ] )[ original ]( elems ); - - // Support: Android <=4.0 only, PhantomJS 1 only - // .get() because push.apply(_, arraylike) throws on ancient WebKit - push.apply( ret, elems.get() ); - } - - return this.pushStack( ret ); - }; -} ); -var rmargin = ( /^margin/ ); - -var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); - -var getStyles = function( elem ) { - - // Support: IE <=11 only, Firefox <=30 (#15098, #14150) - // IE throws on elements created in popups - // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" - var view = elem.ownerDocument.defaultView; - - if ( !view || !view.opener ) { - view = window; - } - - return view.getComputedStyle( elem ); - }; - - - -( function() { - - // Executing both pixelPosition & boxSizingReliable tests require only one layout - // so they're executed at the same time to save the second computation. - function computeStyleTests() { - - // This is a singleton, we need to execute it only once - if ( !div ) { - return; - } - - div.style.cssText = - "box-sizing:border-box;" + - "position:relative;display:block;" + - "margin:auto;border:1px;padding:1px;" + - "top:1%;width:50%"; - div.innerHTML = ""; - documentElement.appendChild( container ); - - var divStyle = window.getComputedStyle( div ); - pixelPositionVal = divStyle.top !== "1%"; - - // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 - reliableMarginLeftVal = divStyle.marginLeft === "2px"; - boxSizingReliableVal = divStyle.width === "4px"; - - // Support: Android 4.0 - 4.3 only - // Some styles come back with percentage values, even though they shouldn't - div.style.marginRight = "50%"; - pixelMarginRightVal = divStyle.marginRight === "4px"; - - documentElement.removeChild( container ); - - // Nullify the div so it wouldn't be stored in the memory and - // it will also be a sign that checks already performed - div = null; - } - - var pixelPositionVal, boxSizingReliableVal, pixelMarginRightVal, reliableMarginLeftVal, - container = document.createElement( "div" ), - div = document.createElement( "div" ); - - // Finish early in limited (non-browser) environments - if ( !div.style ) { - return; - } - - // Support: IE <=9 - 11 only - // Style of cloned element affects source element cloned (#8908) - div.style.backgroundClip = "content-box"; - div.cloneNode( true ).style.backgroundClip = ""; - support.clearCloneStyle = div.style.backgroundClip === "content-box"; - - container.style.cssText = "border:0;width:8px;height:0;top:0;left:-9999px;" + - "padding:0;margin-top:1px;position:absolute"; - container.appendChild( div ); - - jQuery.extend( support, { - pixelPosition: function() { - computeStyleTests(); - return pixelPositionVal; - }, - boxSizingReliable: function() { - computeStyleTests(); - return boxSizingReliableVal; - }, - pixelMarginRight: function() { - computeStyleTests(); - return pixelMarginRightVal; - }, - reliableMarginLeft: function() { - computeStyleTests(); - return reliableMarginLeftVal; - } - } ); -} )(); - - -function curCSS( elem, name, computed ) { - var width, minWidth, maxWidth, ret, - - // Support: Firefox 51+ - // Retrieving style before computed somehow - // fixes an issue with getting wrong values - // on detached elements - style = elem.style; - - computed = computed || getStyles( elem ); - - // getPropertyValue is needed for: - // .css('filter') (IE 9 only, #12537) - // .css('--customProperty) (#3144) - if ( computed ) { - ret = computed.getPropertyValue( name ) || computed[ name ]; - - if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) { - ret = jQuery.style( elem, name ); - } - - // A tribute to the "awesome hack by Dean Edwards" - // Android Browser returns percentage for some values, - // but width seems to be reliably pixels. - // This is against the CSSOM draft spec: - // https://drafts.csswg.org/cssom/#resolved-values - if ( !support.pixelMarginRight() && rnumnonpx.test( ret ) && rmargin.test( name ) ) { - - // Remember the original values - width = style.width; - minWidth = style.minWidth; - maxWidth = style.maxWidth; - - // Put in the new values to get a computed value out - style.minWidth = style.maxWidth = style.width = ret; - ret = computed.width; - - // Revert the changed values - style.width = width; - style.minWidth = minWidth; - style.maxWidth = maxWidth; - } - } - - return ret !== undefined ? - - // Support: IE <=9 - 11 only - // IE returns zIndex value as an integer. - ret + "" : - ret; -} - - -function addGetHookIf( conditionFn, hookFn ) { - - // Define the hook, we'll check on the first run if it's really needed. - return { - get: function() { - if ( conditionFn() ) { - - // Hook not needed (or it's not possible to use it due - // to missing dependency), remove it. - delete this.get; - return; - } - - // Hook needed; redefine it so that the support test is not executed again. - return ( this.get = hookFn ).apply( this, arguments ); - } - }; -} - - -var - - // Swappable if display is none or starts with table - // except "table", "table-cell", or "table-caption" - // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display - rdisplayswap = /^(none|table(?!-c[ea]).+)/, - rcustomProp = /^--/, - cssShow = { position: "absolute", visibility: "hidden", display: "block" }, - cssNormalTransform = { - letterSpacing: "0", - fontWeight: "400" - }, - - cssPrefixes = [ "Webkit", "Moz", "ms" ], - emptyStyle = document.createElement( "div" ).style; - -// Return a css property mapped to a potentially vendor prefixed property -function vendorPropName( name ) { - - // Shortcut for names that are not vendor prefixed - if ( name in emptyStyle ) { - return name; - } - - // Check for vendor prefixed names - var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), - i = cssPrefixes.length; - - while ( i-- ) { - name = cssPrefixes[ i ] + capName; - if ( name in emptyStyle ) { - return name; - } - } -} - -// Return a property mapped along what jQuery.cssProps suggests or to -// a vendor prefixed property. -function finalPropName( name ) { - var ret = jQuery.cssProps[ name ]; - if ( !ret ) { - ret = jQuery.cssProps[ name ] = vendorPropName( name ) || name; - } - return ret; -} - -function setPositiveNumber( elem, value, subtract ) { - - // Any relative (+/-) values have already been - // normalized at this point - var matches = rcssNum.exec( value ); - return matches ? - - // Guard against undefined "subtract", e.g., when used as in cssHooks - Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : - value; -} - -function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) { - var i, - val = 0; - - // If we already have the right measurement, avoid augmentation - if ( extra === ( isBorderBox ? "border" : "content" ) ) { - i = 4; - - // Otherwise initialize for horizontal or vertical properties - } else { - i = name === "width" ? 1 : 0; - } - - for ( ; i < 4; i += 2 ) { - - // Both box models exclude margin, so add it if we want it - if ( extra === "margin" ) { - val += jQuery.css( elem, extra + cssExpand[ i ], true, styles ); - } - - if ( isBorderBox ) { - - // border-box includes padding, so remove it if we want content - if ( extra === "content" ) { - val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); - } - - // At this point, extra isn't border nor margin, so remove border - if ( extra !== "margin" ) { - val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); - } - } else { - - // At this point, extra isn't content, so add padding - val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); - - // At this point, extra isn't content nor padding, so add border - if ( extra !== "padding" ) { - val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); - } - } - } - - return val; -} - -function getWidthOrHeight( elem, name, extra ) { - - // Start with computed style - var valueIsBorderBox, - styles = getStyles( elem ), - val = curCSS( elem, name, styles ), - isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; - - // Computed unit is not pixels. Stop here and return. - if ( rnumnonpx.test( val ) ) { - return val; - } - - // Check for style in case a browser which returns unreliable values - // for getComputedStyle silently falls back to the reliable elem.style - valueIsBorderBox = isBorderBox && - ( support.boxSizingReliable() || val === elem.style[ name ] ); - - // Fall back to offsetWidth/Height when value is "auto" - // This happens for inline elements with no explicit setting (gh-3571) - if ( val === "auto" ) { - val = elem[ "offset" + name[ 0 ].toUpperCase() + name.slice( 1 ) ]; - } - - // Normalize "", auto, and prepare for extra - val = parseFloat( val ) || 0; - - // Use the active box-sizing model to add/subtract irrelevant styles - return ( val + - augmentWidthOrHeight( - elem, - name, - extra || ( isBorderBox ? "border" : "content" ), - valueIsBorderBox, - styles - ) - ) + "px"; -} - -jQuery.extend( { - - // Add in style property hooks for overriding the default - // behavior of getting and setting a style property - cssHooks: { - opacity: { - get: function( elem, computed ) { - if ( computed ) { - - // We should always get a number back from opacity - var ret = curCSS( elem, "opacity" ); - return ret === "" ? "1" : ret; - } - } - } - }, - - // Don't automatically add "px" to these possibly-unitless properties - cssNumber: { - "animationIterationCount": true, - "columnCount": true, - "fillOpacity": true, - "flexGrow": true, - "flexShrink": true, - "fontWeight": true, - "lineHeight": true, - "opacity": true, - "order": true, - "orphans": true, - "widows": true, - "zIndex": true, - "zoom": true - }, - - // Add in properties whose names you wish to fix before - // setting or getting the value - cssProps: { - "float": "cssFloat" - }, - - // Get and set the style property on a DOM Node - style: function( elem, name, value, extra ) { - - // Don't set styles on text and comment nodes - if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { - return; - } - - // Make sure that we're working with the right name - var ret, type, hooks, - origName = jQuery.camelCase( name ), - isCustomProp = rcustomProp.test( name ), - style = elem.style; - - // Make sure that we're working with the right name. We don't - // want to query the value if it is a CSS custom property - // since they are user-defined. - if ( !isCustomProp ) { - name = finalPropName( origName ); - } - - // Gets hook for the prefixed version, then unprefixed version - hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; - - // Check if we're setting a value - if ( value !== undefined ) { - type = typeof value; - - // Convert "+=" or "-=" to relative numbers (#7345) - if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { - value = adjustCSS( elem, name, ret ); - - // Fixes bug #9237 - type = "number"; - } - - // Make sure that null and NaN values aren't set (#7116) - if ( value == null || value !== value ) { - return; - } - - // If a number was passed in, add the unit (except for certain CSS properties) - if ( type === "number" ) { - value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); - } - - // background-* props affect original clone's values - if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { - style[ name ] = "inherit"; - } - - // If a hook was provided, use that value, otherwise just set the specified value - if ( !hooks || !( "set" in hooks ) || - ( value = hooks.set( elem, value, extra ) ) !== undefined ) { - - if ( isCustomProp ) { - style.setProperty( name, value ); - } else { - style[ name ] = value; - } - } - - } else { - - // If a hook was provided get the non-computed value from there - if ( hooks && "get" in hooks && - ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { - - return ret; - } - - // Otherwise just get the value from the style object - return style[ name ]; - } - }, - - css: function( elem, name, extra, styles ) { - var val, num, hooks, - origName = jQuery.camelCase( name ), - isCustomProp = rcustomProp.test( name ); - - // Make sure that we're working with the right name. We don't - // want to modify the value if it is a CSS custom property - // since they are user-defined. - if ( !isCustomProp ) { - name = finalPropName( origName ); - } - - // Try prefixed name followed by the unprefixed name - hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; - - // If a hook was provided get the computed value from there - if ( hooks && "get" in hooks ) { - val = hooks.get( elem, true, extra ); - } - - // Otherwise, if a way to get the computed value exists, use that - if ( val === undefined ) { - val = curCSS( elem, name, styles ); - } - - // Convert "normal" to computed value - if ( val === "normal" && name in cssNormalTransform ) { - val = cssNormalTransform[ name ]; - } - - // Make numeric if forced or a qualifier was provided and val looks numeric - if ( extra === "" || extra ) { - num = parseFloat( val ); - return extra === true || isFinite( num ) ? num || 0 : val; - } - - return val; - } -} ); - -jQuery.each( [ "height", "width" ], function( i, name ) { - jQuery.cssHooks[ name ] = { - get: function( elem, computed, extra ) { - if ( computed ) { - - // Certain elements can have dimension info if we invisibly show them - // but it must have a current display style that would benefit - return rdisplayswap.test( jQuery.css( elem, "display" ) ) && - - // Support: Safari 8+ - // Table columns in Safari have non-zero offsetWidth & zero - // getBoundingClientRect().width unless display is changed. - // Support: IE <=11 only - // Running getBoundingClientRect on a disconnected node - // in IE throws an error. - ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? - swap( elem, cssShow, function() { - return getWidthOrHeight( elem, name, extra ); - } ) : - getWidthOrHeight( elem, name, extra ); - } - }, - - set: function( elem, value, extra ) { - var matches, - styles = extra && getStyles( elem ), - subtract = extra && augmentWidthOrHeight( - elem, - name, - extra, - jQuery.css( elem, "boxSizing", false, styles ) === "border-box", - styles - ); - - // Convert to pixels if value adjustment is needed - if ( subtract && ( matches = rcssNum.exec( value ) ) && - ( matches[ 3 ] || "px" ) !== "px" ) { - - elem.style[ name ] = value; - value = jQuery.css( elem, name ); - } - - return setPositiveNumber( elem, value, subtract ); - } - }; -} ); - -jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft, - function( elem, computed ) { - if ( computed ) { - return ( parseFloat( curCSS( elem, "marginLeft" ) ) || - elem.getBoundingClientRect().left - - swap( elem, { marginLeft: 0 }, function() { - return elem.getBoundingClientRect().left; - } ) - ) + "px"; - } - } -); - -// These hooks are used by animate to expand properties -jQuery.each( { - margin: "", - padding: "", - border: "Width" -}, function( prefix, suffix ) { - jQuery.cssHooks[ prefix + suffix ] = { - expand: function( value ) { - var i = 0, - expanded = {}, - - // Assumes a single number if not a string - parts = typeof value === "string" ? value.split( " " ) : [ value ]; - - for ( ; i < 4; i++ ) { - expanded[ prefix + cssExpand[ i ] + suffix ] = - parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; - } - - return expanded; - } - }; - - if ( !rmargin.test( prefix ) ) { - jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; - } -} ); - -jQuery.fn.extend( { - css: function( name, value ) { - return access( this, function( elem, name, value ) { - var styles, len, - map = {}, - i = 0; - - if ( Array.isArray( name ) ) { - styles = getStyles( elem ); - len = name.length; - - for ( ; i < len; i++ ) { - map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); - } - - return map; - } - - return value !== undefined ? - jQuery.style( elem, name, value ) : - jQuery.css( elem, name ); - }, name, value, arguments.length > 1 ); - } -} ); - - -function Tween( elem, options, prop, end, easing ) { - return new Tween.prototype.init( elem, options, prop, end, easing ); -} -jQuery.Tween = Tween; - -Tween.prototype = { - constructor: Tween, - init: function( elem, options, prop, end, easing, unit ) { - this.elem = elem; - this.prop = prop; - this.easing = easing || jQuery.easing._default; - this.options = options; - this.start = this.now = this.cur(); - this.end = end; - this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); - }, - cur: function() { - var hooks = Tween.propHooks[ this.prop ]; - - return hooks && hooks.get ? - hooks.get( this ) : - Tween.propHooks._default.get( this ); - }, - run: function( percent ) { - var eased, - hooks = Tween.propHooks[ this.prop ]; - - if ( this.options.duration ) { - this.pos = eased = jQuery.easing[ this.easing ]( - percent, this.options.duration * percent, 0, 1, this.options.duration - ); - } else { - this.pos = eased = percent; - } - this.now = ( this.end - this.start ) * eased + this.start; - - if ( this.options.step ) { - this.options.step.call( this.elem, this.now, this ); - } - - if ( hooks && hooks.set ) { - hooks.set( this ); - } else { - Tween.propHooks._default.set( this ); - } - return this; - } -}; - -Tween.prototype.init.prototype = Tween.prototype; - -Tween.propHooks = { - _default: { - get: function( tween ) { - var result; - - // Use a property on the element directly when it is not a DOM element, - // or when there is no matching style property that exists. - if ( tween.elem.nodeType !== 1 || - tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) { - return tween.elem[ tween.prop ]; - } - - // Passing an empty string as a 3rd parameter to .css will automatically - // attempt a parseFloat and fallback to a string if the parse fails. - // Simple values such as "10px" are parsed to Float; - // complex values such as "rotate(1rad)" are returned as-is. - result = jQuery.css( tween.elem, tween.prop, "" ); - - // Empty strings, null, undefined and "auto" are converted to 0. - return !result || result === "auto" ? 0 : result; - }, - set: function( tween ) { - - // Use step hook for back compat. - // Use cssHook if its there. - // Use .style if available and use plain properties where available. - if ( jQuery.fx.step[ tween.prop ] ) { - jQuery.fx.step[ tween.prop ]( tween ); - } else if ( tween.elem.nodeType === 1 && - ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || - jQuery.cssHooks[ tween.prop ] ) ) { - jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); - } else { - tween.elem[ tween.prop ] = tween.now; - } - } - } -}; - -// Support: IE <=9 only -// Panic based approach to setting things on disconnected nodes -Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { - set: function( tween ) { - if ( tween.elem.nodeType && tween.elem.parentNode ) { - tween.elem[ tween.prop ] = tween.now; - } - } -}; - -jQuery.easing = { - linear: function( p ) { - return p; - }, - swing: function( p ) { - return 0.5 - Math.cos( p * Math.PI ) / 2; - }, - _default: "swing" -}; - -jQuery.fx = Tween.prototype.init; - -// Back compat <1.8 extension point -jQuery.fx.step = {}; - - - - -var - fxNow, inProgress, - rfxtypes = /^(?:toggle|show|hide)$/, - rrun = /queueHooks$/; - -function schedule() { - if ( inProgress ) { - if ( document.hidden === false && window.requestAnimationFrame ) { - window.requestAnimationFrame( schedule ); - } else { - window.setTimeout( schedule, jQuery.fx.interval ); - } - - jQuery.fx.tick(); - } -} - -// Animations created synchronously will run synchronously -function createFxNow() { - window.setTimeout( function() { - fxNow = undefined; - } ); - return ( fxNow = jQuery.now() ); -} - -// Generate parameters to create a standard animation -function genFx( type, includeWidth ) { - var which, - i = 0, - attrs = { height: type }; - - // If we include width, step value is 1 to do all cssExpand values, - // otherwise step value is 2 to skip over Left and Right - includeWidth = includeWidth ? 1 : 0; - for ( ; i < 4; i += 2 - includeWidth ) { - which = cssExpand[ i ]; - attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; - } - - if ( includeWidth ) { - attrs.opacity = attrs.width = type; - } - - return attrs; -} - -function createTween( value, prop, animation ) { - var tween, - collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ), - index = 0, - length = collection.length; - for ( ; index < length; index++ ) { - if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) { - - // We're done with this property - return tween; - } - } -} - -function defaultPrefilter( elem, props, opts ) { - var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, - isBox = "width" in props || "height" in props, - anim = this, - orig = {}, - style = elem.style, - hidden = elem.nodeType && isHiddenWithinTree( elem ), - dataShow = dataPriv.get( elem, "fxshow" ); - - // Queue-skipping animations hijack the fx hooks - if ( !opts.queue ) { - hooks = jQuery._queueHooks( elem, "fx" ); - if ( hooks.unqueued == null ) { - hooks.unqueued = 0; - oldfire = hooks.empty.fire; - hooks.empty.fire = function() { - if ( !hooks.unqueued ) { - oldfire(); - } - }; - } - hooks.unqueued++; - - anim.always( function() { - - // Ensure the complete handler is called before this completes - anim.always( function() { - hooks.unqueued--; - if ( !jQuery.queue( elem, "fx" ).length ) { - hooks.empty.fire(); - } - } ); - } ); - } - - // Detect show/hide animations - for ( prop in props ) { - value = props[ prop ]; - if ( rfxtypes.test( value ) ) { - delete props[ prop ]; - toggle = toggle || value === "toggle"; - if ( value === ( hidden ? "hide" : "show" ) ) { - - // Pretend to be hidden if this is a "show" and - // there is still data from a stopped show/hide - if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { - hidden = true; - - // Ignore all other no-op show/hide data - } else { - continue; - } - } - orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); - } - } - - // Bail out if this is a no-op like .hide().hide() - propTween = !jQuery.isEmptyObject( props ); - if ( !propTween && jQuery.isEmptyObject( orig ) ) { - return; - } - - // Restrict "overflow" and "display" styles during box animations - if ( isBox && elem.nodeType === 1 ) { - - // Support: IE <=9 - 11, Edge 12 - 13 - // Record all 3 overflow attributes because IE does not infer the shorthand - // from identically-valued overflowX and overflowY - opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; - - // Identify a display type, preferring old show/hide data over the CSS cascade - restoreDisplay = dataShow && dataShow.display; - if ( restoreDisplay == null ) { - restoreDisplay = dataPriv.get( elem, "display" ); - } - display = jQuery.css( elem, "display" ); - if ( display === "none" ) { - if ( restoreDisplay ) { - display = restoreDisplay; - } else { - - // Get nonempty value(s) by temporarily forcing visibility - showHide( [ elem ], true ); - restoreDisplay = elem.style.display || restoreDisplay; - display = jQuery.css( elem, "display" ); - showHide( [ elem ] ); - } - } - - // Animate inline elements as inline-block - if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { - if ( jQuery.css( elem, "float" ) === "none" ) { - - // Restore the original display value at the end of pure show/hide animations - if ( !propTween ) { - anim.done( function() { - style.display = restoreDisplay; - } ); - if ( restoreDisplay == null ) { - display = style.display; - restoreDisplay = display === "none" ? "" : display; - } - } - style.display = "inline-block"; - } - } - } - - if ( opts.overflow ) { - style.overflow = "hidden"; - anim.always( function() { - style.overflow = opts.overflow[ 0 ]; - style.overflowX = opts.overflow[ 1 ]; - style.overflowY = opts.overflow[ 2 ]; - } ); - } - - // Implement show/hide animations - propTween = false; - for ( prop in orig ) { - - // General show/hide setup for this element animation - if ( !propTween ) { - if ( dataShow ) { - if ( "hidden" in dataShow ) { - hidden = dataShow.hidden; - } - } else { - dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); - } - - // Store hidden/visible for toggle so `.stop().toggle()` "reverses" - if ( toggle ) { - dataShow.hidden = !hidden; - } - - // Show elements before animating them - if ( hidden ) { - showHide( [ elem ], true ); - } - - /* eslint-disable no-loop-func */ - - anim.done( function() { - - /* eslint-enable no-loop-func */ - - // The final step of a "hide" animation is actually hiding the element - if ( !hidden ) { - showHide( [ elem ] ); - } - dataPriv.remove( elem, "fxshow" ); - for ( prop in orig ) { - jQuery.style( elem, prop, orig[ prop ] ); - } - } ); - } - - // Per-property setup - propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); - if ( !( prop in dataShow ) ) { - dataShow[ prop ] = propTween.start; - if ( hidden ) { - propTween.end = propTween.start; - propTween.start = 0; - } - } - } -} - -function propFilter( props, specialEasing ) { - var index, name, easing, value, hooks; - - // camelCase, specialEasing and expand cssHook pass - for ( index in props ) { - name = jQuery.camelCase( index ); - easing = specialEasing[ name ]; - value = props[ index ]; - if ( Array.isArray( value ) ) { - easing = value[ 1 ]; - value = props[ index ] = value[ 0 ]; - } - - if ( index !== name ) { - props[ name ] = value; - delete props[ index ]; - } - - hooks = jQuery.cssHooks[ name ]; - if ( hooks && "expand" in hooks ) { - value = hooks.expand( value ); - delete props[ name ]; - - // Not quite $.extend, this won't overwrite existing keys. - // Reusing 'index' because we have the correct "name" - for ( index in value ) { - if ( !( index in props ) ) { - props[ index ] = value[ index ]; - specialEasing[ index ] = easing; - } - } - } else { - specialEasing[ name ] = easing; - } - } -} - -function Animation( elem, properties, options ) { - var result, - stopped, - index = 0, - length = Animation.prefilters.length, - deferred = jQuery.Deferred().always( function() { - - // Don't match elem in the :animated selector - delete tick.elem; - } ), - tick = function() { - if ( stopped ) { - return false; - } - var currentTime = fxNow || createFxNow(), - remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), - - // Support: Android 2.3 only - // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497) - temp = remaining / animation.duration || 0, - percent = 1 - temp, - index = 0, - length = animation.tweens.length; - - for ( ; index < length; index++ ) { - animation.tweens[ index ].run( percent ); - } - - deferred.notifyWith( elem, [ animation, percent, remaining ] ); - - // If there's more to do, yield - if ( percent < 1 && length ) { - return remaining; - } - - // If this was an empty animation, synthesize a final progress notification - if ( !length ) { - deferred.notifyWith( elem, [ animation, 1, 0 ] ); - } - - // Resolve the animation and report its conclusion - deferred.resolveWith( elem, [ animation ] ); - return false; - }, - animation = deferred.promise( { - elem: elem, - props: jQuery.extend( {}, properties ), - opts: jQuery.extend( true, { - specialEasing: {}, - easing: jQuery.easing._default - }, options ), - originalProperties: properties, - originalOptions: options, - startTime: fxNow || createFxNow(), - duration: options.duration, - tweens: [], - createTween: function( prop, end ) { - var tween = jQuery.Tween( elem, animation.opts, prop, end, - animation.opts.specialEasing[ prop ] || animation.opts.easing ); - animation.tweens.push( tween ); - return tween; - }, - stop: function( gotoEnd ) { - var index = 0, - - // If we are going to the end, we want to run all the tweens - // otherwise we skip this part - length = gotoEnd ? animation.tweens.length : 0; - if ( stopped ) { - return this; - } - stopped = true; - for ( ; index < length; index++ ) { - animation.tweens[ index ].run( 1 ); - } - - // Resolve when we played the last frame; otherwise, reject - if ( gotoEnd ) { - deferred.notifyWith( elem, [ animation, 1, 0 ] ); - deferred.resolveWith( elem, [ animation, gotoEnd ] ); - } else { - deferred.rejectWith( elem, [ animation, gotoEnd ] ); - } - return this; - } - } ), - props = animation.props; - - propFilter( props, animation.opts.specialEasing ); - - for ( ; index < length; index++ ) { - result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); - if ( result ) { - if ( jQuery.isFunction( result.stop ) ) { - jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = - jQuery.proxy( result.stop, result ); - } - return result; - } - } - - jQuery.map( props, createTween, animation ); - - if ( jQuery.isFunction( animation.opts.start ) ) { - animation.opts.start.call( elem, animation ); - } - - // Attach callbacks from options - animation - .progress( animation.opts.progress ) - .done( animation.opts.done, animation.opts.complete ) - .fail( animation.opts.fail ) - .always( animation.opts.always ); - - jQuery.fx.timer( - jQuery.extend( tick, { - elem: elem, - anim: animation, - queue: animation.opts.queue - } ) - ); - - return animation; -} - -jQuery.Animation = jQuery.extend( Animation, { - - tweeners: { - "*": [ function( prop, value ) { - var tween = this.createTween( prop, value ); - adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); - return tween; - } ] - }, - - tweener: function( props, callback ) { - if ( jQuery.isFunction( props ) ) { - callback = props; - props = [ "*" ]; - } else { - props = props.match( rnothtmlwhite ); - } - - var prop, - index = 0, - length = props.length; - - for ( ; index < length; index++ ) { - prop = props[ index ]; - Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; - Animation.tweeners[ prop ].unshift( callback ); - } - }, - - prefilters: [ defaultPrefilter ], - - prefilter: function( callback, prepend ) { - if ( prepend ) { - Animation.prefilters.unshift( callback ); - } else { - Animation.prefilters.push( callback ); - } - } -} ); - -jQuery.speed = function( speed, easing, fn ) { - var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { - complete: fn || !fn && easing || - jQuery.isFunction( speed ) && speed, - duration: speed, - easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing - }; - - // Go to the end state if fx are off - if ( jQuery.fx.off ) { - opt.duration = 0; - - } else { - if ( typeof opt.duration !== "number" ) { - if ( opt.duration in jQuery.fx.speeds ) { - opt.duration = jQuery.fx.speeds[ opt.duration ]; - - } else { - opt.duration = jQuery.fx.speeds._default; - } - } - } - - // Normalize opt.queue - true/undefined/null -> "fx" - if ( opt.queue == null || opt.queue === true ) { - opt.queue = "fx"; - } - - // Queueing - opt.old = opt.complete; - - opt.complete = function() { - if ( jQuery.isFunction( opt.old ) ) { - opt.old.call( this ); - } - - if ( opt.queue ) { - jQuery.dequeue( this, opt.queue ); - } - }; - - return opt; -}; - -jQuery.fn.extend( { - fadeTo: function( speed, to, easing, callback ) { - - // Show any hidden elements after setting opacity to 0 - return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show() - - // Animate to the value specified - .end().animate( { opacity: to }, speed, easing, callback ); - }, - animate: function( prop, speed, easing, callback ) { - var empty = jQuery.isEmptyObject( prop ), - optall = jQuery.speed( speed, easing, callback ), - doAnimation = function() { - - // Operate on a copy of prop so per-property easing won't be lost - var anim = Animation( this, jQuery.extend( {}, prop ), optall ); - - // Empty animations, or finishing resolves immediately - if ( empty || dataPriv.get( this, "finish" ) ) { - anim.stop( true ); - } - }; - doAnimation.finish = doAnimation; - - return empty || optall.queue === false ? - this.each( doAnimation ) : - this.queue( optall.queue, doAnimation ); - }, - stop: function( type, clearQueue, gotoEnd ) { - var stopQueue = function( hooks ) { - var stop = hooks.stop; - delete hooks.stop; - stop( gotoEnd ); - }; - - if ( typeof type !== "string" ) { - gotoEnd = clearQueue; - clearQueue = type; - type = undefined; - } - if ( clearQueue && type !== false ) { - this.queue( type || "fx", [] ); - } - - return this.each( function() { - var dequeue = true, - index = type != null && type + "queueHooks", - timers = jQuery.timers, - data = dataPriv.get( this ); - - if ( index ) { - if ( data[ index ] && data[ index ].stop ) { - stopQueue( data[ index ] ); - } - } else { - for ( index in data ) { - if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { - stopQueue( data[ index ] ); - } - } - } - - for ( index = timers.length; index--; ) { - if ( timers[ index ].elem === this && - ( type == null || timers[ index ].queue === type ) ) { - - timers[ index ].anim.stop( gotoEnd ); - dequeue = false; - timers.splice( index, 1 ); - } - } - - // Start the next in the queue if the last step wasn't forced. - // Timers currently will call their complete callbacks, which - // will dequeue but only if they were gotoEnd. - if ( dequeue || !gotoEnd ) { - jQuery.dequeue( this, type ); - } - } ); - }, - finish: function( type ) { - if ( type !== false ) { - type = type || "fx"; - } - return this.each( function() { - var index, - data = dataPriv.get( this ), - queue = data[ type + "queue" ], - hooks = data[ type + "queueHooks" ], - timers = jQuery.timers, - length = queue ? queue.length : 0; - - // Enable finishing flag on private data - data.finish = true; - - // Empty the queue first - jQuery.queue( this, type, [] ); - - if ( hooks && hooks.stop ) { - hooks.stop.call( this, true ); - } - - // Look for any active animations, and finish them - for ( index = timers.length; index--; ) { - if ( timers[ index ].elem === this && timers[ index ].queue === type ) { - timers[ index ].anim.stop( true ); - timers.splice( index, 1 ); - } - } - - // Look for any animations in the old queue and finish them - for ( index = 0; index < length; index++ ) { - if ( queue[ index ] && queue[ index ].finish ) { - queue[ index ].finish.call( this ); - } - } - - // Turn off finishing flag - delete data.finish; - } ); - } -} ); - -jQuery.each( [ "toggle", "show", "hide" ], function( i, name ) { - var cssFn = jQuery.fn[ name ]; - jQuery.fn[ name ] = function( speed, easing, callback ) { - return speed == null || typeof speed === "boolean" ? - cssFn.apply( this, arguments ) : - this.animate( genFx( name, true ), speed, easing, callback ); - }; -} ); - -// Generate shortcuts for custom animations -jQuery.each( { - slideDown: genFx( "show" ), - slideUp: genFx( "hide" ), - slideToggle: genFx( "toggle" ), - fadeIn: { opacity: "show" }, - fadeOut: { opacity: "hide" }, - fadeToggle: { opacity: "toggle" } -}, function( name, props ) { - jQuery.fn[ name ] = function( speed, easing, callback ) { - return this.animate( props, speed, easing, callback ); - }; -} ); - -jQuery.timers = []; -jQuery.fx.tick = function() { - var timer, - i = 0, - timers = jQuery.timers; - - fxNow = jQuery.now(); - - for ( ; i < timers.length; i++ ) { - timer = timers[ i ]; - - // Run the timer and safely remove it when done (allowing for external removal) - if ( !timer() && timers[ i ] === timer ) { - timers.splice( i--, 1 ); - } - } - - if ( !timers.length ) { - jQuery.fx.stop(); - } - fxNow = undefined; -}; - -jQuery.fx.timer = function( timer ) { - jQuery.timers.push( timer ); - jQuery.fx.start(); -}; - -jQuery.fx.interval = 13; -jQuery.fx.start = function() { - if ( inProgress ) { - return; - } - - inProgress = true; - schedule(); -}; - -jQuery.fx.stop = function() { - inProgress = null; -}; - -jQuery.fx.speeds = { - slow: 600, - fast: 200, - - // Default speed - _default: 400 -}; - - -// Based off of the plugin by Clint Helfers, with permission. -// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/ -jQuery.fn.delay = function( time, type ) { - time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; - type = type || "fx"; - - return this.queue( type, function( next, hooks ) { - var timeout = window.setTimeout( next, time ); - hooks.stop = function() { - window.clearTimeout( timeout ); - }; - } ); -}; - - -( function() { - var input = document.createElement( "input" ), - select = document.createElement( "select" ), - opt = select.appendChild( document.createElement( "option" ) ); - - input.type = "checkbox"; - - // Support: Android <=4.3 only - // Default value for a checkbox should be "on" - support.checkOn = input.value !== ""; - - // Support: IE <=11 only - // Must access selectedIndex to make default options select - support.optSelected = opt.selected; - - // Support: IE <=11 only - // An input loses its value after becoming a radio - input = document.createElement( "input" ); - input.value = "t"; - input.type = "radio"; - support.radioValue = input.value === "t"; -} )(); - - -var boolHook, - attrHandle = jQuery.expr.attrHandle; - -jQuery.fn.extend( { - attr: function( name, value ) { - return access( this, jQuery.attr, name, value, arguments.length > 1 ); - }, - - removeAttr: function( name ) { - return this.each( function() { - jQuery.removeAttr( this, name ); - } ); - } -} ); - -jQuery.extend( { - attr: function( elem, name, value ) { - var ret, hooks, - nType = elem.nodeType; - - // Don't get/set attributes on text, comment and attribute nodes - if ( nType === 3 || nType === 8 || nType === 2 ) { - return; - } - - // Fallback to prop when attributes are not supported - if ( typeof elem.getAttribute === "undefined" ) { - return jQuery.prop( elem, name, value ); - } - - // Attribute hooks are determined by the lowercase version - // Grab necessary hook if one is defined - if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { - hooks = jQuery.attrHooks[ name.toLowerCase() ] || - ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); - } - - if ( value !== undefined ) { - if ( value === null ) { - jQuery.removeAttr( elem, name ); - return; - } - - if ( hooks && "set" in hooks && - ( ret = hooks.set( elem, value, name ) ) !== undefined ) { - return ret; - } - - elem.setAttribute( name, value + "" ); - return value; - } - - if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { - return ret; - } - - ret = jQuery.find.attr( elem, name ); - - // Non-existent attributes return null, we normalize to undefined - return ret == null ? undefined : ret; - }, - - attrHooks: { - type: { - set: function( elem, value ) { - if ( !support.radioValue && value === "radio" && - nodeName( elem, "input" ) ) { - var val = elem.value; - elem.setAttribute( "type", value ); - if ( val ) { - elem.value = val; - } - return value; - } - } - } - }, - - removeAttr: function( elem, value ) { - var name, - i = 0, - - // Attribute names can contain non-HTML whitespace characters - // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 - attrNames = value && value.match( rnothtmlwhite ); - - if ( attrNames && elem.nodeType === 1 ) { - while ( ( name = attrNames[ i++ ] ) ) { - elem.removeAttribute( name ); - } - } - } -} ); - -// Hooks for boolean attributes -boolHook = { - set: function( elem, value, name ) { - if ( value === false ) { - - // Remove boolean attributes when set to false - jQuery.removeAttr( elem, name ); - } else { - elem.setAttribute( name, name ); - } - return name; - } -}; - -jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( i, name ) { - var getter = attrHandle[ name ] || jQuery.find.attr; - - attrHandle[ name ] = function( elem, name, isXML ) { - var ret, handle, - lowercaseName = name.toLowerCase(); - - if ( !isXML ) { - - // Avoid an infinite loop by temporarily removing this function from the getter - handle = attrHandle[ lowercaseName ]; - attrHandle[ lowercaseName ] = ret; - ret = getter( elem, name, isXML ) != null ? - lowercaseName : - null; - attrHandle[ lowercaseName ] = handle; - } - return ret; - }; -} ); - - - - -var rfocusable = /^(?:input|select|textarea|button)$/i, - rclickable = /^(?:a|area)$/i; - -jQuery.fn.extend( { - prop: function( name, value ) { - return access( this, jQuery.prop, name, value, arguments.length > 1 ); - }, - - removeProp: function( name ) { - return this.each( function() { - delete this[ jQuery.propFix[ name ] || name ]; - } ); - } -} ); - -jQuery.extend( { - prop: function( elem, name, value ) { - var ret, hooks, - nType = elem.nodeType; - - // Don't get/set properties on text, comment and attribute nodes - if ( nType === 3 || nType === 8 || nType === 2 ) { - return; - } - - if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { - - // Fix name and attach hooks - name = jQuery.propFix[ name ] || name; - hooks = jQuery.propHooks[ name ]; - } - - if ( value !== undefined ) { - if ( hooks && "set" in hooks && - ( ret = hooks.set( elem, value, name ) ) !== undefined ) { - return ret; - } - - return ( elem[ name ] = value ); - } - - if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { - return ret; - } - - return elem[ name ]; - }, - - propHooks: { - tabIndex: { - get: function( elem ) { - - // Support: IE <=9 - 11 only - // elem.tabIndex doesn't always return the - // correct value when it hasn't been explicitly set - // https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ - // Use proper attribute retrieval(#12072) - var tabindex = jQuery.find.attr( elem, "tabindex" ); - - if ( tabindex ) { - return parseInt( tabindex, 10 ); - } - - if ( - rfocusable.test( elem.nodeName ) || - rclickable.test( elem.nodeName ) && - elem.href - ) { - return 0; - } - - return -1; - } - } - }, - - propFix: { - "for": "htmlFor", - "class": "className" - } -} ); - -// Support: IE <=11 only -// Accessing the selectedIndex property -// forces the browser to respect setting selected -// on the option -// The getter ensures a default option is selected -// when in an optgroup -// eslint rule "no-unused-expressions" is disabled for this code -// since it considers such accessions noop -if ( !support.optSelected ) { - jQuery.propHooks.selected = { - get: function( elem ) { - - /* eslint no-unused-expressions: "off" */ - - var parent = elem.parentNode; - if ( parent && parent.parentNode ) { - parent.parentNode.selectedIndex; - } - return null; - }, - set: function( elem ) { - - /* eslint no-unused-expressions: "off" */ - - var parent = elem.parentNode; - if ( parent ) { - parent.selectedIndex; - - if ( parent.parentNode ) { - parent.parentNode.selectedIndex; - } - } - } - }; -} - -jQuery.each( [ - "tabIndex", - "readOnly", - "maxLength", - "cellSpacing", - "cellPadding", - "rowSpan", - "colSpan", - "useMap", - "frameBorder", - "contentEditable" -], function() { - jQuery.propFix[ this.toLowerCase() ] = this; -} ); - - - - - // Strip and collapse whitespace according to HTML spec - // https://html.spec.whatwg.org/multipage/infrastructure.html#strip-and-collapse-whitespace - function stripAndCollapse( value ) { - var tokens = value.match( rnothtmlwhite ) || []; - return tokens.join( " " ); - } - - -function getClass( elem ) { - return elem.getAttribute && elem.getAttribute( "class" ) || ""; -} - -jQuery.fn.extend( { - addClass: function( value ) { - var classes, elem, cur, curValue, clazz, j, finalValue, - i = 0; - - if ( jQuery.isFunction( value ) ) { - return this.each( function( j ) { - jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); - } ); - } - - if ( typeof value === "string" && value ) { - classes = value.match( rnothtmlwhite ) || []; - - while ( ( elem = this[ i++ ] ) ) { - curValue = getClass( elem ); - cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); - - if ( cur ) { - j = 0; - while ( ( clazz = classes[ j++ ] ) ) { - if ( cur.indexOf( " " + clazz + " " ) < 0 ) { - cur += clazz + " "; - } - } - - // Only assign if different to avoid unneeded rendering. - finalValue = stripAndCollapse( cur ); - if ( curValue !== finalValue ) { - elem.setAttribute( "class", finalValue ); - } - } - } - } - - return this; - }, - - removeClass: function( value ) { - var classes, elem, cur, curValue, clazz, j, finalValue, - i = 0; - - if ( jQuery.isFunction( value ) ) { - return this.each( function( j ) { - jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); - } ); - } - - if ( !arguments.length ) { - return this.attr( "class", "" ); - } - - if ( typeof value === "string" && value ) { - classes = value.match( rnothtmlwhite ) || []; - - while ( ( elem = this[ i++ ] ) ) { - curValue = getClass( elem ); - - // This expression is here for better compressibility (see addClass) - cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); - - if ( cur ) { - j = 0; - while ( ( clazz = classes[ j++ ] ) ) { - - // Remove *all* instances - while ( cur.indexOf( " " + clazz + " " ) > -1 ) { - cur = cur.replace( " " + clazz + " ", " " ); - } - } - - // Only assign if different to avoid unneeded rendering. - finalValue = stripAndCollapse( cur ); - if ( curValue !== finalValue ) { - elem.setAttribute( "class", finalValue ); - } - } - } - } - - return this; - }, - - toggleClass: function( value, stateVal ) { - var type = typeof value; - - if ( typeof stateVal === "boolean" && type === "string" ) { - return stateVal ? this.addClass( value ) : this.removeClass( value ); - } - - if ( jQuery.isFunction( value ) ) { - return this.each( function( i ) { - jQuery( this ).toggleClass( - value.call( this, i, getClass( this ), stateVal ), - stateVal - ); - } ); - } - - return this.each( function() { - var className, i, self, classNames; - - if ( type === "string" ) { - - // Toggle individual class names - i = 0; - self = jQuery( this ); - classNames = value.match( rnothtmlwhite ) || []; - - while ( ( className = classNames[ i++ ] ) ) { - - // Check each className given, space separated list - if ( self.hasClass( className ) ) { - self.removeClass( className ); - } else { - self.addClass( className ); - } - } - - // Toggle whole class name - } else if ( value === undefined || type === "boolean" ) { - className = getClass( this ); - if ( className ) { - - // Store className if set - dataPriv.set( this, "__className__", className ); - } - - // If the element has a class name or if we're passed `false`, - // then remove the whole classname (if there was one, the above saved it). - // Otherwise bring back whatever was previously saved (if anything), - // falling back to the empty string if nothing was stored. - if ( this.setAttribute ) { - this.setAttribute( "class", - className || value === false ? - "" : - dataPriv.get( this, "__className__" ) || "" - ); - } - } - } ); - }, - - hasClass: function( selector ) { - var className, elem, - i = 0; - - className = " " + selector + " "; - while ( ( elem = this[ i++ ] ) ) { - if ( elem.nodeType === 1 && - ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { - return true; - } - } - - return false; - } -} ); - - - - -var rreturn = /\r/g; - -jQuery.fn.extend( { - val: function( value ) { - var hooks, ret, isFunction, - elem = this[ 0 ]; - - if ( !arguments.length ) { - if ( elem ) { - hooks = jQuery.valHooks[ elem.type ] || - jQuery.valHooks[ elem.nodeName.toLowerCase() ]; - - if ( hooks && - "get" in hooks && - ( ret = hooks.get( elem, "value" ) ) !== undefined - ) { - return ret; - } - - ret = elem.value; - - // Handle most common string cases - if ( typeof ret === "string" ) { - return ret.replace( rreturn, "" ); - } - - // Handle cases where value is null/undef or number - return ret == null ? "" : ret; - } - - return; - } - - isFunction = jQuery.isFunction( value ); - - return this.each( function( i ) { - var val; - - if ( this.nodeType !== 1 ) { - return; - } - - if ( isFunction ) { - val = value.call( this, i, jQuery( this ).val() ); - } else { - val = value; - } - - // Treat null/undefined as ""; convert numbers to string - if ( val == null ) { - val = ""; - - } else if ( typeof val === "number" ) { - val += ""; - - } else if ( Array.isArray( val ) ) { - val = jQuery.map( val, function( value ) { - return value == null ? "" : value + ""; - } ); - } - - hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; - - // If set returns undefined, fall back to normal setting - if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { - this.value = val; - } - } ); - } -} ); - -jQuery.extend( { - valHooks: { - option: { - get: function( elem ) { - - var val = jQuery.find.attr( elem, "value" ); - return val != null ? - val : - - // Support: IE <=10 - 11 only - // option.text throws exceptions (#14686, #14858) - // Strip and collapse whitespace - // https://html.spec.whatwg.org/#strip-and-collapse-whitespace - stripAndCollapse( jQuery.text( elem ) ); - } - }, - select: { - get: function( elem ) { - var value, option, i, - options = elem.options, - index = elem.selectedIndex, - one = elem.type === "select-one", - values = one ? null : [], - max = one ? index + 1 : options.length; - - if ( index < 0 ) { - i = max; - - } else { - i = one ? index : 0; - } - - // Loop through all the selected options - for ( ; i < max; i++ ) { - option = options[ i ]; - - // Support: IE <=9 only - // IE8-9 doesn't update selected after form reset (#2551) - if ( ( option.selected || i === index ) && - - // Don't return options that are disabled or in a disabled optgroup - !option.disabled && - ( !option.parentNode.disabled || - !nodeName( option.parentNode, "optgroup" ) ) ) { - - // Get the specific value for the option - value = jQuery( option ).val(); - - // We don't need an array for one selects - if ( one ) { - return value; - } - - // Multi-Selects return an array - values.push( value ); - } - } - - return values; - }, - - set: function( elem, value ) { - var optionSet, option, - options = elem.options, - values = jQuery.makeArray( value ), - i = options.length; - - while ( i-- ) { - option = options[ i ]; - - /* eslint-disable no-cond-assign */ - - if ( option.selected = - jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 - ) { - optionSet = true; - } - - /* eslint-enable no-cond-assign */ - } - - // Force browsers to behave consistently when non-matching value is set - if ( !optionSet ) { - elem.selectedIndex = -1; - } - return values; - } - } - } -} ); - -// Radios and checkboxes getter/setter -jQuery.each( [ "radio", "checkbox" ], function() { - jQuery.valHooks[ this ] = { - set: function( elem, value ) { - if ( Array.isArray( value ) ) { - return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); - } - } - }; - if ( !support.checkOn ) { - jQuery.valHooks[ this ].get = function( elem ) { - return elem.getAttribute( "value" ) === null ? "on" : elem.value; - }; - } -} ); - - - - -// Return jQuery for attributes-only inclusion - - -var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/; - -jQuery.extend( jQuery.event, { - - trigger: function( event, data, elem, onlyHandlers ) { - - var i, cur, tmp, bubbleType, ontype, handle, special, - eventPath = [ elem || document ], - type = hasOwn.call( event, "type" ) ? event.type : event, - namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; - - cur = tmp = elem = elem || document; - - // Don't do events on text and comment nodes - if ( elem.nodeType === 3 || elem.nodeType === 8 ) { - return; - } - - // focus/blur morphs to focusin/out; ensure we're not firing them right now - if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { - return; - } - - if ( type.indexOf( "." ) > -1 ) { - - // Namespaced trigger; create a regexp to match event type in handle() - namespaces = type.split( "." ); - type = namespaces.shift(); - namespaces.sort(); - } - ontype = type.indexOf( ":" ) < 0 && "on" + type; - - // Caller can pass in a jQuery.Event object, Object, or just an event type string - event = event[ jQuery.expando ] ? - event : - new jQuery.Event( type, typeof event === "object" && event ); - - // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) - event.isTrigger = onlyHandlers ? 2 : 3; - event.namespace = namespaces.join( "." ); - event.rnamespace = event.namespace ? - new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : - null; - - // Clean up the event in case it is being reused - event.result = undefined; - if ( !event.target ) { - event.target = elem; - } - - // Clone any incoming data and prepend the event, creating the handler arg list - data = data == null ? - [ event ] : - jQuery.makeArray( data, [ event ] ); - - // Allow special events to draw outside the lines - special = jQuery.event.special[ type ] || {}; - if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { - return; - } - - // Determine event propagation path in advance, per W3C events spec (#9951) - // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) - if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { - - bubbleType = special.delegateType || type; - if ( !rfocusMorph.test( bubbleType + type ) ) { - cur = cur.parentNode; - } - for ( ; cur; cur = cur.parentNode ) { - eventPath.push( cur ); - tmp = cur; - } - - // Only add window if we got to document (e.g., not plain obj or detached DOM) - if ( tmp === ( elem.ownerDocument || document ) ) { - eventPath.push( tmp.defaultView || tmp.parentWindow || window ); - } - } - - // Fire handlers on the event path - i = 0; - while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { - - event.type = i > 1 ? - bubbleType : - special.bindType || type; - - // jQuery handler - handle = ( dataPriv.get( cur, "events" ) || {} )[ event.type ] && - dataPriv.get( cur, "handle" ); - if ( handle ) { - handle.apply( cur, data ); - } - - // Native handler - handle = ontype && cur[ ontype ]; - if ( handle && handle.apply && acceptData( cur ) ) { - event.result = handle.apply( cur, data ); - if ( event.result === false ) { - event.preventDefault(); - } - } - } - event.type = type; - - // If nobody prevented the default action, do it now - if ( !onlyHandlers && !event.isDefaultPrevented() ) { - - if ( ( !special._default || - special._default.apply( eventPath.pop(), data ) === false ) && - acceptData( elem ) ) { - - // Call a native DOM method on the target with the same name as the event. - // Don't do default actions on window, that's where global variables be (#6170) - if ( ontype && jQuery.isFunction( elem[ type ] ) && !jQuery.isWindow( elem ) ) { - - // Don't re-trigger an onFOO event when we call its FOO() method - tmp = elem[ ontype ]; - - if ( tmp ) { - elem[ ontype ] = null; - } - - // Prevent re-triggering of the same event, since we already bubbled it above - jQuery.event.triggered = type; - elem[ type ](); - jQuery.event.triggered = undefined; - - if ( tmp ) { - elem[ ontype ] = tmp; - } - } - } - } - - return event.result; - }, - - // Piggyback on a donor event to simulate a different one - // Used only for `focus(in | out)` events - simulate: function( type, elem, event ) { - var e = jQuery.extend( - new jQuery.Event(), - event, - { - type: type, - isSimulated: true - } - ); - - jQuery.event.trigger( e, null, elem ); - } - -} ); - -jQuery.fn.extend( { - - trigger: function( type, data ) { - return this.each( function() { - jQuery.event.trigger( type, data, this ); - } ); - }, - triggerHandler: function( type, data ) { - var elem = this[ 0 ]; - if ( elem ) { - return jQuery.event.trigger( type, data, elem, true ); - } - } -} ); - - -jQuery.each( ( "blur focus focusin focusout resize scroll click dblclick " + - "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + - "change select submit keydown keypress keyup contextmenu" ).split( " " ), - function( i, name ) { - - // Handle event binding - jQuery.fn[ name ] = function( data, fn ) { - return arguments.length > 0 ? - this.on( name, null, data, fn ) : - this.trigger( name ); - }; -} ); - -jQuery.fn.extend( { - hover: function( fnOver, fnOut ) { - return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); - } -} ); - - - - -support.focusin = "onfocusin" in window; - - -// Support: Firefox <=44 -// Firefox doesn't have focus(in | out) events -// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 -// -// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 -// focus(in | out) events fire after focus & blur events, -// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order -// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 -if ( !support.focusin ) { - jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) { - - // Attach a single capturing handler on the document while someone wants focusin/focusout - var handler = function( event ) { - jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) ); - }; - - jQuery.event.special[ fix ] = { - setup: function() { - var doc = this.ownerDocument || this, - attaches = dataPriv.access( doc, fix ); - - if ( !attaches ) { - doc.addEventListener( orig, handler, true ); - } - dataPriv.access( doc, fix, ( attaches || 0 ) + 1 ); - }, - teardown: function() { - var doc = this.ownerDocument || this, - attaches = dataPriv.access( doc, fix ) - 1; - - if ( !attaches ) { - doc.removeEventListener( orig, handler, true ); - dataPriv.remove( doc, fix ); - - } else { - dataPriv.access( doc, fix, attaches ); - } - } - }; - } ); -} -var location = window.location; - -var nonce = jQuery.now(); - -var rquery = ( /\?/ ); - - - -// Cross-browser xml parsing -jQuery.parseXML = function( data ) { - var xml; - if ( !data || typeof data !== "string" ) { - return null; - } - - // Support: IE 9 - 11 only - // IE throws on parseFromString with invalid input. - try { - xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); - } catch ( e ) { - xml = undefined; - } - - if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) { - jQuery.error( "Invalid XML: " + data ); - } - return xml; -}; - - -var - rbracket = /\[\]$/, - rCRLF = /\r?\n/g, - rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, - rsubmittable = /^(?:input|select|textarea|keygen)/i; - -function buildParams( prefix, obj, traditional, add ) { - var name; - - if ( Array.isArray( obj ) ) { - - // Serialize array item. - jQuery.each( obj, function( i, v ) { - if ( traditional || rbracket.test( prefix ) ) { - - // Treat each array item as a scalar. - add( prefix, v ); - - } else { - - // Item is non-scalar (array or object), encode its numeric index. - buildParams( - prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", - v, - traditional, - add - ); - } - } ); - - } else if ( !traditional && jQuery.type( obj ) === "object" ) { - - // Serialize object item. - for ( name in obj ) { - buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); - } - - } else { - - // Serialize scalar item. - add( prefix, obj ); - } -} - -// Serialize an array of form elements or a set of -// key/values into a query string -jQuery.param = function( a, traditional ) { - var prefix, - s = [], - add = function( key, valueOrFunction ) { - - // If value is a function, invoke it and use its return value - var value = jQuery.isFunction( valueOrFunction ) ? - valueOrFunction() : - valueOrFunction; - - s[ s.length ] = encodeURIComponent( key ) + "=" + - encodeURIComponent( value == null ? "" : value ); - }; - - // If an array was passed in, assume that it is an array of form elements. - if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { - - // Serialize the form elements - jQuery.each( a, function() { - add( this.name, this.value ); - } ); - - } else { - - // If traditional, encode the "old" way (the way 1.3.2 or older - // did it), otherwise encode params recursively. - for ( prefix in a ) { - buildParams( prefix, a[ prefix ], traditional, add ); - } - } - - // Return the resulting serialization - return s.join( "&" ); -}; - -jQuery.fn.extend( { - serialize: function() { - return jQuery.param( this.serializeArray() ); - }, - serializeArray: function() { - return this.map( function() { - - // Can add propHook for "elements" to filter or add form elements - var elements = jQuery.prop( this, "elements" ); - return elements ? jQuery.makeArray( elements ) : this; - } ) - .filter( function() { - var type = this.type; - - // Use .is( ":disabled" ) so that fieldset[disabled] works - return this.name && !jQuery( this ).is( ":disabled" ) && - rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && - ( this.checked || !rcheckableType.test( type ) ); - } ) - .map( function( i, elem ) { - var val = jQuery( this ).val(); - - if ( val == null ) { - return null; - } - - if ( Array.isArray( val ) ) { - return jQuery.map( val, function( val ) { - return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; - } ); - } - - return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; - } ).get(); - } -} ); - - -var - r20 = /%20/g, - rhash = /#.*$/, - rantiCache = /([?&])_=[^&]*/, - rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, - - // #7653, #8125, #8152: local protocol detection - rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, - rnoContent = /^(?:GET|HEAD)$/, - rprotocol = /^\/\//, - - /* Prefilters - * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) - * 2) These are called: - * - BEFORE asking for a transport - * - AFTER param serialization (s.data is a string if s.processData is true) - * 3) key is the dataType - * 4) the catchall symbol "*" can be used - * 5) execution will start with transport dataType and THEN continue down to "*" if needed - */ - prefilters = {}, - - /* Transports bindings - * 1) key is the dataType - * 2) the catchall symbol "*" can be used - * 3) selection will start with transport dataType and THEN go to "*" if needed - */ - transports = {}, - - // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression - allTypes = "*/".concat( "*" ), - - // Anchor tag for parsing the document origin - originAnchor = document.createElement( "a" ); - originAnchor.href = location.href; - -// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport -function addToPrefiltersOrTransports( structure ) { - - // dataTypeExpression is optional and defaults to "*" - return function( dataTypeExpression, func ) { - - if ( typeof dataTypeExpression !== "string" ) { - func = dataTypeExpression; - dataTypeExpression = "*"; - } - - var dataType, - i = 0, - dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; - - if ( jQuery.isFunction( func ) ) { - - // For each dataType in the dataTypeExpression - while ( ( dataType = dataTypes[ i++ ] ) ) { - - // Prepend if requested - if ( dataType[ 0 ] === "+" ) { - dataType = dataType.slice( 1 ) || "*"; - ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func ); - - // Otherwise append - } else { - ( structure[ dataType ] = structure[ dataType ] || [] ).push( func ); - } - } - } - }; -} - -// Base inspection function for prefilters and transports -function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { - - var inspected = {}, - seekingTransport = ( structure === transports ); - - function inspect( dataType ) { - var selected; - inspected[ dataType ] = true; - jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { - var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); - if ( typeof dataTypeOrTransport === "string" && - !seekingTransport && !inspected[ dataTypeOrTransport ] ) { - - options.dataTypes.unshift( dataTypeOrTransport ); - inspect( dataTypeOrTransport ); - return false; - } else if ( seekingTransport ) { - return !( selected = dataTypeOrTransport ); - } - } ); - return selected; - } - - return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); -} - -// A special extend for ajax options -// that takes "flat" options (not to be deep extended) -// Fixes #9887 -function ajaxExtend( target, src ) { - var key, deep, - flatOptions = jQuery.ajaxSettings.flatOptions || {}; - - for ( key in src ) { - if ( src[ key ] !== undefined ) { - ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; - } - } - if ( deep ) { - jQuery.extend( true, target, deep ); - } - - return target; -} - -/* Handles responses to an ajax request: - * - finds the right dataType (mediates between content-type and expected dataType) - * - returns the corresponding response - */ -function ajaxHandleResponses( s, jqXHR, responses ) { - - var ct, type, finalDataType, firstDataType, - contents = s.contents, - dataTypes = s.dataTypes; - - // Remove auto dataType and get content-type in the process - while ( dataTypes[ 0 ] === "*" ) { - dataTypes.shift(); - if ( ct === undefined ) { - ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" ); - } - } - - // Check if we're dealing with a known content-type - if ( ct ) { - for ( type in contents ) { - if ( contents[ type ] && contents[ type ].test( ct ) ) { - dataTypes.unshift( type ); - break; - } - } - } - - // Check to see if we have a response for the expected dataType - if ( dataTypes[ 0 ] in responses ) { - finalDataType = dataTypes[ 0 ]; - } else { - - // Try convertible dataTypes - for ( type in responses ) { - if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) { - finalDataType = type; - break; - } - if ( !firstDataType ) { - firstDataType = type; - } - } - - // Or just use first one - finalDataType = finalDataType || firstDataType; - } - - // If we found a dataType - // We add the dataType to the list if needed - // and return the corresponding response - if ( finalDataType ) { - if ( finalDataType !== dataTypes[ 0 ] ) { - dataTypes.unshift( finalDataType ); - } - return responses[ finalDataType ]; - } -} - -/* Chain conversions given the request and the original response - * Also sets the responseXXX fields on the jqXHR instance - */ -function ajaxConvert( s, response, jqXHR, isSuccess ) { - var conv2, current, conv, tmp, prev, - converters = {}, - - // Work with a copy of dataTypes in case we need to modify it for conversion - dataTypes = s.dataTypes.slice(); - - // Create converters map with lowercased keys - if ( dataTypes[ 1 ] ) { - for ( conv in s.converters ) { - converters[ conv.toLowerCase() ] = s.converters[ conv ]; - } - } - - current = dataTypes.shift(); - - // Convert to each sequential dataType - while ( current ) { - - if ( s.responseFields[ current ] ) { - jqXHR[ s.responseFields[ current ] ] = response; - } - - // Apply the dataFilter if provided - if ( !prev && isSuccess && s.dataFilter ) { - response = s.dataFilter( response, s.dataType ); - } - - prev = current; - current = dataTypes.shift(); - - if ( current ) { - - // There's only work to do if current dataType is non-auto - if ( current === "*" ) { - - current = prev; - - // Convert response if prev dataType is non-auto and differs from current - } else if ( prev !== "*" && prev !== current ) { - - // Seek a direct converter - conv = converters[ prev + " " + current ] || converters[ "* " + current ]; - - // If none found, seek a pair - if ( !conv ) { - for ( conv2 in converters ) { - - // If conv2 outputs current - tmp = conv2.split( " " ); - if ( tmp[ 1 ] === current ) { - - // If prev can be converted to accepted input - conv = converters[ prev + " " + tmp[ 0 ] ] || - converters[ "* " + tmp[ 0 ] ]; - if ( conv ) { - - // Condense equivalence converters - if ( conv === true ) { - conv = converters[ conv2 ]; - - // Otherwise, insert the intermediate dataType - } else if ( converters[ conv2 ] !== true ) { - current = tmp[ 0 ]; - dataTypes.unshift( tmp[ 1 ] ); - } - break; - } - } - } - } - - // Apply converter (if not an equivalence) - if ( conv !== true ) { - - // Unless errors are allowed to bubble, catch and return them - if ( conv && s.throws ) { - response = conv( response ); - } else { - try { - response = conv( response ); - } catch ( e ) { - return { - state: "parsererror", - error: conv ? e : "No conversion from " + prev + " to " + current - }; - } - } - } - } - } - } - - return { state: "success", data: response }; -} - -jQuery.extend( { - - // Counter for holding the number of active queries - active: 0, - - // Last-Modified header cache for next request - lastModified: {}, - etag: {}, - - ajaxSettings: { - url: location.href, - type: "GET", - isLocal: rlocalProtocol.test( location.protocol ), - global: true, - processData: true, - async: true, - contentType: "application/x-www-form-urlencoded; charset=UTF-8", - - /* - timeout: 0, - data: null, - dataType: null, - username: null, - password: null, - cache: null, - throws: false, - traditional: false, - headers: {}, - */ - - accepts: { - "*": allTypes, - text: "text/plain", - html: "text/html", - xml: "application/xml, text/xml", - json: "application/json, text/javascript" - }, - - contents: { - xml: /\bxml\b/, - html: /\bhtml/, - json: /\bjson\b/ - }, - - responseFields: { - xml: "responseXML", - text: "responseText", - json: "responseJSON" - }, - - // Data converters - // Keys separate source (or catchall "*") and destination types with a single space - converters: { - - // Convert anything to text - "* text": String, - - // Text to html (true = no transformation) - "text html": true, - - // Evaluate text as a json expression - "text json": JSON.parse, - - // Parse text as xml - "text xml": jQuery.parseXML - }, - - // For options that shouldn't be deep extended: - // you can add your own custom options here if - // and when you create one that shouldn't be - // deep extended (see ajaxExtend) - flatOptions: { - url: true, - context: true - } - }, - - // Creates a full fledged settings object into target - // with both ajaxSettings and settings fields. - // If target is omitted, writes into ajaxSettings. - ajaxSetup: function( target, settings ) { - return settings ? - - // Building a settings object - ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : - - // Extending ajaxSettings - ajaxExtend( jQuery.ajaxSettings, target ); - }, - - ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), - ajaxTransport: addToPrefiltersOrTransports( transports ), - - // Main method - ajax: function( url, options ) { - - // If url is an object, simulate pre-1.5 signature - if ( typeof url === "object" ) { - options = url; - url = undefined; - } - - // Force options to be an object - options = options || {}; - - var transport, - - // URL without anti-cache param - cacheURL, - - // Response headers - responseHeadersString, - responseHeaders, - - // timeout handle - timeoutTimer, - - // Url cleanup var - urlAnchor, - - // Request state (becomes false upon send and true upon completion) - completed, - - // To know if global events are to be dispatched - fireGlobals, - - // Loop variable - i, - - // uncached part of the url - uncached, - - // Create the final options object - s = jQuery.ajaxSetup( {}, options ), - - // Callbacks context - callbackContext = s.context || s, - - // Context for global events is callbackContext if it is a DOM node or jQuery collection - globalEventContext = s.context && - ( callbackContext.nodeType || callbackContext.jquery ) ? - jQuery( callbackContext ) : - jQuery.event, - - // Deferreds - deferred = jQuery.Deferred(), - completeDeferred = jQuery.Callbacks( "once memory" ), - - // Status-dependent callbacks - statusCode = s.statusCode || {}, - - // Headers (they are sent all at once) - requestHeaders = {}, - requestHeadersNames = {}, - - // Default abort message - strAbort = "canceled", - - // Fake xhr - jqXHR = { - readyState: 0, - - // Builds headers hashtable if needed - getResponseHeader: function( key ) { - var match; - if ( completed ) { - if ( !responseHeaders ) { - responseHeaders = {}; - while ( ( match = rheaders.exec( responseHeadersString ) ) ) { - responseHeaders[ match[ 1 ].toLowerCase() ] = match[ 2 ]; - } - } - match = responseHeaders[ key.toLowerCase() ]; - } - return match == null ? null : match; - }, - - // Raw string - getAllResponseHeaders: function() { - return completed ? responseHeadersString : null; - }, - - // Caches the header - setRequestHeader: function( name, value ) { - if ( completed == null ) { - name = requestHeadersNames[ name.toLowerCase() ] = - requestHeadersNames[ name.toLowerCase() ] || name; - requestHeaders[ name ] = value; - } - return this; - }, - - // Overrides response content-type header - overrideMimeType: function( type ) { - if ( completed == null ) { - s.mimeType = type; - } - return this; - }, - - // Status-dependent callbacks - statusCode: function( map ) { - var code; - if ( map ) { - if ( completed ) { - - // Execute the appropriate callbacks - jqXHR.always( map[ jqXHR.status ] ); - } else { - - // Lazy-add the new callbacks in a way that preserves old ones - for ( code in map ) { - statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; - } - } - } - return this; - }, - - // Cancel the request - abort: function( statusText ) { - var finalText = statusText || strAbort; - if ( transport ) { - transport.abort( finalText ); - } - done( 0, finalText ); - return this; - } - }; - - // Attach deferreds - deferred.promise( jqXHR ); - - // Add protocol if not provided (prefilters might expect it) - // Handle falsy url in the settings object (#10093: consistency with old signature) - // We also use the url parameter if available - s.url = ( ( url || s.url || location.href ) + "" ) - .replace( rprotocol, location.protocol + "//" ); - - // Alias method option to type as per ticket #12004 - s.type = options.method || options.type || s.method || s.type; - - // Extract dataTypes list - s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; - - // A cross-domain request is in order when the origin doesn't match the current origin. - if ( s.crossDomain == null ) { - urlAnchor = document.createElement( "a" ); - - // Support: IE <=8 - 11, Edge 12 - 13 - // IE throws exception on accessing the href property if url is malformed, - // e.g. http://example.com:80x/ - try { - urlAnchor.href = s.url; - - // Support: IE <=8 - 11 only - // Anchor's host property isn't correctly set when s.url is relative - urlAnchor.href = urlAnchor.href; - s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== - urlAnchor.protocol + "//" + urlAnchor.host; - } catch ( e ) { - - // If there is an error parsing the URL, assume it is crossDomain, - // it can be rejected by the transport if it is invalid - s.crossDomain = true; - } - } - - // Convert data if not already a string - if ( s.data && s.processData && typeof s.data !== "string" ) { - s.data = jQuery.param( s.data, s.traditional ); - } - - // Apply prefilters - inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); - - // If request was aborted inside a prefilter, stop there - if ( completed ) { - return jqXHR; - } - - // We can fire global events as of now if asked to - // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118) - fireGlobals = jQuery.event && s.global; - - // Watch for a new set of requests - if ( fireGlobals && jQuery.active++ === 0 ) { - jQuery.event.trigger( "ajaxStart" ); - } - - // Uppercase the type - s.type = s.type.toUpperCase(); - - // Determine if request has content - s.hasContent = !rnoContent.test( s.type ); - - // Save the URL in case we're toying with the If-Modified-Since - // and/or If-None-Match header later on - // Remove hash to simplify url manipulation - cacheURL = s.url.replace( rhash, "" ); - - // More options handling for requests with no content - if ( !s.hasContent ) { - - // Remember the hash so we can put it back - uncached = s.url.slice( cacheURL.length ); - - // If data is available, append data to url - if ( s.data ) { - cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; - - // #9682: remove data so that it's not used in an eventual retry - delete s.data; - } - - // Add or update anti-cache param if needed - if ( s.cache === false ) { - cacheURL = cacheURL.replace( rantiCache, "$1" ); - uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce++ ) + uncached; - } - - // Put hash and anti-cache on the URL that will be requested (gh-1732) - s.url = cacheURL + uncached; - - // Change '%20' to '+' if this is encoded form body content (gh-2658) - } else if ( s.data && s.processData && - ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { - s.data = s.data.replace( r20, "+" ); - } - - // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. - if ( s.ifModified ) { - if ( jQuery.lastModified[ cacheURL ] ) { - jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); - } - if ( jQuery.etag[ cacheURL ] ) { - jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); - } - } - - // Set the correct header, if data is being sent - if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { - jqXHR.setRequestHeader( "Content-Type", s.contentType ); - } - - // Set the Accepts header for the server, depending on the dataType - jqXHR.setRequestHeader( - "Accept", - s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? - s.accepts[ s.dataTypes[ 0 ] ] + - ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : - s.accepts[ "*" ] - ); - - // Check for headers option - for ( i in s.headers ) { - jqXHR.setRequestHeader( i, s.headers[ i ] ); - } - - // Allow custom headers/mimetypes and early abort - if ( s.beforeSend && - ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { - - // Abort if not done already and return - return jqXHR.abort(); - } - - // Aborting is no longer a cancellation - strAbort = "abort"; - - // Install callbacks on deferreds - completeDeferred.add( s.complete ); - jqXHR.done( s.success ); - jqXHR.fail( s.error ); - - // Get transport - transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); - - // If no transport, we auto-abort - if ( !transport ) { - done( -1, "No Transport" ); - } else { - jqXHR.readyState = 1; - - // Send global event - if ( fireGlobals ) { - globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); - } - - // If request was aborted inside ajaxSend, stop there - if ( completed ) { - return jqXHR; - } - - // Timeout - if ( s.async && s.timeout > 0 ) { - timeoutTimer = window.setTimeout( function() { - jqXHR.abort( "timeout" ); - }, s.timeout ); - } - - try { - completed = false; - transport.send( requestHeaders, done ); - } catch ( e ) { - - // Rethrow post-completion exceptions - if ( completed ) { - throw e; - } - - // Propagate others as results - done( -1, e ); - } - } - - // Callback for when everything is done - function done( status, nativeStatusText, responses, headers ) { - var isSuccess, success, error, response, modified, - statusText = nativeStatusText; - - // Ignore repeat invocations - if ( completed ) { - return; - } - - completed = true; - - // Clear timeout if it exists - if ( timeoutTimer ) { - window.clearTimeout( timeoutTimer ); - } - - // Dereference transport for early garbage collection - // (no matter how long the jqXHR object will be used) - transport = undefined; - - // Cache response headers - responseHeadersString = headers || ""; - - // Set readyState - jqXHR.readyState = status > 0 ? 4 : 0; - - // Determine if successful - isSuccess = status >= 200 && status < 300 || status === 304; - - // Get response data - if ( responses ) { - response = ajaxHandleResponses( s, jqXHR, responses ); - } - - // Convert no matter what (that way responseXXX fields are always set) - response = ajaxConvert( s, response, jqXHR, isSuccess ); - - // If successful, handle type chaining - if ( isSuccess ) { - - // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. - if ( s.ifModified ) { - modified = jqXHR.getResponseHeader( "Last-Modified" ); - if ( modified ) { - jQuery.lastModified[ cacheURL ] = modified; - } - modified = jqXHR.getResponseHeader( "etag" ); - if ( modified ) { - jQuery.etag[ cacheURL ] = modified; - } - } - - // if no content - if ( status === 204 || s.type === "HEAD" ) { - statusText = "nocontent"; - - // if not modified - } else if ( status === 304 ) { - statusText = "notmodified"; - - // If we have data, let's convert it - } else { - statusText = response.state; - success = response.data; - error = response.error; - isSuccess = !error; - } - } else { - - // Extract error from statusText and normalize for non-aborts - error = statusText; - if ( status || !statusText ) { - statusText = "error"; - if ( status < 0 ) { - status = 0; - } - } - } - - // Set data for the fake xhr object - jqXHR.status = status; - jqXHR.statusText = ( nativeStatusText || statusText ) + ""; - - // Success/Error - if ( isSuccess ) { - deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); - } else { - deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); - } - - // Status-dependent callbacks - jqXHR.statusCode( statusCode ); - statusCode = undefined; - - if ( fireGlobals ) { - globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", - [ jqXHR, s, isSuccess ? success : error ] ); - } - - // Complete - completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); - - if ( fireGlobals ) { - globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); - - // Handle the global AJAX counter - if ( !( --jQuery.active ) ) { - jQuery.event.trigger( "ajaxStop" ); - } - } - } - - return jqXHR; - }, - - getJSON: function( url, data, callback ) { - return jQuery.get( url, data, callback, "json" ); - }, - - getScript: function( url, callback ) { - return jQuery.get( url, undefined, callback, "script" ); - } -} ); - -jQuery.each( [ "get", "post" ], function( i, method ) { - jQuery[ method ] = function( url, data, callback, type ) { - - // Shift arguments if data argument was omitted - if ( jQuery.isFunction( data ) ) { - type = type || callback; - callback = data; - data = undefined; - } - - // The url can be an options object (which then must have .url) - return jQuery.ajax( jQuery.extend( { - url: url, - type: method, - dataType: type, - data: data, - success: callback - }, jQuery.isPlainObject( url ) && url ) ); - }; -} ); - - -jQuery._evalUrl = function( url ) { - return jQuery.ajax( { - url: url, - - // Make this explicit, since user can override this through ajaxSetup (#11264) - type: "GET", - dataType: "script", - cache: true, - async: false, - global: false, - "throws": true - } ); -}; - - -jQuery.fn.extend( { - wrapAll: function( html ) { - var wrap; - - if ( this[ 0 ] ) { - if ( jQuery.isFunction( html ) ) { - html = html.call( this[ 0 ] ); - } - - // The elements to wrap the target around - wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); - - if ( this[ 0 ].parentNode ) { - wrap.insertBefore( this[ 0 ] ); - } - - wrap.map( function() { - var elem = this; - - while ( elem.firstElementChild ) { - elem = elem.firstElementChild; - } - - return elem; - } ).append( this ); - } - - return this; - }, - - wrapInner: function( html ) { - if ( jQuery.isFunction( html ) ) { - return this.each( function( i ) { - jQuery( this ).wrapInner( html.call( this, i ) ); - } ); - } - - return this.each( function() { - var self = jQuery( this ), - contents = self.contents(); - - if ( contents.length ) { - contents.wrapAll( html ); - - } else { - self.append( html ); - } - } ); - }, - - wrap: function( html ) { - var isFunction = jQuery.isFunction( html ); - - return this.each( function( i ) { - jQuery( this ).wrapAll( isFunction ? html.call( this, i ) : html ); - } ); - }, - - unwrap: function( selector ) { - this.parent( selector ).not( "body" ).each( function() { - jQuery( this ).replaceWith( this.childNodes ); - } ); - return this; - } -} ); - - -jQuery.expr.pseudos.hidden = function( elem ) { - return !jQuery.expr.pseudos.visible( elem ); -}; -jQuery.expr.pseudos.visible = function( elem ) { - return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); -}; - - - - -jQuery.ajaxSettings.xhr = function() { - try { - return new window.XMLHttpRequest(); - } catch ( e ) {} -}; - -var xhrSuccessStatus = { - - // File protocol always yields status code 0, assume 200 - 0: 200, - - // Support: IE <=9 only - // #1450: sometimes IE returns 1223 when it should be 204 - 1223: 204 - }, - xhrSupported = jQuery.ajaxSettings.xhr(); - -support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); -support.ajax = xhrSupported = !!xhrSupported; - -jQuery.ajaxTransport( function( options ) { - var callback, errorCallback; - - // Cross domain only allowed if supported through XMLHttpRequest - if ( support.cors || xhrSupported && !options.crossDomain ) { - return { - send: function( headers, complete ) { - var i, - xhr = options.xhr(); - - xhr.open( - options.type, - options.url, - options.async, - options.username, - options.password - ); - - // Apply custom fields if provided - if ( options.xhrFields ) { - for ( i in options.xhrFields ) { - xhr[ i ] = options.xhrFields[ i ]; - } - } - - // Override mime type if needed - if ( options.mimeType && xhr.overrideMimeType ) { - xhr.overrideMimeType( options.mimeType ); - } - - // X-Requested-With header - // For cross-domain requests, seeing as conditions for a preflight are - // akin to a jigsaw puzzle, we simply never set it to be sure. - // (it can always be set on a per-request basis or even using ajaxSetup) - // For same-domain requests, won't change header if already provided. - if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) { - headers[ "X-Requested-With" ] = "XMLHttpRequest"; - } - - // Set headers - for ( i in headers ) { - xhr.setRequestHeader( i, headers[ i ] ); - } - - // Callback - callback = function( type ) { - return function() { - if ( callback ) { - callback = errorCallback = xhr.onload = - xhr.onerror = xhr.onabort = xhr.onreadystatechange = null; - - if ( type === "abort" ) { - xhr.abort(); - } else if ( type === "error" ) { - - // Support: IE <=9 only - // On a manual native abort, IE9 throws - // errors on any property access that is not readyState - if ( typeof xhr.status !== "number" ) { - complete( 0, "error" ); - } else { - complete( - - // File: protocol always yields status 0; see #8605, #14207 - xhr.status, - xhr.statusText - ); - } - } else { - complete( - xhrSuccessStatus[ xhr.status ] || xhr.status, - xhr.statusText, - - // Support: IE <=9 only - // IE9 has no XHR2 but throws on binary (trac-11426) - // For XHR2 non-text, let the caller handle it (gh-2498) - ( xhr.responseType || "text" ) !== "text" || - typeof xhr.responseText !== "string" ? - { binary: xhr.response } : - { text: xhr.responseText }, - xhr.getAllResponseHeaders() - ); - } - } - }; - }; - - // Listen to events - xhr.onload = callback(); - errorCallback = xhr.onerror = callback( "error" ); - - // Support: IE 9 only - // Use onreadystatechange to replace onabort - // to handle uncaught aborts - if ( xhr.onabort !== undefined ) { - xhr.onabort = errorCallback; - } else { - xhr.onreadystatechange = function() { - - // Check readyState before timeout as it changes - if ( xhr.readyState === 4 ) { - - // Allow onerror to be called first, - // but that will not handle a native abort - // Also, save errorCallback to a variable - // as xhr.onerror cannot be accessed - window.setTimeout( function() { - if ( callback ) { - errorCallback(); - } - } ); - } - }; - } - - // Create the abort callback - callback = callback( "abort" ); - - try { - - // Do send the request (this may raise an exception) - xhr.send( options.hasContent && options.data || null ); - } catch ( e ) { - - // #14683: Only rethrow if this hasn't been notified as an error yet - if ( callback ) { - throw e; - } - } - }, - - abort: function() { - if ( callback ) { - callback(); - } - } - }; - } -} ); - - - - -// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) -jQuery.ajaxPrefilter( function( s ) { - if ( s.crossDomain ) { - s.contents.script = false; - } -} ); - -// Install script dataType -jQuery.ajaxSetup( { - accepts: { - script: "text/javascript, application/javascript, " + - "application/ecmascript, application/x-ecmascript" - }, - contents: { - script: /\b(?:java|ecma)script\b/ - }, - converters: { - "text script": function( text ) { - jQuery.globalEval( text ); - return text; - } - } -} ); - -// Handle cache's special case and crossDomain -jQuery.ajaxPrefilter( "script", function( s ) { - if ( s.cache === undefined ) { - s.cache = false; - } - if ( s.crossDomain ) { - s.type = "GET"; - } -} ); - -// Bind script tag hack transport -jQuery.ajaxTransport( "script", function( s ) { - - // This transport only deals with cross domain requests - if ( s.crossDomain ) { - var script, callback; - return { - send: function( _, complete ) { - script = jQuery( " - - - - - - - - - - - - - - - -
- - - - - - - slackclient (Legacy Python Slack SDK) - - -
- - -
-
- - - - - -
- -
-
-

Tokens & Installationยถ

-
-

Keeping tokens safeยถ

-

The OAuth token you use to call the Slack API has access to the data on the workspace where it is installed. Depending on the scopes granted to the token, it potentially has the ability to read and write data. Treat these tokens just as you would a password โ€“ donโ€™t publish them, donโ€™t check them into source code, donโ€™t share them with others.

-

๐ŸšซAvoid this:

-
token = 'xoxb-111-222-xxxxx'
-
-
-

We recommend you pass tokens in as environment variables, or persist them in a database that is accessed at runtime. You can add a token to the environment by starting your app as:

-
SLACK_BOT_TOKEN="xoxb-111-222-xxxxx" python myapp.py
-
-
-

Then retrieve the key with:

-
import os
-SLACK_BOT_TOKEN = os.environ["SLACK_BOT_TOKEN"]
-
-
-

For additional information, please see our Safely Storing Credentials page.

-
-
-

Single Workspace Installยถ

-

If youโ€™re building an application for a single Slack workspace, thereโ€™s no need to build out the entire OAuth flow.

-

Once youโ€™ve setup your features, click on the Install App to Team button found on the Install App page. -If you add new permission scopes or Slack app features after an app has been installed, you must reinstall the app to -your workspace for changes to take effect.

-

For additional information, see the Installing Apps of our Building Slack apps page.

-
-
-

Multiple Workspace Installยถ

-

If you intend for an app to be installed on multiple Slack workspaces, you will need to handle this installation via the industry-standard OAuth protocol. You can read more about how Slack handles Oauth.

-

(The OAuth exchange is facilitated via HTTP and requires a webserver; in this example, weโ€™ll use Flask.)

-

To configure your app for OAuth, youโ€™ll need a client ID, a client secret, and a set of one or more scopes that will be applied to the token once it is granted. The client ID and client secret are available from your appโ€™s configuration page. The scopes are determined by the functionality of the app โ€“ every method you wish to access has a corresponding scope and your app will need to request that scope in order to be able to access the method. Review Slackโ€™s full list of OAuth scopes.

-
import os
-from slack import WebClient
-from flask import Flask, request
-
-client_id = os.environ["SLACK_CLIENT_ID"]
-client_secret = os.environ["SLACK_CLIENT_SECRET"]
-oauth_scope = os.environ["SLACK_SCOPES"]
-
-app = Flask(__name__)
-
-
-

The OAuth initiation link

-

To begin the OAuth flow that will install your app on a workspace, youโ€™ll need to provide the user with a link to Slackโ€™s OAuth page. This can be a simple link to https://slack.com/oauth/v2/authorize with scope and client_id query parameters, or you can use our pre-built Add to Slack button to do all the work for you.

-

This link directs the user to Slackโ€™s OAuth acceptance page, where the user will review and accept or refuse the permissions your app is requesting as defined by the scope(s).

-
@app.route("/slack/install", methods=["GET"])
-def pre_install():
-  state = "randomly-generated-one-time-value"
-  return '<a href="https://slack.com/oauth/v2/authorize?' \
-    f'scope={oauth_scope}&client_id={client_id}&state={state}">' \
-    'Add to Slack</a>'
-
-
-

The OAuth completion page

-

Once the user has agreed to the permissions youโ€™ve requested, Slack will redirect the user to your auth completion page, which includes a code query string param. Youโ€™ll use the code param to call the oauth.v2.access endpoint that will finally grant you the token.

-
@app.route("/slack/oauth_redirect", methods=["GET"])
-def post_install():
-  # Verify the "state" parameter
-
-  # Retrieve the auth code from the request params
-  code_param = request.args['code']
-
-  # An empty string is a valid token for this request
-  client = WebClient()
-
-  # Request the auth tokens from Slack
-  response = client.oauth_v2_access(
-    client_id=client_id,
-    client_secret=client_secret,
-    code=code_param
-  )
-
-
-

A successful request to oauth.v2.access will yield a JSON payload with at least one token, a bot token that begins with xoxb.

-
@app.route("/slack/oauth_redirect", methods=["GET"])
-def post_install():
-  # Verify the "state" parameter
-
-  # Retrieve the auth code from the request params
-  code_param = request.args['code']
-
-  # An empty string is a valid token for this request
-  client = WebClient()
-
-  # Request the auth tokens from Slack
-  response = client.oauth_v2_access(
-    client_id=client_id,
-    client_secret=client_secret,
-    code=code_param
-  )
-  print(response)
-
-  # Save the bot token to an environmental variable or to your data store
-  # for later use
-  os.environ["SLACK_BOT_TOKEN"] = response['access_token']
-
-  # Don't forget to let the user know that OAuth has succeeded!
-  return "Installation is completed!"
-
-if __name__ == "__main__":
-  app.run("localhost", 3000)
-
-
-

Once your user has completed the OAuth flow, youโ€™ll be able to use the provided tokens to call any of Slackโ€™s API methods that require an access token.

-

See the Basic Usage section of this documentation for usage examples.

-
-
- - -
-
-
- -
-
- -
-

- ยฉ 2015- Slack Technologies, LLC and contributors -

-
- - - - - \ No newline at end of file diff --git a/docs-v2/basic_usage.html b/docs-v2/basic_usage.html deleted file mode 100644 index 7d3e2febd..000000000 --- a/docs-v2/basic_usage.html +++ /dev/null @@ -1,691 +0,0 @@ - - - - - - Basic Usage — slackclient (Legacy Python Slack SDK) - - - - - - - - - - - - - - - - - - -
- - - - - - - slackclient (Legacy Python Slack SDK) - - -
- - -
-
- - - - - -
- -
-
-

Basic Usageยถ

-

The Slack Web API allows you to build applications that interact with Slack in more complex ways than the integrations we provide out of the box.

-

Access Slackโ€™s API methods requires an OAuth token โ€“ see the Tokens & Authentication section for more on how Slack uses OAuth tokens as well as best practices.

-

Each of these API methods is fully documented on our developer site at api.slack.com

-
-

Sending a messageยถ

-

One of the primary uses of Slack is posting messages to a channel using the channel ID or as a DM to another person using their user ID. This method will handle either a channel ID or a user ID passed to the channel parameter.

-
import logging
-logging.basicConfig(level=logging.DEBUG)
-
-import os
-from slack import WebClient
-from slack.errors import SlackApiError
-
-slack_token = os.environ["SLACK_API_TOKEN"]
-client = WebClient(token=slack_token)
-
-try:
-  response = client.chat_postMessage(
-    channel="C0XXXXXX",
-    text="Hello from your app! :tada:"
-  )
-except SlackApiError as e:
-  # You will get a SlackApiError if "ok" is False
-  assert e.response["error"]  # str like 'invalid_auth', 'channel_not_found'
-
-
-

Sending an ephemeral message, which is only visible to an assigned user in a specified channel, is nearly the same -as sending a regular message, but with an additional user parameter.

-
import os
-from slack import WebClient
-
-slack_token = os.environ["SLACK_API_TOKEN"]
-client = WebClient(token=slack_token)
-
-response = client.chat_postEphemeral(
-  channel="C0XXXXXX",
-  text="Hello silently from your app! :tada:",
-  user="U0XXXXXXX"
-)
-
-
-

See chat.postEphemeral for more info.

-
-
-
-

Formatting with Block Kitยถ

-

Messages posted from apps can contain more than just text, though. They can include full user interfaces composed of blocks.

-

The chat.postMessage method takes an optional blocks argument that allows you to customize the layout of a message. Blocks specified in a single object literal, so just add additional keys for any optional argument.

-

To send a message to a channel, use the channelโ€™s ID. For IMs, use the userโ€™s ID.

-
client.chat_postMessage(
-  channel="C0XXXXXX",
-  blocks=[
-    {
-      "type": "section",
-      "text": {
-        "type": "mrkdwn",
-        "text": "Danny Torrence left the following review for your property:"
-      }
-    },
-    {
-      "type": "section",
-      "text": {
-        "type": "mrkdwn",
-        "text": "<https://example.com|Overlook Hotel> \n :star: \n Doors had too many axe holes, guest in room " +
-          "237 was far too rowdy, whole place felt stuck in the 1920s."
-      },
-      "accessory": {
-        "type": "image",
-        "image_url": "https://images.pexels.com/photos/750319/pexels-photo-750319.jpeg",
-        "alt_text": "Haunted hotel image"
-      }
-    },
-    {
-      "type": "section",
-      "fields": [
-        {
-          "type": "mrkdwn",
-          "text": "*Average Rating*\n1.0"
-        }
-      ]
-    }
-  ]
-)
-
-
-

Note: You can use the Block Kit Builder to prototype your messageโ€™s look and feel.

-
-
-
-

Threading Messagesยถ

-

Threaded messages are a way of grouping messages together to provide greater context. You can reply to a thread or start a new threaded conversation by simply passing the original messageโ€™s ts ID in the thread_ts attribute when posting a message. If youโ€™re replying to a threaded message, youโ€™ll pass the thread_ts ID of the message youโ€™re replying to.

-

A channel or DM conversation is a nearly linear timeline of messages exchanged between people, bots, and apps. When one of these messages is replied to, it becomes the parent of a thread. By default, threaded replies do not appear directly in the channel, instead relegated to a kind of forked timeline descending from the parent message.

-
response = client.chat_postMessage(
-  channel="C0XXXXXX",
-  thread_ts="1476746830.000003",
-  text="Hello from your app! :tada:"
-)
-
-
-

By default, reply_broadcast is set to False. To indicate your reply is germane to all members of a channel, and therefore a notification of the reply should be posted in-channel, set the reply_broadcast to True.

-
response = client.chat_postMessage(
-  channel="C0XXXXXX",
-  thread_ts="1476746830.000003",
-  text="Hello from your app! :tada:",
-  reply_broadcast=True
-)
-
-
-

Note: While threaded messages may contain attachments and message buttons, when your reply is broadcast to the -channel, itโ€™ll actually be a reference to your reply, not the reply itself. -So, when appearing in the channel, it wonโ€™t contain any attachments or message buttons. Also note that updates and -deletion of threaded replies works the same as regular messages.

-

See the Threading messages together -article for more information.

-
-
-
-

Updating a messageยถ

-

Letโ€™s say you have a bot which posts the status of a request. When that request changes, youโ€™ll want to update the message to reflect itโ€™s state.

-
response = client.chat_update(
-  channel="C0XXXXXX",
-  ts="1476746830.000003",
-  text="updates from your app! :tada:"
-)
-
-
-

See chat.update for formatting options and some special considerations when calling this with a bot user.

-
-
-
-

Deleting a messageยถ

-

Sometimes you need to delete things.

-
response = client.chat_delete(
-  channel="C0XXXXXX",
-  ts="1476745373.000002"
-)
-
-
-

See chat.delete for more info.

-
-
-
-

Opening a modalยถ

-

Modals allow you to collect data from users and display dynamic information in a focused surface.

-

Modals use the same blocks that compose messages with the addition of an input block.

-
# This module is available since v2.6
-from slack.signature import SignatureVerifier
-signature_verifier = SignatureVerifier(os.environ["SLACK_SIGNING_SECRET"])
-
-from flask import Flask, request, make_response
-app = Flask(__name__)
-
-@app.route("/slack/events", methods=["POST"])
-def slack_app():
-  if not signature_verifier.is_valid_request(request.get_data(), request.headers):
-    return make_response("invalid request", 403)
-
-  if "payload" in request.form:
-    payload = json.loads(request.form["payload"])
-
-    if payload["type"] == "shortcut" \
-      and payload["callback_id"] == "open-modal-shortcut":
-      # Open a new modal by a global shortcut
-      try:
-        api_response = client.views_open(
-          trigger_id=payload["trigger_id"],
-          view={
-            "type": "modal",
-            "callback_id": "modal-id",
-            "title": {
-              "type": "plain_text",
-              "text": "Awesome Modal"
-            },
-            "submit": {
-              "type": "plain_text",
-              "text": "Submit"
-            },
-            "close": {
-              "type": "plain_text",
-              "text": "Cancel"
-            },
-            "blocks": [
-              {
-                "type": "input",
-                "block_id": "b-id",
-                "label": {
-                  "type": "plain_text",
-                  "text": "Input label",
-                },
-                "element": {
-                  "action_id": "a-id",
-                  "type": "plain_text_input",
-                }
-              }
-            ]
-          }
-        )
-        return make_response("", 200)
-      except SlackApiError as e:
-        code = e.response["error"]
-        return make_response(f"Failed to open a modal due to {code}", 200)
-
-    if payload["type"] == "view_submission" \
-      and payload["view"]["callback_id"] == "modal-id":
-      # Handle a data submission request from the modal
-      submitted_data = payload["view"]["state"]["values"]
-      print(submitted_data)  # {'b-id': {'a-id': {'type': 'plain_text_input', 'value': 'your input'}}}
-      return make_response("", 200)
-
-  return make_response("", 404)
-
-if __name__ == "__main__":
-  # export SLACK_SIGNING_SECRET=***
-  # export SLACK_API_TOKEN=xoxb-***
-  # export FLASK_ENV=development
-  # python3 app.py
-  app.run("localhost", 3000)
-
-
-

See views.open more details and additional parameters.

-

Also, to run the above example, the following Slack app configurations are required.

-
    -
  • Enable Interactivity with a valid Request URL: https://{your-public-domain}/slack/events

  • -
  • Add a global shortcut with the Callback ID: open-modal-shortcut

  • -
-
-
-
-

Updating and pushing modalsยถ

-

You can dynamically update a view inside of a modal by calling views.update and passing the view ID returned in the previous views.open call.

-
private_metadata = "any str data you want to store"
-response = client.views_update(
-  view_id=payload["view"]["id"],
-  hash=payload["view"]["hash"],
-  view={
-    "type": "modal",
-    "callback_id": "modal-id",
-    "private_metadata": private_metadata,
-    "title": {
-      "type": "plain_text",
-      "text": "Awesome Modal"
-    },
-    "submit": {
-      "type": "plain_text",
-      "text": "Submit"
-    },
-    "close": {
-      "type": "plain_text",
-      "text": "Cancel"
-    },
-    "blocks": [
-      {
-        "type": "input",
-        "block_id": "b-id",
-        "label": {
-          "type": "plain_text",
-          "text": "Input label",
-        },
-        "element": {
-          "action_id": "a-id",
-          "type": "plain_text_input",
-        }
-      }
-    ]
-  }
-)
-
-
-

See views.update for more info.

-

If you want to push a new view onto the modal instead of updating an existing view, reference the views.push documentation.

-
-
-
-

Emoji reactionsยถ

-

You can quickly respond to any message on Slack with an emoji reaction. Reactions can be used for any purpose: voting, checking off to-do items, showing excitement -โ€” or just for fun.

-

This method adds a reaction (emoji) to an item (file, file comment, channel message, group message, or direct message). One of file, file_comment, or the combination of channel and timestamp must be specified.

-
response = client.reactions_add(
-  channel="C0XXXXXXX",
-  name="thumbsup",
-  timestamp="1234567890.123456"
-)
-
-
-

Removing an emoji reaction is basically the same format, but youโ€™ll use reactions.remove instead of reactions.add

-
response = client.reactions_remove(
-  channel="C0XXXXXXX",
-  name="thumbsup",
-  timestamp="1234567890.123456"
-)
-
-
-

See reactions.add and reactions.remove for more info.

-
-
-
-

Listing public channelsยถ

-

At some point, youโ€™ll want to find out what channels are available to your app. This is how you get that list.

-
response = client.conversations_list(types="public_channel")
-
-
-

Archived channels are included by default. You can exclude them by passing exclude_archived=1 to your request.

-
response = client.conversations_list(exclude_archived=1)
-
-
-

See conversations.list for more info.

-
-
-
-

Getting a channelโ€™s infoยถ

-

Once you have the ID for a specific channel, you can fetch information about that channel.

-
response = client.conversations_info(channel="C0XXXXXXX")
-
-
-

See conversations.info for more info.

-
-
-
-

Joining a channelยถ

-

Channels are the social hub of most Slack teams. Hereโ€™s how you hop into one:

-
response = client.conversations_join(channel="C0XXXXXXY")
-
-
-

If you are already in the channel, the response is slightly different. -already_in_channel will be true, and a limited channel object will be returned. Bot users cannot join a channel on their own, they need to be invited by another user.

-

See conversations.join for more info.

-
-
-
-

Leaving a channelยถ

-

Maybe youโ€™ve finished up all the business you had in a channel, or maybe you -joined one by accident. This is how you leave a channel.

-
response = client.conversations_leave(channel="C0XXXXXXX")
-
-
-

See conversations.leave for more info.

-
-
-
-

Listing team membersยถ

-
response = client.users_list()
-users = response["members"]
-user_ids = list(map(lambda u: u["id"], users))
-
-
-

See users.list for more info.

-
-
-
-

Uploading filesยถ

-
response = client.files_upload(
-  channels="C3UKJTQAC",
-  file="files.pdf",
-  title="Test upload"
-)
-
-
-

See files.upload for more info.

-
-
-
-

Calling any API methodsยถ

-

This library covers all the public endpoints as the methods in WebClient. That said, you may see a bit delay of the library release. When youโ€™re in a hurry, you can directly use api_call method as below.

-
import os
-from slack import WebClient
-
-client = WebClient(token=os.environ['SLACK_API_TOKEN'])
-response = client.api_call(
-  api_method='chat.postMessage',
-  json={'channel': '#random','text': "Hello world!"}
-)
-assert response["message"]["text"] == "Hello world!"
-
-
-
-
-
-

Web API Rate Limitsยถ

-

When posting messages to a channel, Slack allows applications to send no more than one message per channel per second. We allow bursts over that limit for short periods. However, if your app continues to exceed the limit over a longer period of time it will be rate limited. Different API methods have other rate limits โ€“ be sure to check the limits and test that your application has a graceful fallback if it should hit those limits.

-

If you go over these limits, Slack will start returning a HTTP 429 Too Many Requests error, a JSON object containing the number of calls you have been making, and a Retry-After header containing the number of seconds until you can retry.

-

Hereโ€™s a very basic example of how one might deal with rate limited requests.

-
import os
-import time
-from slack import WebClient
-from slack.errors import SlackApiError
-
-client = WebClient(token=os.environ["SLACK_API_TOKEN"])
-
-# Simple wrapper for sending a Slack message
-def send_slack_message(channel, message):
-  return client.chat_postMessage(
-    channel=channel,
-    text=message
-  )
-
-# Make the API call and save results to `response`
-channel = "#random"
-message = "Hello, from Python!"
-# Do until being rate limited
-while True:
-  try:
-    response = send_slack_message(channel, message)
-  except SlackApiError as e:
-    if e.response.status_code == 429:
-      # The `Retry-After` header will tell you how long to wait before retrying
-      delay = int(e.response.headers['Retry-After'])
-      print(f"Rate limited. Retrying in {delay} seconds")
-      time.sleep(delay)
-      response = send_slack_message(channel, message)
-    else:
-      # other errors
-      raise e
-
-
-

See the documentation on Rate Limiting for more info.

-
-
- - -
-
-
- -
-
- -
-

- ยฉ 2015- Slack Technologies, LLC and contributors -

-
- - - - - \ No newline at end of file diff --git a/docs-v2/changelog.html b/docs-v2/changelog.html deleted file mode 100644 index 3914f92d4..000000000 --- a/docs-v2/changelog.html +++ /dev/null @@ -1,754 +0,0 @@ - - - - - - Changelog — slackclient (Legacy Python Slack SDK) - - - - - - - - - - - - - - - - - - -
- - - - - - - slackclient (Legacy Python Slack SDK) - - -
- - -
-
- - - - - -
- -
-
-

Changelogยถ

-
-

v3.0.0 (2020-11-09)ยถ

-

This is the first stable version of slack_sdk v3. The remarkable updates in this major version are:

-
    -
  • Newly added OAuth flow support

  • -
  • Better Async/sync separation for WebClient and WebhookClient

  • -
  • Renamed packages (from slack to slack_sdk) with deprecation warnings

  • -
-

Refer to v3.0.0 milestone and the website for details. If youโ€™re a slackclient user, the migration guide for slackclient v2.x users is available at https://slack.dev/python-slack-sdk/v3-migration/

-
-
-

v2.9.3 (2020-10-20)ยถ

-

Refer to v2.9.3 milestone to know the complete list of the issues resolved by this release.

-

Updates

-
    -
  1. [Block Kit] #851 #852 Set default_type for HeaderBlock text - Thanks @fwump38

  2. -
  3. [Block Kit] #853 #854 Enable to use input blocks in Home tab views - Thanks @fwump38

  4. -
  5. [RTMClient] #857 #846 RTMClient does not pass timeout value to WebClient - Thanks @Luden @seratch

  6. -
-
-
-

v2.9.2 (2020-10-09)ยถ

-

Refer to v2.9.2 milestone to know the complete list of the issues resolved by this release.

-

Updates

-
    -
  1. [Block Kit] #841 Dispatch Action in Input blocks - Thanks @seratch

  2. -
  3. [WebClient] #838 Add apps.event.authorizations.list and other APIs - Thanks @seratch

  4. -
  5. [WebClient][WebhookClient] #829 Improve error body parser to handle no charset responses - Thanks @adamchainz @seratch

  6. -
  7. [Block Kit] #824 Correct text field validation in Header blocks - Thanks @seratch

  8. -
-
-
-

v2.9.1 (2020-09-23)ยถ

-

Refer to v2.9.1 milestone to know the complete list of the issues resolved by this release.

-

Updates

-
    -
  1. [WebClient][WebhookClient] #820 #821 #822 The proxy option in WebClient/WebhookClient no longer works - Thanks @seratch

  2. -
-
-
-

v2.9.0 (2020-09-17)ยถ

-

Refer to v2.9.0 milestone to know the complete list of the issues resolved by this release.

-

Updates

-
    -
  1. [WebClient] #811 Add workflows.* API support - Thanks @misscoded

  2. -
  3. [WebClient] #810 #809 Only set default filename in files_upload if file is an instance of str - Thanks @csaska

  4. -
-
-
-

v2.8.2 (2020-09-04)ยถ

-

Refer to v2.8.2 milestone to know the complete list of the issues resolved by this release.

-

Updates

-
    -
  1. [WebClient] #795 #794 Add admin.conversations.* API methods in WebClient/AsyncWebClient - Thanks @ruberVulpes

  2. -
  3. [WebClient] #796 Fix a link to the Static options documentation - Thanks @Jamim

  4. -
-
-
-

v2.8.1 (2020-08-28)ยถ

-

Refer to v2.8.1 milestone to know the complete list of the issues resolved by this release.

-

Updates

-
    -
  1. [WebClient] #778 #779 Adding support for View objects for views.push/update/publish - Thanks @ruberVulpes

  2. -
  3. [WebClient] #786 Fix admin.conversations.restrictAccess.* methods to match documentation - Thanks @ruberVulpes

  4. -
-
-
-

v2.8.0 (2020-08-06)ยถ

-

Refer to v2.8.0 milestone to know the complete list of the issues resolved by this release.

-

New Features

-
    -
  1. [WebClient] #765 #766 Introduce AsyncWebClient/AsyncWebhookClient providing coroutines - Thanks @seratch

  2. -
  3. [Block Kit] #767 #768 Add โ€œheaderโ€ block support - Thanks @mwbrooks

  4. -
-

Updates

-
    -
  1. [WebClient] #738 Add HTTP_PROXY, HTTPS_PROXY env variable support in async WebClient - Thanks @iamtofr @seratch

  2. -
  3. [WebClient] #769 #773 Enable User-Agent to have additional info part - Thanks @seratch

  4. -
  5. [WebClient] #770 #771 Fix a bug where files.uploadโ€™s file param doesnโ€™t accept bytes data - Thanks @seratch

  6. -
-
-
-

v2.7.3 (2020-07-20)ยถ

-

Refer to v2.7.3 milestone to know the complete list of the issues resolved by this release.

-

Updates

-
    -
  1. [WebClient] #754 Fix #729 Add admin.conversations.restrictAccess.*, conversations.mark API - Thanks @ruberVulpes @kian2attari

  2. -
  3. [WebClient] #758 Fix #757 Add admin.usergroups.addTeams, calls.participants.remove API - Thanks @seratch

  4. -
  5. [WebClient] #727 Fix #645 Unclosed client session - Thanks @NoAnyLove @jourdanrodrigues

  6. -
  7. [WebClient] #745 Fix #744 a validation logic bug in DatePickerElement - Thanks @dzudi941

  8. -
  9. [WebClient] #752 Fix #733 Better error handling when getting TimeoutError in RTMClient#start() - Thanks @liorblob @seratch

  10. -
  11. [WebClient] #751 Fix #718 by handling unexpected response body format - Thanks @jeffbuswell @seratch

  12. -
-
-
-

v2.7.2 (2020-06-23)ยถ

-

Refer to v2.7.2 milestone to know the complete list of the issues resolved by this release.

-

Updates

-
    -
  1. [WebClient] Fix #728 by adding bytearray support in files_upload (sync mode) - Thanks @sofya-salmanova @seratch

  2. -
  3. [WebClient] #726 Fix InputBlock.hint validation failure - Thanks @jourdanrodrigues

  4. -
  5. [WebClient] #723 Correct the default value of InputBlock.label, hint - Thanks @jourdanrodrigues

  6. -
-
-
-

v2.7.1 (2020-06-04)ยถ

-

This release includes the fixes for regression bugs in WebClient since v2.6.0. Refer to v2.7.1 milestone to know the complete list of the issues resolved by this release.

-

Updates

-
    -
  1. [WebClient] #716 #712 Support timeout in sync sync web clients - Thanks @DanialErfanian @seratch

  2. -
  3. [WebClient] #713 Support custom SSL context in sync sync web clients - Thanks @austinbutler

  4. -
  5. [WebClient] #715 #714 Support proxy in sync sync web clients - Thanks @austinbutler @seratch

  6. -
-
-
-

v2.7.0 (2020-06-02)ยถ

-

Refer to v2.7.0 milestone to know the complete list of the issues resolved by this release.

-

New Features

-
    -
  1. [WebhookClient] #707 #270 #531 Add WebhookClient for Incoming Webhooks & response_url - Thanks @seratch @chubz @Ambro17

  2. -
-

Updates

-
    -
  1. [WebClient] #704 #695 Add calls_* methods to WebClient and CallBlock in Block Kit classes - Thanks @seratch

  2. -
  3. [WebClient] #710 #536 Allow Tokens to be specified per request - Thanks @seratch

  4. -
  5. [WebClient] #709 #708 Add default_to_current_conversation in conversations_select elements - Thanks @seratch

  6. -
-
-
-

v2.6.2 (2020-05-28)ยถ

-

Refer to v2.6.2 milestone to know the complete details of this release.

-

Updates

-
    -
  1. [WebClient] #705 WebClientโ€™s paginated API calls may fail with no params - Thanks @seratch

  2. -
-
-
-

v2.6.1 (2020-05-24)ยถ

-

This patch release is a quick fix for #701, a major issue that affected RTMClient users in v2.6.0. The malfunction was introduced by #667 trying to address #558 #619. Those issues were reopened and will be resolved by another approach. Refer to v2.6.1 milestone to know the complete list of the issues resolved by this release.

-

Updates

-
    -
  1. [RTMClient] #701 RTMClient drops some messages when they come in rapid succession - Thanks @pbrackin @seratch

  2. -
-
-
-

v2.6.0 (2020-05-21)ยถ

-

Refer to v2.6.0 milestone to know the complete list of the issues resolved by this release.

-

New Features

-
    -
  1. [Block Kit] #659 Add complete supports for Block Kit components and fixed a few existing bugs as well (#500 #519 #623 #632 #635 #639 #676 #699) - Thanks @seratch @diurnalist @ruberVulpes @jeremyschulman @e271828- @RodneyU215

  2. -
  3. [Signature] #686 Add slack.signature.SignatureVerifier for request verification - Thanks @seratch

  4. -
  5. [WebClient] #682 Add missing Grid admin APIs (admin.usergroups.*, admin.users.*, admin.apps.*) - Thanks @stevengill @seratch

  6. -
-

Updates

-
    -
  1. [WebClient][RTMClient] Fixed a bunch of the currency issues this SDK had (#429 #463 #492 #497 #530 #569 #605 #613 #626 #630 #631 #633 #669) - Thanks @seratch @aaguilartablada @aoberoi @stevengill @marshallino16

  2. -
  3. [WebClient] #681 #560 Enable using bool values for request parameters - Thanks @roman-kachanovsky @seratch

  4. -
  5. [WebClient] #661 #678 Improve handling of required โ€œidsโ€ parameters (e.g., channel_ids, users) - Thanks @seratch

  6. -
  7. [WebClient] #680 Add non-conversation API deprecation warnings - Thanks @seratch

  8. -
  9. [WebClient] #671 #670 Enable passing None values for request parameters (they used to result in errors) - Thanks @yuji38kwmt @seratch

  10. -
  11. [WebClient] #673 Fix #672 files.upload fails with a filepath containing multi byte chars - Thanks @yuji38kwmt @seratch

  12. -
  13. [WebClient] #656 Fix #594 preview_image for files.remote.add API method is not properly supported - Thanks @Eothred @seratch

  14. -
  15. [Maintenance] #618 Add py.typed file to package distribution - Thanks @JKillian

  16. -
  17. [WebClient] #599 Strip token string parameters of whitespace - Thanks @TheFrozenFire

  18. -
  19. [WebClient] #692 Fix superfluous_charset warnings since v2.4.0 - Thanks @seratch

  20. -
  21. [WebClient] #652 Update oauth_v2_access to include redirect_uri (as optional) - Thanks @tomasreimers

  22. -
-
-
-

v2.5.0 (2019-12-09)ยถ

-

New Features

-
    -
  1. [WebClient] Adding new oauth.v2.access Web API method. #577

  2. -
-
-
-

v2.4.0 (2019-11-27)ยถ

-

New Features

-
    -
  1. [WebClient] Adding new admin.* Web API methods. #571

  2. -
-

Updates -1. [WebClient] Weโ€™re no longer validating token types for Web API methods. Improves compatibility with granular bot permissions. #568 (Thanks @Smotko) -2. [WebClient] Correcting typos in descriptions #554 (Thanks @phamk) -3. [WebClient] Fixed โ€˜iteractingโ€™ typo in library file headers #564 (Thanks @acabey) -4. [Message Builders] Remove value from LinkButtonElement #563 (Thanks @pedroma)

-
-
-

v2.3.1 (2019-10-29)ยถ

-

Updates

-
    -
  1. [WebClient] Fixing a regression that causes the client to close sessions prematurely. #544 (Thanks @fatih-acar!)

  2. -
  3. [WebClient] Adding required missing view param to views.update Web API method. #542

  4. -
-
-
-

v2.3.0 (2019-10-22)ยถ

-

New Features

-
    -
  1. [WebClient] Adding new views.publish Web API method. #540

  2. -
-

Updates

-
    -
  1. [WebClient] Some server responses donโ€™t return json. Correcting initial assumption. #540

  2. -
  3. [Maintenance] Add py.typed to mark the library to support type hinting #524s

  4. -
-
-
-

v2.2.1 (2019-10-08)ยถ

-

Updates

-
    -
  1. [Docs] Fix Indentation of Code Snippets in README.md #525 (Thanks @abhishekjiitr)

  2. -
  3. [WebClient] Fix Web Client custom iterator #521 (Thanks @smaeda-ks)

  4. -
  5. [WebClient] Oauth previously failed to pass along credentials properly. This is fixed now. #527

  6. -
  7. [WebClient] When a SlackApiError occurs weโ€™re now passing the entire SlackResponse into the exception. #527

  8. -
-
-
-

v2.2.0 (2019-09-25)ยถ

-

New Features

-
    -
  1. [WebClient] Adding new admin and remote files API methods. #501

  2. -
  3. [WebClient] Adding new view API methods. #517

  4. -
-

Updates

-
    -
  1. [Message Builders] Update BlockAttachment to not send invalid JSON due to fields attribute #473 (Thanks @paul-griffith)

  2. -
  3. [Docs] Add RTM section for docs v2 #477 (Thanks @shanedewael)

  4. -
  5. [Docs] Fix typo; recieved -> received #478 (Thanks @joakimnordling)

  6. -
  7. [Docs] Fix block kit link & update docs #484 (Thanks @clavin)

  8. -
  9. [RTMClient] Return callback from RTMClient.run_on #490 (Thanks @clavin)

  10. -
  11. [Docs] Fix link to Auth Guide in readme #498 (Thanks @asherf)

  12. -
  13. [Docs] Fix missing word and typo #512 (Thanks @marks)

  14. -
  15. [Message Builders] bugfix for value length in button elements #514 (Thanks @avanderm)

  16. -
  17. [Docs] Fixes formatting #515 (Thanks @vpetersson)

  18. -
  19. [Docs] Improve a code snippet on README #516 (Thanks @seratch)

  20. -
  21. [WebClient] Fixed an OAuth Headers bug and made the token param optional. #517

  22. -
-
-
-

v2.1.0 (2019-07-01)ยถ

-

New Features

-
    -
  1. Type-hinted helper classes for building messages in v2 #400 (Thanks @paul-griffith)

  2. -
-

Breaking Changes

-
    -
  1. [RTMClient] Converted the RTMClient#typing() function to async #446

  2. -
-

Updates

-
    -
  1. [RTMClient] Handle case in which aiohttp closes the websocket due to lack of ping responses. #453 (Thanks @flyte)

  2. -
  3. Modify package identifier in user agent to match v1.x identifier #418 (Thanks @aoberoi)

  4. -
  5. [WebClient] Fixed typo in Scheduled message #428 & #435 (Thanks @splinterific)

  6. -
  7. Transform install_requires of โ€˜aiodnsโ€™ into extras_require. #440 (Thanks @staticdev)

  8. -
-

Thank you!! -To everyone whoโ€™s opened, commented or reacted to an issue; this project is better because of you! -Thank you for helping the Slack community!

-
-
-

v2.0.0 (2019-04-29)ยถ

-

Original RFC

-

v2 PR

-

New Features

-
    -
  1. Client Decomposition: Weโ€™ve split the client into two.

  2. -
-
-
    -
  1. WebClient: A HTTP client focused on Slackโ€™s Web API.

  2. -
  3. RTMClient: A websocket client focused on Slackโ€™s RTM API.

  4. -
-
-
    -
  1. RTMClient: Completely redesigned, this client allows you to link your applicationโ€™s callbacks to corresponding Slack events.

  2. -
  3. WebClient: The WebClient now provides built-in methods for Slackโ€™s Web API. These methods act as helpers enabling you to focus less on how the request is constructed. Here are a few things that this provides:

  4. -
-
-
    -
  1. Basic information about each method through the docstring.

  2. -
  3. Easy File Uploads: You can now pass in the location of a file and the library will handle opening and retrieving the file object to be transmitted.

  4. -
  5. Token type validation: This gives you better error messaging when youโ€™re attempting to consume an api method that your token doesnโ€™t have access to.

  6. -
  7. Constructs requests using Slackโ€™s preferred HTTP methods and content-types.

  8. -
-
-

Breaking Changes: -If youโ€™re migrating from v1.x of slackclient to v2.x, Please follow our migration guide to ensure your app continues working after updating.

-

Check out the Migration Guide here!

-

Thank you! -This release would not have been possible without the support of our community. Thank you to everyone whoโ€™s contributed to this release.

-
-
-

v1.3.1 (2019-02-28)ยถ

-
    -
  • Lock websocket-client version to < 0.55.0: temp fix for #385

  • -
-
-
-

v1.3.0 (2018-09-11)ยถ

-

## New Features -- Adds support for short lived tokens and automatic token refresh #347 (Thanks @roach!)

-

## Other -- update RTM rate limiting comment and error message #308 (Thanks @benoitlavigne!) -- Use logging instead of traceback #309 (Thanks @harlowja!) -- Remove Python 3.3 from test environments #346 (Thanks @roach!) -- Enforced linting when using VSCode. #347 (Thanks @roach!)

-
-
-

v1.2.1 (2018-03-26)ยถ

-
    -
  • Added rate limit handling for rtm connections (thanks @jayalane!)

  • -
-
-
-

v1.2.0 (2018-03-20)ยถ

-
    -
  • You can now tell the RTM client to automatically reconnect by passing auto_reconnect=True

  • -
-
-
-

v1.1.3 (2018-03-01)ยถ

-
    -
  • Fixed another API param encoding bug. It encodes things properly now.

  • -
-
-
-

v1.1.2 (2018-01-31)ยถ

-
    -
  • Fixed an encoding issue which was encoding some Web API params incorrectly (sorry)

  • -
-
-
-

v1.1.1 (2018-01-30)ยถ

-
-
    -
  • Adds HTTP response headers to api_call responses to expose things like rate limit info

  • -
  • Moves token into auth header rather than request params

  • -
-
-
-
-

v1.1.0 (2017-11-21)ยถ

-
-
    -
  • Aadds new SlackClientError and ResponseParseError types to describe errors - thanks @aoberoi!

  • -
  • Fix Build Error (#245) - thanks @stasfilin!

  • -
  • include email as user property (#173) - thanks @acaire!

  • -
  • Add http reply into slack login and slack connection error (#216) - thanks @harlowja!

  • -
  • Removed unused exception class (#233)

  • -
  • Fix rtm_send_message bug (#225) - thanks @kt5356!

  • -
  • Allow use of custom parameters on rtm_connect() (#210) - thanks @kamushadenes!

  • -
  • Fix link to rtm.connect docs (#223) - @sampart!

  • -
-
-
-
-

v1.0.9 (2017-08-31)ยถ

-
-
    -
  • Fixed rtm_send_message ID bug introduced in 1.0.8

  • -
-
-
-
-

v1.0.8 (2017-08-31)ยถ

-
-
    -
  • Added rtm.connect support

  • -
-
-
-
-

v1.0.7 (2017-08-02)ยถ

-
-
    -
  • Fixes an issue where connecting over RTM to large teams may result in โ€œWebsocket URL expiredโ€ errors

  • -
  • A load of packaging improvements

  • -
-
-
-
-

v1.0.6 (2017-06-12)ยถ

-
-
    -
  • Added proxy support (thanks @timfeirg!)

  • -
  • Tidied up docs (thanks @schlueter!)

  • -
  • Added tox settings for Python 3 testing (thanks @cclauss!)

  • -
-
-
-
-

v1.0.5 (2017-01-23)ยถ

-
-
    -
  • Allow RTM Channel.send_message to reply to a thread

  • -
  • Index users by ID instead of Name (non-breaking change)

  • -
  • Added timeout to api calls.

  • -
  • Fixed a typo about token access in auth.rst, thanks @kelvintaywl!

  • -
  • Added Message Threads to the docs

  • -
-
-
-
-

v1.0.4 (2016-12-15)ยถ

-
-
    -
  • fixed the ability to search for a user by ID

  • -
-
-
-
-

v1.0.3 (2016-12-13)ยถ

-
-
    -
  • fixed an issue causing RTM connections to fail for large teams

  • -
-
-
-
-

v1.0.2 (2016-09-22)ยถ

-
-
    -
  • removed unused ping counter

  • -
  • fixed contributor guidelines links

  • -
  • updated documentation

  • -
  • Fix bug preventing API calls requiring a file ID

  • -
  • Removes files from api_calls before JSON encoding, so the request is properly formatted

  • -
-
-
-
-

v1.0.1 (2016-03-25)ยถ

-
-
    -
  • fix for __eq__ comparison in channels using โ€˜#โ€™ in channel name

  • -
  • added copyright info to the LICENSE file

  • -
-
-
-
-

v1.0.0 (2016-02-28)ยถ

-
-
    -
  • the api_call function now returns a decoded JSON object, rather than a JSON encoded string

  • -
  • some api_call calls now call actions on the parent server object: -- im.open -- mpim.open, groups.create, groups.createChild -- channels.create, channels.join`

  • -
-
-
-
-

v0.18.0 (2016-02-21)ยถ

-
-
    -
  • Moves to use semver for versioning

  • -
  • Adds support for private groups and MPDMs

  • -
  • Switches to use requests instead of urllib

  • -
  • Gets Travis CI integration working

  • -
  • Fixes some formatting issues so the code will work for python 2.6

  • -
  • Cleans up some unused imports, some PEP-8 fixes and a couple bad default args fixes

  • -
-
-
-
-

v0.17.0 (2016-02-15)ยถ

-
-
-
-
-
- - -
-
-
- -
-
- -
-

- ยฉ 2015- Slack Technologies, LLC and contributors -

-
- - - - - \ No newline at end of file diff --git a/docs-v2/conversations.html b/docs-v2/conversations.html deleted file mode 100644 index bebbc3cbf..000000000 --- a/docs-v2/conversations.html +++ /dev/null @@ -1,381 +0,0 @@ - - - - - - Conversations API — slackclient (Legacy Python Slack SDK) - - - - - - - - - - - - - - - - - - -
- - - - - - - slackclient (Legacy Python Slack SDK) - - -
- - -
-
- - - - - -
- -
-
-

Conversations APIยถ

-

The Slack Conversations API provides your app with a unified interface to work with all the channel-like things encountered in Slack; public channels, private channels, direct messages, group direct messages, and our newest channel type, Shared Channels.

-

See Conversations API docs for more info.

-
-
-

Direct messagesยถ

-

The conversations_open method opens either a 1:1 direct message with a single user or a a multi-person direct message, depending on the number of users supplied to the users parameter.

-

For public or private channels, use the ``conversations_create`` method.

-

Provide a users parameter as an array with 1 to 8 user IDs to open or resume a conversation. Providing only 1 ID will create a direct message. Providing more will create a new multi-party DM or resume an existing conversation.

-

Subsequent calls to conversations_open with the same set of users will return the already existing conversation.

-
import os
-from slack import WebClient
-
-client = WebClient(token=os.environ["SLACK_API_TOKEN"])
-response = client.conversations_open(users=["W123456789", "U987654321"])
-
-
-

See conversations.open additional info.

-
-
-
-

Creating channelsยถ

-

Creates a new channel, either public or private. The name parameter is required, may contain numbers, letters, hyphens, and underscores, and must contain fewer than 80 characters. To make the channel private, set the option is_private parameter to True.

-
import os
-from slack import WebClient
-from time import time
-
-client = WebClient(token=os.environ["SLACK_API_TOKEN"])
-channel_name = f"my-private-channel-{round(time())}"
-response = client.conversations_create(
-  name=channel_name,
-  is_private=True
-)
-channel_id = response["channel"]["id"]
-response = client.conversations_archive(channel=channel_id)
-
-
-

See conversations.create additional info.

-
-
-
-

Getting more informationยถ

-

To retrieve a set of metadata about a channel (public, private, DM, or multi-party DM), use conversations_info. The channel parameter is required and must be a valid channel ID. The optional include_locale boolean parameter will return locale data, which may be useful if you wish to return localized responses. The include_num_members boolean parameter will return the number of people in a channel.

-
import os
-from slack import WebClient
-
-client = WebClient(token=os.environ["SLACK_API_TOKEN"])
-response = client.conversations_info(
-  channel="C031415926",
-  include_num_members=1
-)
-
-
-

See conversations.info for more info.

-
-
-
-

Listing conversationsยถ

-

To get a list of all the conversations in a workspace, use conversations_list. By default, only public conversations are returned; use the types parameter specify which types of conversations youโ€™re interested in (Note: types is a string of comma-separated values)

-
import os
-from slack import WebClient
-
-client = WebClient(token=os.environ["SLACK_API_TOKEN"])
-response = client.conversations_list()
-conversations = response["channels"]
-
-
-

Use the types parameter to request additional channels, including public_channel, private_channel, mpim, and im. This parameter is a string of comma-separated values.

-
import os
-from slack import WebClient
-
-client = WebClient(token=os.environ["SLACK_API_TOKEN"])
-response = client.conversations_list(
-  types="public_channel, private_channel"
-)
-
-
-

See conversations.list for more info.

-
-
-
-

Leaving a conversationยถ

-

To leave a conversation, use conversations_leave with the required channel param containing the ID of the channel to leave.

-
import os
-from slack import WebClient
-
-client = WebClient(token=os.environ["SLACK_API_TOKEN"])
-response = client.conversations_leave(channel="C27182818")
-
-
-

See conversations.leave for more info.

-
-
-
-

Getting membersยถ

-

To get a list of the members of a conversation, use conversations_members with the required channel parameter.

-
import os
-from slack import WebClient
-
-client = WebClient(token=os.environ["SLACK_API_TOKEN"])
-response = client.conversations_members(channel="C16180339")
-user_ids = response["members"]
-
-
-

See conversations.members for more info.

-
-
- - -
-
-
- -
-
- -
-

- ยฉ 2015- Slack Technologies, LLC and contributors -

-
- - - - - \ No newline at end of file diff --git a/docs-v2/faq.html b/docs-v2/faq.html deleted file mode 100644 index b6456586e..000000000 --- a/docs-v2/faq.html +++ /dev/null @@ -1,340 +0,0 @@ - - - - - - Frequently Asked Questions — slackclient (Legacy Python Slack SDK) - - - - - - - - - - - - - - - - - - -
- - - - - - - slackclient (Legacy Python Slack SDK) - - -
- - -
-
- - - - - -
- -
-
-

Frequently Asked Questionsยถ

-
-

I cannot install slackclientโ€ฆยถ

-

We recommend using virtualenv (venv) to set up your Python runtime.

-
# Create a dedicated virtual env for running your Python scripts
-python -m venv env
-
-# Run env\Scripts\activate on Windows OS
-source env/bin/activate
-
-# Install slackclient PyPI package
-pip install "slackclient>=2.0"
-
-# Set your token as an env variable (`set` command for Windows OS)
-export SLACK_API_TOKEN=xoxb-***
-
-
-

Then, verify the following code works on the Python REPL (you can start it by just python).

-
import os
-import logging
-from slack import WebClient
-logging.basicConfig(level=logging.DEBUG)
-client = WebClient(token=os.environ["SLACK_API_TOKEN"])
-res = client.api_test()
-
-
-

If you encounter an error saying AttributeError: module 'slack' has no attribute 'WebClient', run pip list. If you find both slackclient and slack in the output, try removing slack by pip uninstall slack and reinstalling slackclient.

-
-
-

Should I go with run_async?ยถ

-

For most cases, we recommend going with run_async=False mode. So, the default is False.

-

If your application turns run_async on, the app should follow right and efficient ways to use asyncioโ€™s non-blocking event loops and aiohttp. Also, consider using async frameworks and their appropriate runtime. Running event loops along with Flask or similar may not be a good fit.

-

If you have to simultaneously run WebClient with run_async=True outside an event loop for some reason, sharing a single WebClient instance doesnโ€™t work for you. Create an instance every time you run the code. The run_async=False mode doesnโ€™t have such issues.

-
-
-

I found a bug!ยถ

-

Thatโ€™s great! Thank you. Let us know on the Issue Tracker. If youโ€™re feeling particularly ambitious, why not submit a pull request with a bug fix?

-
-
-

Thereโ€™s a feature missing!ยถ

-

Thereโ€™s always something more that could be added! You can let us know in the Issue Tracker to start a discussion around the proposed feature, thatโ€™s a good start. If youโ€™re feeling particularly ambitious, why not write the feature yourself, and submit a pull request! We love feedback and we love help and we donโ€™t bite. Much.

-
-
-

How do I contribute?ยถ

-

What an excellent question. First of all, please have a look at our general contributing guidelines.

-

All done? Great! While weโ€™re super excited to incorporate your new feature, there are a couple of things we want to make sure youโ€™ve given thought to.

-
    -
  • Please write unit tests for your new code. But donโ€™t just aim to increase the test coverage, rather, we expect you -to have written thoughtful tests that ensure your new feature will continue to work as expected, and to help future -contributors to ensure they donโ€™t break it!

  • -
  • Please document your new feature. Think about concrete use cases for your feature, and add a section to the -appropriate document, including a complete sample program that demonstrates your feature. Donโ€™t forget to update -the changelog in changelog.rst!

  • -
-

Including these two items with your pull request will totally make our dayโ€”and, more importantly, your future usersโ€™ days!

-

On that noteโ€ฆ

-
-
-

How do I compile the documentation?ยถ

-

This projectโ€™s documentation is generated with Sphinx. If you are editing one of the many reStructuredText files in the docs-src folder, youโ€™ll need to rebuild the documentation. It is recommended to run the following steps inside a virtualenv environment.

-
tox -e docs
-
-
-

Do be sure to add the docs folder and its contents to your pull request!

-
-
- - -
-
-
- -
-
- -
-

- ยฉ 2015- Slack Technologies, LLC and contributors -

-
- - - - - \ No newline at end of file diff --git a/docs-v2/genindex.html b/docs-v2/genindex.html deleted file mode 100644 index 99afb565d..000000000 --- a/docs-v2/genindex.html +++ /dev/null @@ -1,279 +0,0 @@ - - - - - - Index — slackclient (Legacy Python Slack SDK) - - - - - - - - - - - - - - - - - - -
- - - - - - - slackclient (Legacy Python Slack SDK) - - -
- - -
-
- - - - - -
- -
- -

Index

- -
- -
- - -
-
-
- -
-
- -
-

- ยฉ 2015- Slack Technologies, LLC and contributors -

-
- - - - - \ No newline at end of file diff --git a/docs-v2/index.html b/docs-v2/index.html deleted file mode 100644 index 87d04af7a..000000000 --- a/docs-v2/index.html +++ /dev/null @@ -1,338 +0,0 @@ - - - - - - Important Notice — slackclient (Legacy Python Slack SDK) - - - - - - - - - - - - - - - - - - -
- - - - - - - slackclient (Legacy Python Slack SDK) - - -
- - -
-
- - - - - -
- -
-
-
-
-

Important Noticeยถ

-

The slackclient PyPI project is in maintenance mode now and slack-sdk project is the successor. The v3 SDK provides more functionalities such as Socket Mode, OAuth flow module, SCIM API, Audit Logs API, better asyncio support, retry hanlders, and many more.

-

Refer to the migration guide to learn how to smoothly migrate your existing code.

-
-
-

slackclient (Legacy Python Slack SDK)ยถ

-

Slackโ€™s APIs allow anyone to build full featured integrations that extend -and expand the capabilities of your Slack workspace. These APIs allow you -to build applications that interact with Slack just like the people on your -team โ€“ they can post messages, respond to events that happen โ€“ as well -as build complex UIs for getting work done.

-

To make it easier for Python programmers to build Slack applications, weโ€™ve -provided this open source SDK. slackclient (Legacy Python Slack SDK) will let you get started building -Python apps as quickly as possible. The current version, 2.0, is -built for Python 3.6 and higher โ€“ if you need to target Python 2.x, you might -consider using v1 of the SDK.

-
-
-
-

Slack Platform Basicsยถ

-

If youโ€™re new to the Slack platform, we have a general purpose guide for building apps that isnโ€™t specific to any language or framework. Itโ€™s a great place to learn all about the concepts that go into building a great Slack app.

-

Before you get started building on the Slack platform, you need to set up your appโ€™s configuration. This is where you define things like your appโ€™s permissions and the endpoints that Slack should use for interacting with the backend you will build with Python.

-

The app configuration page is also where you will acquire the OAuth token you will use to call Slackโ€™s APIs. Treat this token with care, just like you would a password, because it has access to workspace and can potentially read and write data to and from it.

-
-
-

Installationยถ

-

We recommend using PyPI to install slackclient (Legacy Python Slack SDK)

-
pip install slackclient
-
-
-

Of course, you can always pull the source code directly into your project:

-
git clone https://github.com/slackapi/python-slackclient.git
-
-
-

And then, save a few lines of code as ./test.py.

-
# test.py
-import sys
-# Load the local source directly
-sys.path.insert(1, "./python-slackclient")
-# Enable debug logging
-import logging
-logging.basicConfig(level=logging.DEBUG)
-# Verify it works
-from slack import WebClient
-client = WebClient()
-api_response = client.api_test()
-
-
-

You can run the code this way.

-
python test.py
-
-
-

Itโ€™s also good to try on the Python REPL.

-
-
-

Getting Helpยถ

-

If you get stuck, weโ€™re here to help. The following are the best ways to get assistance working through your issue:

-
    -
  • Use our Github Issue Tracker for reporting bugs or requesting features.

  • -
  • Visit the Slack Developer Community for getting help using slackclient (Legacy Python Slack SDK) or just generally bond with your fellow Slack developers.

  • -
-
- - -
-
-
- -
-
- -
-

- ยฉ 2015- Slack Technologies, LLC and contributors -

-
- - - - - \ No newline at end of file diff --git a/docs-v2/metadata.html b/docs-v2/metadata.html deleted file mode 100644 index 14c0feb3c..000000000 --- a/docs-v2/metadata.html +++ /dev/null @@ -1,273 +0,0 @@ - - - - - - <no title> — slackclient (Legacy Python Slack SDK) - - - - - - - - - - - - - - - - - - -
- - - - - - - slackclient (Legacy Python Slack SDK) - - -
- - -
-
- - - - - -
- -
- - -
-
-
- -
-
- -
-

- ยฉ 2015- Slack Technologies, LLC and contributors -

-
- - - - - \ No newline at end of file diff --git a/docs-v2/objects.inv b/docs-v2/objects.inv deleted file mode 100644 index cee9d18e0..000000000 Binary files a/docs-v2/objects.inv and /dev/null differ diff --git a/docs-v2/real_time_messaging.html b/docs-v2/real_time_messaging.html deleted file mode 100644 index f327637e8..000000000 --- a/docs-v2/real_time_messaging.html +++ /dev/null @@ -1,360 +0,0 @@ - - - - - - Real Time Messaging (RTM) — slackclient (Legacy Python Slack SDK) - - - - - - - - - - - - - - - - - - -
- - - - - - - slackclient (Legacy Python Slack SDK) - - -
- - -
-
- - - - - -
- -
-
-

Real Time Messaging (RTM)ยถ

-

The Real Time Messaging (RTM) API is a WebSocket-based API that allows you to receive events from Slack in real time and send messages as users.

-

If you prefer events to be pushed to your app, we recommend using the HTTP-based Events API instead. -The Events API contains some events that arenโ€™t supported in the RTM API (like app_home_opened event), -and it supports most of the event types in the RTM API. If youโ€™d like to use the Events API, you can use the Python Slack Events Adaptor.

-

The RTMClient allows apps to communicate with the Slack Platformโ€™s RTM API.

-

The event-driven architecture of this client allows you to simply link callbacks to their corresponding events. When an event occurs this client executes your callback while passing along any information it receives. We also give you the ability to call our web client from inside your callbacks.

-

In our example below, we watch for a message event that contains โ€œHelloโ€ and if its received, we call the say_hello() function. We then issue a call to the web client to post back to the channel saying โ€œHiโ€ to the user.

-
-

Configuring the RTM APIยถ

-

Events using the RTM API must use a classic Slack app (with a plain bot scope).

-

If you already have a classic Slack app, you can use those credentials. If you donโ€™t and need to use the RTM API, you can create a classic Slack app. You can learn more in the API documentation.

-

Also, even if the Slack app configuration pages encourage you to upgrade to the newer permission model, donโ€™t upgrade it and keep using the โ€œclassicโ€ bot permission.

-
-
-

Connecting to the RTM APIยถ

-
import os
-from slack import RTMClient
-
-@RTMClient.run_on(event="message")
-def say_hello(**payload):
-  data = payload['data']
-  web_client = payload['web_client']
-
-  if 'Hello' in data['text']:
-    channel_id = data['channel']
-    thread_ts = data['ts']
-    user = data['user'] # This is not username but user ID (the format is either U*** or W***)
-
-    web_client.chat_postMessage(
-      channel=channel_id,
-      text=f"Hi <@{user}>!",
-      thread_ts=thread_ts
-    )
-
-slack_token = os.environ["SLACK_API_TOKEN"]
-rtm_client = RTMClient(token=slack_token)
-rtm_client.start()
-
-
-
-
-

rtm.start vs rtm.connectยถ

-

By default, the RTM client uses rtm.connect to establish a WebSocket connection with Slack. The response contains basic information about the team and WebSocket url.

-

If youโ€™d rather use rtm.start to establish the connection, which provides more information about the conversations and users on the team, you can set the connect_method option to rtm.start when instantiating the RTM Client. Note that on larger teams, use of rtm.start can be slow and unreliable.

-
import os
-from slack import RTMClient
-
-@RTMClient.run_on(event="message")
-def say_hello(**payload):
-  data = payload['data']
-  web_client = payload['web_client']
-  if 'text' in data and 'Hello' in data['text']:
-    channel_id = data['channel']
-    thread_ts = data['ts']
-    user = data['user'] # This is not username but user ID (the format is either U*** or W***)
-
-    web_client.chat_postMessage(
-      channel=channel_id,
-      text=f"Hi <@{user}>!",
-      thread_ts=thread_ts
-    )
-
-slack_token = os.environ["SLACK_API_TOKEN"]
-rtm_client = RTMClient(
-  token=slack_token,
-  connect_method='rtm.start'
-)
-rtm_client.start()
-
-
-

Read the rtm.connect docs and the rtm.start docs for more details.

-
-
-

RTM Eventsยถ

-
{
-  'type': 'message',
-  'ts': '1358878749.000002',
-  'user': 'U023BECGF',
-  'text': 'Hello'
-}
-
-
-

See RTM Events for a complete list of events.

-
-
- - -
-
-
- -
-
- -
-

- ยฉ 2015- Slack Technologies, LLC and contributors -

-
- - - - - \ No newline at end of file diff --git a/docs-v2/search.html b/docs-v2/search.html deleted file mode 100644 index ba3b5d148..000000000 --- a/docs-v2/search.html +++ /dev/null @@ -1,293 +0,0 @@ - - - - - - Search — slackclient (Legacy Python Slack SDK) - - - - - - - - - - - - - - - - - - -
- - - - - - - slackclient (Legacy Python Slack SDK) - - -
- - -
-
- - - - - -
- -
-

Search

-
- -

- Please activate JavaScript to enable the search - functionality. -

-
-

- Searching for multiple words only shows matches that contain - all words. -

-
- - - -
- -
- -
- -
-
-
- -
-
- -
-

- ยฉ 2015- Slack Technologies, LLC and contributors -

-
- - - - - \ No newline at end of file diff --git a/docs-v2/searchindex.js b/docs-v2/searchindex.js deleted file mode 100644 index 967b77c34..000000000 --- a/docs-v2/searchindex.js +++ /dev/null @@ -1 +0,0 @@ -Search.setIndex({docnames:["about","auth","basic_usage","changelog","conversations","faq","index","metadata","real_time_messaging"],envversion:{"sphinx.domains.c":2,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":3,"sphinx.domains.index":1,"sphinx.domains.javascript":2,"sphinx.domains.math":2,"sphinx.domains.python":2,"sphinx.domains.rst":2,"sphinx.domains.std":1,sphinx:56},filenames:["about.rst","auth.rst","basic_usage.rst","changelog.rst","conversations.rst","faq.rst","index.rst","metadata.rst","real_time_messaging.rst"],objects:{},objnames:{},objtypes:{},terms:{"000002":[2,8],"000003":2,"0cb4bcd6e887b428e27e8059b6278b86ee661aaa":3,"1046cc2375a85a22e94573e2aad954ba7287c886":3,"111":1,"123456":2,"1234567890":2,"1358878749":8,"1476745373":2,"1476746830":2,"173":3,"1920":2,"200":2,"210":3,"216":3,"222":1,"223":3,"225":3,"233":3,"237":2,"245":3,"270":3,"3000":[1,2],"308":3,"309":3,"346":3,"347":3,"385":3,"400":3,"403":2,"404":2,"418":3,"428":3,"429":[2,3],"435":3,"440":3,"446":3,"453":3,"463":3,"473":3,"477":3,"478":3,"484":3,"490":3,"492":3,"497":3,"498":3,"500":3,"501":3,"512":3,"514":3,"515":3,"516":3,"517":3,"519":3,"521":3,"524":3,"525":3,"527":3,"530":3,"531":3,"536":3,"540":3,"542":3,"544":3,"554":3,"558":3,"560":3,"563":3,"564":3,"568":3,"569":3,"571":3,"577":3,"594":3,"599":3,"605":3,"613":3,"618":3,"619":3,"623":3,"626":3,"630":3,"631":3,"632":3,"633":3,"635":3,"639":3,"645":3,"652":3,"656":3,"659":3,"661":3,"667":3,"669":3,"670":3,"671":3,"672":3,"673":3,"676":3,"678":3,"680":3,"681":3,"682":3,"686":3,"692":3,"695":3,"699":3,"701":3,"704":3,"705":3,"707":3,"708":3,"709":3,"710":3,"712":3,"713":3,"714":3,"715":3,"716":3,"718":3,"723":3,"726":3,"727":3,"728":3,"729":3,"733":3,"738":3,"744":3,"745":3,"750319":2,"751":3,"752":3,"754":3,"757":3,"758":3,"765":3,"766":3,"767":3,"768":3,"769":3,"770":3,"771":3,"773":3,"778":3,"779":3,"786":3,"794":3,"795":3,"796":3,"7d01515cebc80918a29100b0e4793790eb83e7b9":3,"809":3,"810":3,"811":3,"820":3,"821":3,"822":3,"824":3,"829":3,"838":3,"841":3,"846":3,"851":3,"852":3,"853":3,"854":3,"857":3,"boolean":4,"break":[3,5],"byte":3,"case":[3,5],"char":3,"class":3,"default":[2,3,4,5,8],"export":[2,5],"final":1,"function":[1,3,6,8],"import":[1,2,3,4,5,8],"int":2,"long":2,"new":[1,2,3,4,5,6],"public":4,"return":[1,2,3,4],"short":[2,3],"static":3,"super":5,"switch":3,"true":[2,3,4,5],"try":[2,3,5,6],"while":[2,5,8],Added:3,Adding:3,And:6,But:5,For:[1,2,4,5],IDs:4,IMs:2,One:2,That:[2,5],The:[1,2,3,4,5,6,8],Then:[1,5],These:[3,6],UIs:6,Use:[3,4,6],__eq__:3,__main__:[1,2],__name__:[1,2],aadd:3,aaguilartablada:3,abhishekjiitr:3,abil:[1,3,8],abl:1,about:[1,2,3,4,5,6,8],abov:2,acabei:3,acair:3,acar:3,accept:[1,3],access:[0,1,2,3,6],access_token:1,accessori:2,accid:2,acquir:6,act:3,action:3,action_id:2,activ:5,actual:2,adamchainz:3,adaptor:8,add:[1,2,3,5],added:[3,5],adding:3,addit:[1,2,3,4],address:3,addteam:3,admin:3,affect:3,after:[1,2,3],agent:3,agre:1,agreement:0,aim:5,aiodn:3,aiohttp:[3,5],all:[1,2,4,5,6],allow:[2,3,6,8],along:[3,5,8],alreadi:[2,4,8],already_in_channel:2,also:[2,5,6,8],alt_text:2,alwai:[5,6],ambiti:5,ambro17:3,ani:[1,6,8],anoth:[2,3],anyon:6,aoberoi:3,api:[0,1,3,6],api_cal:[2,3],api_method:2,api_respons:[2,6],api_test:[5,6],app:[0,1,2,3,4,5,6,8],app_home_open:8,appear:2,appli:1,applic:[1,2,3,5,6],approach:3,appropri:5,architectur:8,archiv:2,aren:8,arg:[1,3],argument:2,around:5,arrai:4,articl:2,asherf:3,assert:2,assign:2,assist:6,assumpt:3,async:[3,5],asyncio:[5,6],asyncwebcli:3,asyncwebhookcli:3,attach:2,attempt:3,attribut:[2,3,5],attributeerror:5,audit:6,austinbutl:3,auth:[1,3],authent:[2,3],author:[1,3],auto_reconnect:3,automat:3,avail:[1,2,3],avanderm:3,averag:2,avoid:1,awesom:2,axe:2,back:8,backend:6,bad:3,base:8,basic:[1,3,8],basicconfig:[2,5,6],becaus:[3,6],becom:2,been:[1,2,3],befor:[2,3,6],begin:1,being:2,below:[2,8],benoitlavign:3,best:[2,6],better:[3,6],between:2,bin:5,bit:2,bite:5,block:[3,5],block_id:2,blockattach:3,bodi:3,bond:6,bool:3,bot:[1,2,3,8],both:5,box:2,broadcast:2,bug:[3,6],bugfix:3,build:[0,1,2,3,6],builder:[2,3],built:[1,3,6],bunch:3,burst:2,busi:2,button:[1,2,3],bytearrai:3,c031415926:4,c0xxxxxx:2,c0xxxxxxx:2,c0xxxxxxy:2,c16180339:4,c27182818:4,c3ukjtqac:2,cach:3,call:[1,3,4,6,8],callback:[2,3,8],callback_id:2,callblock:3,calls_:3,can:[1,2,3,5,6,8],cancel:2,cannot:2,capabl:6,care:6,caus:3,cclauss:3,chang:[1,2,3],changelog:5,channel:[3,8],channel_id:[3,4,8],channel_nam:4,channel_not_found:2,channnel:3,charact:4,charset:3,chat:2,chat_delet:2,chat_postephemer:2,chat_postmessag:[2,8],chat_upd:2,check:[1,2,3],chubz:3,classic:8,clavin:3,clean:3,click:1,client:[1,2,3,4,5,6,8],client_id:1,client_secret:1,clone:6,close:[2,3],code:[0,1,2,3,5,6],code_param:1,collect:2,com:[1,2,3,6],combin:2,come:3,comma:4,command:5,comment:[2,3],commit:3,commun:[3,6,8],comparison:3,compat:3,complet:[1,3,5,8],complex:[2,6],compon:3,compos:2,concept:6,concret:5,conduct:0,configur:[1,2,3,6],connect:3,connect_method:8,consid:[5,6],consider:2,construct:3,consum:3,contain:[2,3,4,8],content:[3,5],context:[2,3],continu:[2,3,5],contribut:[0,3],contributor:[0,3,5],convers:[2,3,8],conversations_arch:4,conversations_cr:4,conversations_info:[2,4],conversations_join:2,conversations_leav:[2,4],conversations_list:[2,4],conversations_memb:4,conversations_open:4,conversations_select:3,convert:3,copyright:3,coroutin:3,correct:3,correspond:[1,3,8],could:5,counter:3,coupl:[3,5],cours:6,cover:2,coverag:5,creat:[3,5,8],createchild:3,credenti:[1,3,8],csaska:3,currenc:3,current:6,custom:[2,3],d45285d2f1025899dcd65e259624ee73771f94bb:3,dai:5,danialerfanian:3,danni:2,data:[1,2,3,4,6,8],databas:1,datepickerel:3,deal:2,debug:[2,5,6],decod:3,decomposit:3,dedic:5,def:[1,2,8],default_to_current_convers:3,default_typ:3,defin:[1,6],delai:2,demonstr:5,depend:[1,4],deprec:3,descend:2,describ:3,descript:3,detail:[2,3,8],determin:1,dev:3,develop:[0,2,6],differ:2,direct:[1,2],directli:[2,6],discuss:5,dispatch:3,displai:2,distribut:3,diurnalist:3,doc:[3,4,5,8],docstr:3,document:[1,2,3,8],doe:3,doesn:[3,5],domain:2,don:[1,3,5,8],done:[5,6],door:2,driven:8,drop:3,due:[2,3],duplic:3,dynam:2,dzudi941:3,e271828:3,each:[2,3],easi:3,easier:6,edit:5,effect:1,effici:5,either:[2,4,8],element:[2,3],els:2,email:3,empti:1,enabl:[2,3,6],encod:3,encount:[4,5],encourag:8,endpoint:[1,2,6],enforc:3,ensur:[3,5],entir:[1,3],env:[3,5],environ:[1,2,3,4,5,8],environment:1,eothr:3,ephemer:2,error:[2,3,5],establish:8,even:8,event:[2,3,5,6],everi:[1,5],everyon:3,exampl:[1,2,8],exce:2,excel:5,except:[2,3],exchang:[1,2],excit:[2,5],exclud:2,exclude_archiv:2,execut:8,exist:[2,3,4,6],expand:6,expect:5,expir:3,expos:3,extend:6,extras_requir:3,f7bb8889580cc34471ba1ddc05afc34d1a5efa23:3,facilit:1,fail:[2,3],failur:3,fallback:2,fals:[2,5],far:2,fatih:3,featur:[1,3,6],feedback:5,feel:[2,5],fellow:6,felt:2,fetch:2,few:[3,6],fewer:4,field:[2,3],file:[3,5],file_com:2,filenam:3,filepath:3,files_upload:[2,3],find:[2,5],finish:2,first:[3,5],fit:5,fix:[3,5],flask:[1,2,5],flask_env:2,flow:[1,3,6],flyte:3,focu:3,focus:[2,3],folder:5,follow:[2,3,5,6],forget:[1,5],fork:2,form:2,format:[3,8],found:1,framework:[5,6],from:[0,1,2,3,4,5,6,8],full:[1,2,6],fulli:2,fun:2,futur:5,fwump38:3,gener:[1,5,6],german:2,get:[1,3],get_data:2,git:6,github:[3,6],give:[3,8],given:5,global:2,going:5,good:[5,6],grace:2,grant:1,granular:3,great:[5,6],greater:2,grid:3,griffith:3,group:[2,3,4],guest:2,guid:[3,6],guidelin:[3,5],had:[2,3],handl:[1,2,3],hanlder:6,happen:6,harlowja:3,has:[1,2,5,6],hash:2,haunt:2,have:[2,3,5,6,8],header:[2,3],headerblock:3,hello:[2,8],help:[3,5],helper:3,here:[2,3,6],higher:6,hint:3,hit:2,hole:2,home:3,hop:2,hotel:2,how:[1,2,3,6],howev:2,href:1,http:[1,2,3,6,8],http_proxi:3,https_proxi:3,hub:2,hurri:2,hyphen:4,iamtofr:3,identifi:3,ids:3,imag:2,image_url:2,importantli:5,improv:3,includ:[1,2,3,4,5],include_local:4,include_num_memb:4,incom:3,incorpor:5,incorrectli:3,increas:5,indent:3,index:3,indic:2,industri:1,info:[3,4],inform:[1,2,3,8],initi:[1,3],input:[2,3],inputblock:3,insert:6,insid:[2,5,8],install_requir:3,instanc:[3,5],instanti:8,instead:[2,3,8],integr:[2,3,6],intend:1,interact:[2,6],interest:4,interfac:[2,4],intern:3,introduc:3,invalid:[2,3],invalid_auth:2,invit:2,is_priv:4,is_valid_request:2,isn:6,issu:[3,5,6,8],item:[2,5],iter:3,iteract:3,its:[3,5,8],itself:2,jamim:3,jayalan:3,jeffbuswel:3,jeremyschulman:3,jkillian:3,joakimnordl:3,join:3,jourdanrodrigu:3,jpeg:2,json:[1,2,3],just:[1,2,5,6],kachanovski:3,kamushaden:3,keep:8,kei:[1,2],kelvintaywl:3,kian2attari:3,kind:2,kit:3,know:[1,3,5],kt5356:3,label:[2,3],lack:3,lambda:2,languag:6,larg:3,larger:8,later:1,layout:2,learn:[6,8],least:1,left:2,length:3,less:3,let:[0,1,2,5,6],letter:4,level:[2,5,6],librari:[2,3],licens:[0,3],like:[2,3,4,6,8],limit:3,line:6,linear:2,link:[1,3,8],linkbuttonel:3,lint:3,liorblob:3,list:[1,3,5,8],liter:2,live:3,load:[2,3,6],local:[3,4,6],localhost:[1,2],locat:3,lock:3,log:[2,3,5,6],logic:3,login:3,longer:[2,3],look:[2,5],loop:5,love:5,luden:3,made:3,mai:[2,3,4,5],maintain:0,mainten:[3,6],major:3,make:[2,4,5,6],make_respons:2,malfunct:3,mani:[2,5,6],map:2,mark:3,marshallino16:3,match:3,mayb:2,messag:[3,6],metadata:4,method:[1,3,4],might:[2,6],migrat:[3,6],mileston:3,miss:3,misscod:3,mode:[3,5,6],model:8,modifi:3,modul:[2,5,6],more:[1,2,5,6,8],most:[2,5,8],move:3,mpdm:3,mpim:[3,4],mrkdwn:2,much:5,multi:[3,4],must:[1,2,4,8],mwbrook:3,myapp:1,name:[2,3,4],nearli:2,need:[1,2,5,6,8],newer:8,newest:4,newli:3,noanylov:3,non:[3,5],none:3,note:[2,4,5,8],notif:2,now:[3,6],number:[2,4],oauth:[1,2,3,6],oauth_redirect:1,oauth_scop:1,oauth_v2_access:[1,3],object:[2,3],occur:[3,8],off:2,onc:[1,2],one:[1,2,5],onli:[2,3,4],onto:2,open:[3,4,6],option:[2,3,4,8],order:1,org:[],origin:[2,3],other:[1,2,3],our:[1,2,3,4,5,6,8],out:[1,2,3],output:5,outsid:5,over:[2,3],overlook:2,own:2,packag:[3,5],page:[1,6,8],pagin:3,param:[1,3,4],paramet:[1,2,3,4],parent:[2,3],parser:3,part:3,parti:4,particip:3,particularli:5,pass:[1,2,3,8],password:[1,6],patch:3,path:6,paul:3,payload:[1,2,8],pbrackin:3,pdf:2,pedroma:3,peopl:[2,4,6],pep:3,per:[2,3],period:2,permiss:[1,3,6,8],persist:1,person:[2,4],pexel:2,phamk:3,photo:2,ping:3,pip:[5,6],place:[2,6],plain:8,plain_text:2,plain_text_input:2,platform:[0,8],pleas:[1,3,5],point:[2,3],possibl:[3,6],post:[2,6,8],post_instal:1,postephemer:2,postmessag:2,potenti:[1,6],practic:2,pre:1,pre_instal:1,prefer:[3,8],prematur:3,prevent:3,preview_imag:3,previou:2,previous:3,primari:2,print:[1,2],privat:[3,4],private_channel:4,private_metadata:2,program:5,programm:6,project:[3,5,6],properli:3,properti:[2,3],propos:5,protocol:1,prototyp:2,proudli:0,provid:[1,2,3,4,6,8],proxi:3,public_channel:[2,4],publish:[1,3],pull:[5,6],purpos:[2,6],push:[3,8],pypi:[5,6],python3:2,python:[1,2,3,5,8],queri:1,quick:3,quickli:[2,6],rais:2,random:2,randomli:1,rapid:3,rate:3,ratelimit:[],rather:[3,5,8],react:3,reactions_add:2,reactions_remov:2,read:[1,6,8],readm:3,reason:5,rebuild:5,receiv:[3,8],reciev:3,recommend:[1,5,6,8],reconnect:3,redesign:3,redirect:1,redirect_uri:3,refer:[2,3,6],reflect:2,refresh:3,refus:1,regress:3,regular:2,reinstal:[1,5],releas:[2,3],releg:2,remark:3,remot:3,remov:[2,3,5],renam:3,reopen:3,repl:[5,6],repli:[2,3],reply_broadcast:2,report:6,request:[1,2,3,4,5,6],requir:[1,2,3,4],res:5,resolv:3,respond:[2,6],respons:[1,2,3,4,8],response_url:3,responseparseerror:3,restrictaccess:3,restructuredtext:5,result:[2,3],resum:4,retri:[2,6],retriev:[1,3,4],review:[1,2],rfc:3,right:5,roach:3,rodneyu215:3,roman:3,room:2,round:4,rout:[1,2],rowdi:2,rst:[3,5],rtm:3,rtm_client:8,rtm_connect:3,rtm_send_messag:3,rtmclient:[3,8],rubervulp:3,run:[1,2,5,6],run_on:[3,8],runtim:[1,5],sai:[2,5,8],said:2,salmanova:3,same:[2,4],sampart:3,sampl:5,save:[1,2,6],say_hello:8,schedul:3,schlueter:3,scim:6,scope:[1,8],script:5,sdk:3,search:3,second:2,secret:1,section:[1,2,3,5],see:[1,2,4,8],semver:3,send:[3,8],send_messag:3,send_slack_messag:2,separ:[3,4],seratch:3,server:3,session:3,set:[1,2,3,4,5,6,8],setup:1,shanedewael:3,share:[1,4,5],shortcut:2,should:[2,6],show:2,signatur:[2,3],signature_verifi:2,signatureverifi:[2,3],silent:2,similar:5,simpl:[1,2],simpli:[2,8],simultan:5,sinc:[2,3],singl:[2,4,5],site:2,slack:[1,2,3,4,5,8],slack_api_token:[2,4,5,8],slack_app:2,slack_bot_token:1,slack_client_id:1,slack_client_secret:1,slack_scop:1,slack_sdk:3,slack_signing_secret:2,slack_token:[2,8],slackapi:[3,6],slackapierror:[2,3],slackclient:3,slackclienterror:3,slackrespons:3,sleep:2,slightli:2,slow:8,smaeda:3,smoothli:6,smotko:3,snippet:3,social:2,socket:6,sofya:3,some:[2,3,5,8],someth:5,sometim:2,sorri:3,sourc:[1,5,6],special:2,specif:[2,6],specifi:[2,3,4],sphinx:5,splinterif:3,split:3,src:5,ssl:3,stabl:3,standard:1,star:2,start:[1,2,3,5,6],stasfilin:3,state:[1,2],staticdev:3,statu:2,status_cod:2,step:5,stevengil:3,store:[1,2],str:[2,3],string:[1,3,4],strip:3,stuck:[2,6],submiss:2,submit:[2,5],submitted_data:2,subsequ:4,succeed:1,success:[1,3],successor:6,superfluous_charset:3,suppli:4,support:[3,6,8],sure:[2,5],surfac:2,sync:3,sys:6,tab:3,tada:2,take:[1,2],target:6,team:[0,1,3,6,8],tell:[2,3],temp:3,test:[2,3,5,6],text:[2,3,8],than:[2,3,4],thank:[3,5],thefrozenfir:3,thei:[2,3,5,6],them:[1,2],therefor:2,thi:[1,2,3,4,5,6,8],thing:[2,3,4,5,6],think:5,those:[2,3,8],though:2,thought:5,thread:3,thread_t:[2,8],through:[3,6],thumbsup:2,tidi:3,time:[1,2,4,5],timelin:2,timeout:3,timeouterror:3,timestamp:2,timfeirg:3,titl:2,togeth:2,token:[2,3,4,5,6,8],tomasreim:3,too:2,tool:0,torrenc:2,total:5,tox:[3,5],traceback:3,tracker:[5,6],transform:3,transmit:3,travi:3,treat:[1,6],trigger_id:2,turn:5,two:[3,5],type:[2,3,4,8],typo:3,u023becgf:8,u0xxxxxxx:2,u987654321:4,ubervulp:[],unclos:3,underscor:4,unexpect:3,unifi:4,uninstal:5,unit:5,unreli:8,until:2,unus:3,updat:[3,5],upgrad:8,upload:3,url:[2,3,8],urllib:3,usag:1,use:[1,2,3,4,5,6,8],used:[2,3],useful:4,user:[1,2,3,4,5,8],user_id:[2,4],usergroup:3,usernam:8,users_list:2,uses:[2,8],using:[2,3,5,6,8],valid:[1,2,3,4],valu:[1,2,3,4],variabl:[1,3,5],venv:5,veri:2,verif:3,verifi:[1,5,6],version:[3,6],via:1,view:[2,3],view_id:2,view_submiss:2,views_open:2,views_upd:2,virtual:5,virtualenv:5,visibl:2,visit:6,vote:2,vpetersson:3,vscode:3,w123456789:4,wai:[2,5,6],wait:2,want:[2,5],warn:3,watch:8,web:[0,3,8],web_client:8,webclient:[1,2,3,4,5,6],webhook:3,webhookcli:3,webserv:1,websit:3,websocket:[3,8],well:[2,3,6],were:3,what:[2,5],when:[2,3,8],where:[1,3,6],which:[1,2,3,4,8],whitespac:3,who:3,whole:2,why:5,window:5,wish:[1,4],without:3,won:2,word:3,work:[1,2,3,4,5,6],workflow:3,workspac:[4,6],world:2,would:[1,3,6],wrapper:2,write:[1,5,6],written:5,xoxb:[1,2,5],xxxxx:1,yield:1,you:[0,1,2,3,4,5,6,8],your:[0,1,2,3,4,5,6,8],yourself:5,yuji38kwmt:3},titles:["About","Tokens & Installation","Basic Usage","Changelog","Conversations API","Frequently Asked Questions","Important Notice","<no title>","Real Time Messaging (RTM)"],titleterms:{"2016":3,"2017":3,"2018":3,"2019":3,"2020":3,"import":6,"public":2,There:5,about:0,ani:2,api:[2,4,8],ask:5,basic:[2,6],block:2,bug:5,call:2,cannot:5,changelog:3,channel:[2,4],compil:5,configur:8,connect:8,contribut:5,convers:4,creat:4,delet:2,develop:[],direct:4,document:5,emoji:2,event:8,featur:5,file:2,format:2,found:5,frequent:5,get:[2,4,6],help:6,how:5,info:2,inform:4,instal:[1,5,6],join:2,keep:1,kit:2,leav:[2,4],legaci:[0,6],limit:2,list:[2,4],member:[2,4],messag:[2,4,8],method:2,miss:5,modal:2,more:4,multipl:1,notic:6,open:2,platform:6,push:2,python:[0,6],question:5,rate:2,reaction:2,real:8,rtm:8,run_async:5,safe:1,sdk:[0,6],send:2,should:5,singl:1,slack:[0,6],slackclient:[0,5,6],start:8,team:2,thread:2,time:8,token:1,updat:2,upload:2,usag:2,web:2,workspac:1}}) \ No newline at end of file diff --git a/docs/.buildinfo b/docs/.buildinfo deleted file mode 100644 index b3bf41598..000000000 --- a/docs/.buildinfo +++ /dev/null @@ -1,4 +0,0 @@ -# Sphinx build info version 1 -# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. -config: ad89fb64a5900a5c9194f645c4af338b -tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/about.html b/docs/about.html deleted file mode 100644 index 2595a3761..000000000 --- a/docs/about.html +++ /dev/null @@ -1,254 +0,0 @@ - - - - - - About — Python Slack SDK - - - - - - - - - - - - - - - - - - -
- - - - - - - Python Slack SDK - - -
- - - - -
-

- ยฉ 2015- Slack Technologies, LLC and contributors -

-
- - - - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/aiohttp_version_checker.html b/docs/api-docs/slack_sdk/aiohttp_version_checker.html deleted file mode 100644 index ab5bbafd2..000000000 --- a/docs/api-docs/slack_sdk/aiohttp_version_checker.html +++ /dev/null @@ -1,119 +0,0 @@ - - - - - - -slack_sdk.aiohttp_version_checker API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.aiohttp_version_checker

-
-
-

Internal module for checking aiohttp compatibility of async modules

-
- -Expand source code - -
"""Internal module for checking aiohttp compatibility of async modules"""
-import logging
-from typing import Callable
-
-
-def _print_warning_log(message: str) -> None:
-    logging.getLogger(__name__).warning(message)
-
-
-def validate_aiohttp_version(
-    aiohttp_version: str,
-    print_warning: Callable[[str], None] = _print_warning_log,
-):
-    if aiohttp_version is not None:
-        elements = aiohttp_version.split(".")
-        if len(elements) >= 3:
-            # patch version can be a non-numeric value
-            major, minor, patch = int(elements[0]), int(elements[1]), elements[2]
-            if major <= 2 or (
-                major == 3 and (minor == 6 or (minor == 7 and patch == "0"))
-            ):
-                print_warning(
-                    "We highly recommend upgrading aiohttp to 3.7.3 or higher versions."
-                    "An older version of the library may not work with the Slack server-side in the future."
-                )
-
-
-
-
-
-
-
-

Functions

-
-
-def validate_aiohttp_version(aiohttp_version:ย str, print_warning:ย Callable[[str],ย None]ย =ย <function _print_warning_log>) -
-
-
-
- -Expand source code - -
def validate_aiohttp_version(
-    aiohttp_version: str,
-    print_warning: Callable[[str], None] = _print_warning_log,
-):
-    if aiohttp_version is not None:
-        elements = aiohttp_version.split(".")
-        if len(elements) >= 3:
-            # patch version can be a non-numeric value
-            major, minor, patch = int(elements[0]), int(elements[1]), elements[2]
-            if major <= 2 or (
-                major == 3 and (minor == 6 or (minor == 7 and patch == "0"))
-            ):
-                print_warning(
-                    "We highly recommend upgrading aiohttp to 3.7.3 or higher versions."
-                    "An older version of the library may not work with the Slack server-side in the future."
-                )
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/audit_logs/async_client.html b/docs/api-docs/slack_sdk/audit_logs/async_client.html deleted file mode 100644 index 5699db68a..000000000 --- a/docs/api-docs/slack_sdk/audit_logs/async_client.html +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - -slack_sdk.audit_logs.async_client API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.audit_logs.async_client

-
-
-
- -Expand source code - -
from .v1.async_client import AsyncAuditLogsClient  # noqa
-
-
-
-
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/audit_logs/index.html b/docs/api-docs/slack_sdk/audit_logs/index.html deleted file mode 100644 index da8b7e3e9..000000000 --- a/docs/api-docs/slack_sdk/audit_logs/index.html +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - -slack_sdk.audit_logs API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.audit_logs

-
-
-

Audit Logs API is a set of APIs for monitoring whatโ€™s happening in your Enterprise Grid organization.

-

Refer to https://slack.dev/python-slack-sdk/audit-logs/ for details.

-
- -Expand source code - -
"""Audit Logs API is a set of APIs for monitoring whatโ€™s happening in your Enterprise Grid organization.
-
-Refer to https://slack.dev/python-slack-sdk/audit-logs/ for details.
-"""
-from .v1.client import AuditLogsClient  # noqa
-from .v1.response import AuditLogsResponse  # noqa
-
-
-
-

Sub-modules

-
-
slack_sdk.audit_logs.async_client
-
-
-
-
slack_sdk.audit_logs.v1
-
-

Audit Logs API is a set of APIs for monitoring whatโ€™s happening in your Enterprise Grid organization โ€ฆ

-
-
-
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/audit_logs/v1/index.html b/docs/api-docs/slack_sdk/audit_logs/v1/index.html deleted file mode 100644 index a6e34d966..000000000 --- a/docs/api-docs/slack_sdk/audit_logs/v1/index.html +++ /dev/null @@ -1,96 +0,0 @@ - - - - - - -slack_sdk.audit_logs.v1 API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.audit_logs.v1

-
-
-

Audit Logs API is a set of APIs for monitoring whatโ€™s happening in your Enterprise Grid organization.

-

Refer to https://slack.dev/python-slack-sdk/audit-logs/ for details.

-
- -Expand source code - -
"""Audit Logs API is a set of APIs for monitoring whatโ€™s happening in your Enterprise Grid organization.
-
-Refer to https://slack.dev/python-slack-sdk/audit-logs/ for details.
-"""
-
-
-
-

Sub-modules

-
-
slack_sdk.audit_logs.v1.async_client
-
-

Audit Logs API is a set of APIs for monitoring whatโ€™s happening in your Enterprise Grid organization โ€ฆ

-
-
slack_sdk.audit_logs.v1.client
-
-

Audit Logs API is a set of APIs for monitoring whatโ€™s happening in your Enterprise Grid organization โ€ฆ

-
-
slack_sdk.audit_logs.v1.internal_utils
-
-
-
-
slack_sdk.audit_logs.v1.logs
-
-
-
-
slack_sdk.audit_logs.v1.response
-
-
-
-
-
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/audit_logs/v1/internal_utils.html b/docs/api-docs/slack_sdk/audit_logs/v1/internal_utils.html deleted file mode 100644 index ce0635fd3..000000000 --- a/docs/api-docs/slack_sdk/audit_logs/v1/internal_utils.html +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - -slack_sdk.audit_logs.v1.internal_utils API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.audit_logs.v1.internal_utils

-
-
-
- -Expand source code - -
import logging
-from typing import Optional, Dict, Any
-from urllib.parse import quote
-
-from slack_sdk.web.internal_utils import get_user_agent
-from .response import AuditLogsResponse
-
-
-def _build_query(params: Optional[Dict[str, Any]]) -> str:
-    if params is not None and len(params) > 0:
-        return "&".join(
-            {
-                f"{quote(str(k))}={quote(str(v))}"
-                for k, v in params.items()
-                if v is not None
-            }
-        )
-    return ""
-
-
-def _build_request_headers(
-    token: str,
-    default_headers: Dict[str, str],
-    additional_headers: Optional[Dict[str, str]],
-) -> Dict[str, str]:
-    request_headers = {
-        "Content-Type": "application/json;charset=utf-8",
-        "Authorization": f"Bearer {token}",
-    }
-    if default_headers is None or "User-Agent" not in default_headers:
-        request_headers["User-Agent"] = get_user_agent()
-    if default_headers is not None:
-        request_headers.update(default_headers)
-    if additional_headers is not None:
-        request_headers.update(additional_headers)
-    return request_headers
-
-
-def _debug_log_response(logger, resp: AuditLogsResponse) -> None:
-    if logger.level <= logging.DEBUG:
-        logger.debug(
-            "Received the following response - "
-            f"status: {resp.status_code}, "
-            f"headers: {(dict(resp.headers))}, "
-            f"body: {resp.raw_body}"
-        )
-
-
-
-
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/audit_logs/v1/logs.html b/docs/api-docs/slack_sdk/audit_logs/v1/logs.html deleted file mode 100644 index 28e49fd1a..000000000 --- a/docs/api-docs/slack_sdk/audit_logs/v1/logs.html +++ /dev/null @@ -1,2148 +0,0 @@ - - - - - - -slack_sdk.audit_logs.v1.logs API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.audit_logs.v1.logs

-
-
-
- -Expand source code - -
from typing import Optional, List, Union, Any, Dict
-
-
-class User:
-    id: Optional[str]
-    name: Optional[str]
-    email: Optional[str]
-    team: Optional[str]
-    unknown_fields: Dict[str, Any]
-
-    def __init__(
-        self,
-        *,
-        id: Optional[str] = None,
-        name: Optional[str] = None,
-        email: Optional[str] = None,
-        team: Optional[str] = None,
-        **kwargs,
-    ) -> None:
-        self.id = id
-        self.name = name
-        self.email = email
-        self.team = team
-        self.unknown_fields = kwargs
-
-
-class Actor:
-    type: Optional[str]
-    user: Optional[User]
-    unknown_fields: Dict[str, Any]
-
-    def __init__(
-        self,
-        type: Optional[str] = None,
-        user: Optional[User] = None,
-        **kwargs,
-    ) -> None:
-        self.type = type
-        self.user = User(**user) if isinstance(user, dict) else user
-        self.unknown_fields = kwargs
-
-
-class Location:
-    type: Optional[str]
-    id: Optional[str]
-    name: Optional[str]
-    domain: Optional[str]
-    unknown_fields: Dict[str, Any]
-
-    def __init__(
-        self,
-        *,
-        type: Optional[str] = None,
-        id: Optional[str] = None,
-        name: Optional[str] = None,
-        domain: Optional[str] = None,
-        **kwargs,
-    ) -> None:
-        self.type = type
-        self.id = id
-        self.name = name
-        self.domain = domain
-        self.unknown_fields = kwargs
-
-
-class Context:
-    location: Optional[Location]
-    ua: Optional[str]
-    ip_address: Optional[str]
-    session_id: Optional[str]
-    unknown_fields: Dict[str, Any]
-
-    def __init__(
-        self,
-        *,
-        location: Optional[Location] = None,
-        ua: Optional[str] = None,
-        ip_address: Optional[str] = None,
-        session_id: Optional[str] = None,
-        **kwargs,
-    ) -> None:
-        self.location = Location(**location) if isinstance(location, dict) else location
-        self.ua = ua
-        self.ip_address = ip_address
-        self.session_id = session_id
-        self.unknown_fields = kwargs
-
-
-class RetentionPolicy:
-    type: Optional[str]
-    duration_days: Optional[int]
-
-    def __init__(
-        self,
-        *,
-        type: Optional[str] = None,
-        duration_days: Optional[int] = None,
-        **kwargs,
-    ) -> None:
-        self.type = type
-        self.duration_days = duration_days
-        self.unknown_fields = kwargs
-
-
-class Details:
-    name: Optional[str]
-    new_value: Optional[Union[str, List[str], Dict[str, Any]]]
-    previous_value: Optional[Union[str, List[str], Dict[str, Any]]]
-    expires_on: Optional[int]
-    mobile_only: Optional[bool]
-    web_only: Optional[bool]
-    non_sso_only: Optional[bool]
-    type: Optional[str]
-    is_workflow: Optional[bool]
-    inviter: Optional[User]
-    kicker: Optional[User]
-    shared_to: Optional[str]
-    reason: Optional[str]
-    origin_team: Optional[str]
-    target_team: Optional[str]
-    is_internal_integration: Optional[bool]
-    cleared_resolution: Optional[str]
-    app_owner_id: Optional[str]
-    bot_scopes: Optional[List[str]]
-    new_scopes: Optional[List[str]]
-    previous_scopes: Optional[List[str]]
-    granular_bot_token: Optional[bool]
-    scopes: Optional[List[str]]
-    resolution: Optional[str]
-    app_previously_resolved: Optional[bool]
-    admin_app_id: Optional[str]
-    bot_id: Optional[str]
-    installer_user_id: Optional[str]
-    approver_id: Optional[str]
-    approval_type: Optional[str]
-    app_previously_approved: Optional[bool]
-    old_scopes: Optional[List[str]]
-    channels: Optional[List[str]]
-    permissions: Optional[List[Dict[str, Any]]]
-    new_version_id: Optional[str]
-    trigger: Optional[str]
-    export_type: Optional[str]
-    export_start_ts: Optional[str]
-    export_end_ts: Optional[str]
-    barrier_id: Optional[str]
-    primary_usergroup_id: Optional[str]
-    barriered_from_usergroup_ids: Optional[List[str]]
-    restricted_subjects: Optional[List[str]]
-    duration: Optional[int]
-    desktop_app_browser_quit: Optional[bool]
-    invite_id: Optional[str]
-    external_organization_id: Optional[str]
-    external_organization_name: Optional[str]
-    external_user_id: Optional[str]
-    external_user_email: Optional[str]
-    channel_id: Optional[str]
-    added_team_id: Optional[str]
-    unknown_fields: Dict[str, Any]
-    is_token_rotation_enabled_app: Optional[bool]
-    old_retention_policy: Optional[RetentionPolicy]
-    new_retention_policy: Optional[RetentionPolicy]
-
-    def __init__(
-        self,
-        *,
-        name: Optional[str] = None,
-        new_value: Optional[Union[str, List[str], Dict[str, Any]]] = None,
-        previous_value: Optional[Union[str, List[str], Dict[str, Any]]] = None,
-        expires_on: Optional[int] = None,
-        mobile_only: Optional[bool] = None,
-        web_only: Optional[bool] = None,
-        non_sso_only: Optional[bool] = None,
-        type: Optional[str] = None,
-        is_workflow: Optional[bool] = None,
-        inviter: Optional[Union[Dict[str, Any], User]] = None,
-        kicker: Optional[Union[Dict[str, Any], User]] = None,
-        shared_to: Optional[str] = None,
-        reason: Optional[str] = None,
-        origin_team: Optional[str] = None,
-        target_team: Optional[str] = None,
-        is_internal_integration: Optional[bool] = None,
-        cleared_resolution: Optional[str] = None,
-        app_owner_id: Optional[str] = None,
-        bot_scopes: Optional[List[str]] = None,
-        new_scopes: Optional[List[str]] = None,
-        previous_scopes: Optional[List[str]] = None,
-        granular_bot_token: Optional[bool] = None,
-        scopes: Optional[List[str]] = None,
-        resolution: Optional[str] = None,
-        app_previously_resolved: Optional[bool] = None,
-        admin_app_id: Optional[str] = None,
-        bot_id: Optional[str] = None,
-        installer_user_id: Optional[str] = None,
-        approver_id: Optional[str] = None,
-        approval_type: Optional[str] = None,
-        app_previously_approved: Optional[bool] = None,
-        old_scopes: Optional[List[str]] = None,
-        channels: Optional[List[str]] = None,
-        permissions: Optional[List[Dict[str, Any]]] = None,
-        new_version_id: Optional[str] = None,
-        trigger: Optional[str] = None,
-        export_type: Optional[str] = None,
-        export_start_ts: Optional[str] = None,
-        export_end_ts: Optional[str] = None,
-        barrier_id: Optional[str] = None,
-        primary_usergroup_id: Optional[str] = None,
-        barriered_from_usergroup_ids: Optional[List[str]] = None,
-        restricted_subjects: Optional[List[str]] = None,
-        duration: Optional[int] = None,
-        desktop_app_browser_quit: Optional[bool] = None,
-        invite_id: Optional[str] = None,
-        external_organization_id: Optional[str] = None,
-        external_organization_name: Optional[str] = None,
-        external_user_id: Optional[str] = None,
-        external_user_email: Optional[str] = None,
-        channel_id: Optional[str] = None,
-        added_team_id: Optional[str] = None,
-        is_token_rotation_enabled_app: Optional[bool] = None,
-        old_retention_policy: Optional[Union[Dict[str, Any], RetentionPolicy]] = None,
-        new_retention_policy: Optional[Union[Dict[str, Any], RetentionPolicy]] = None,
-        **kwargs,
-    ) -> None:
-        self.name = name
-        self.new_value = new_value
-        self.previous_value = previous_value
-        self.expires_on = expires_on
-        self.mobile_only = mobile_only
-        self.web_only = web_only
-        self.non_sso_only = non_sso_only
-        self.type = type
-        self.is_workflow = is_workflow
-        self.inviter = inviter if isinstance(inviter, User) else User(**inviter)
-        self.kicker = kicker if isinstance(kicker, User) else User(**kicker)
-        self.shared_to = shared_to
-        self.reason = reason
-        self.origin_team = origin_team
-        self.target_team = target_team
-        self.is_internal_integration = is_internal_integration
-        self.cleared_resolution = cleared_resolution
-        self.app_owner_id = app_owner_id
-        self.bot_scopes = bot_scopes
-        self.new_scopes = new_scopes
-        self.previous_scopes = previous_scopes
-        self.granular_bot_token = granular_bot_token
-        self.scopes = scopes
-        self.resolution = resolution
-        self.app_previously_resolved = app_previously_resolved
-        self.admin_app_id = admin_app_id
-        self.bot_id = bot_id
-        self.unknown_fields = kwargs
-        self.installer_user_id = installer_user_id
-        self.approver_id = approver_id
-        self.approval_type = approval_type
-        self.app_previously_approved = app_previously_approved
-        self.old_scopes = old_scopes
-        self.channels = channels
-        self.permissions = permissions
-        self.new_version_id = new_version_id
-        self.trigger = trigger
-        self.export_type = export_type
-        self.export_start_ts = export_start_ts
-        self.export_end_ts = export_end_ts
-        self.barrier_id = barrier_id
-        self.primary_usergroup_id = primary_usergroup_id
-        self.barriered_from_usergroup_ids = barriered_from_usergroup_ids
-        self.restricted_subjects = restricted_subjects
-        self.duration = duration
-        self.desktop_app_browser_quit = desktop_app_browser_quit
-        self.invite_id = invite_id
-        self.external_organization_id = external_organization_id
-        self.external_organization_name = external_organization_name
-        self.external_user_id = external_user_id
-        self.external_user_email = external_user_email
-        self.channel_id = channel_id
-        self.added_team_id = added_team_id
-        self.is_token_rotation_enabled_app = is_token_rotation_enabled_app
-        self.old_retention_policy = (
-            old_retention_policy
-            if isinstance(old_retention_policy, RetentionPolicy)
-            else RetentionPolicy(**old_retention_policy)
-        )
-        self.new_retention_policy = (
-            new_retention_policy
-            if isinstance(new_retention_policy, RetentionPolicy)
-            else RetentionPolicy(**new_retention_policy)
-        )
-
-
-class App:
-    id: Optional[str]
-    name: Optional[str]
-    is_distributed: Optional[bool]
-    is_directory_approved: Optional[bool]
-    is_workflow_app: Optional[bool]
-    scopes: Optional[List[str]]
-    unknown_fields: Dict[str, Any]
-
-    def __init__(
-        self,
-        *,
-        id: Optional[str] = None,
-        name: Optional[str] = None,
-        is_distributed: Optional[bool] = None,
-        is_directory_approved: Optional[bool] = None,
-        is_workflow_app: Optional[bool] = None,
-        scopes: Optional[List[str]] = None,
-        **kwargs,
-    ) -> None:
-        self.id = id
-        self.name = name
-        self.is_distributed = is_distributed
-        self.is_directory_approved = is_directory_approved
-        self.is_workflow_app = is_workflow_app
-        self.scopes = scopes
-        self.unknown_fields = kwargs
-
-
-class Channel:
-    id: Optional[str]
-    privacy: Optional[str]
-    name: Optional[str]
-    is_shared: Optional[bool]
-    is_org_shared: Optional[bool]
-    teams_shared_with: Optional[List[str]]
-    original_connected_channel_id: Optional[str]
-    unknown_fields: Dict[str, Any]
-
-    def __init__(
-        self,
-        *,
-        id: Optional[str] = None,
-        privacy: Optional[str] = None,
-        name: Optional[str] = None,
-        is_shared: Optional[bool] = None,
-        is_org_shared: Optional[bool] = None,
-        teams_shared_with: Optional[List[str]] = None,
-        original_connected_channel_id: Optional[str] = None,
-        **kwargs,
-    ) -> None:
-        self.id = id
-        self.privacy = privacy
-        self.name = name
-        self.is_shared = is_shared
-        self.is_org_shared = is_org_shared
-        self.teams_shared_with = teams_shared_with
-        self.original_connected_channel_id = original_connected_channel_id
-        self.unknown_fields = kwargs
-
-
-class File:
-    id: Optional[str]
-    name: Optional[str]
-    filetype: Optional[str]
-    title: Optional[str]
-    unknown_fields: Dict[str, Any]
-
-    def __init__(
-        self,
-        *,
-        id: Optional[str] = None,
-        name: Optional[str] = None,
-        filetype: Optional[str] = None,
-        title: Optional[str] = None,
-        **kwargs,
-    ) -> None:
-        self.id = id
-        self.name = name
-        self.filetype = filetype
-        self.title = title
-        self.unknown_fields = kwargs
-
-
-class Usergroup:
-    id: Optional[str]
-    name: Optional[str]
-    unknown_fields: Dict[str, Any]
-
-    def __init__(
-        self,
-        *,
-        id: Optional[str] = None,
-        name: Optional[str] = None,
-        **kwargs,
-    ) -> None:
-        self.id = id
-        self.name = name
-        self.unknown_fields = kwargs
-
-
-class Workflow:
-    id: Optional[str]
-    name: Optional[str]
-    domain: Optional[str]
-    unknown_fields: Dict[str, Any]
-
-    def __init__(
-        self,
-        *,
-        id: Optional[str] = None,
-        name: Optional[str] = None,
-        domain: Optional[str] = None,
-        **kwargs,
-    ) -> None:
-        self.id = id
-        self.name = name
-        self.domain = domain
-        self.unknown_fields = kwargs
-
-
-class InformationBarrier:
-    id: Optional[str]
-    primary_usergroup: Optional[str]
-    barriered_from_usergroups: Optional[List[str]]
-    restricted_subjects: Optional[List[str]]
-    unknown_fields: Dict[str, Any]
-
-    def __init__(
-        self,
-        *,
-        id: Optional[str] = None,
-        primary_usergroup: Optional[str] = None,
-        barriered_from_usergroups: Optional[List[str]] = None,
-        restricted_subjects: Optional[List[str]] = None,
-        **kwargs,
-    ) -> None:
-        self.id = id
-        self.primary_usergroup = primary_usergroup
-        self.barriered_from_usergroups = barriered_from_usergroups
-        self.restricted_subjects = restricted_subjects
-        self.unknown_fields = kwargs
-
-
-class Entity:
-    type: Optional[str]
-    user: Optional[User]
-    workspace: Optional[Location]
-    enterprise: Optional[Location]
-    channel: Optional[Channel]
-    file: Optional[File]
-    app: Optional[App]
-    usergroup: Optional[Usergroup]
-    workflow: Optional[Workflow]
-    barrier: Optional[InformationBarrier]
-    unknown_fields: Dict[str, Any]
-
-    def __init__(
-        self,
-        *,
-        type: Optional[str] = None,
-        user: Optional[Union[User, dict]] = None,
-        workspace: Optional[Union[Location, dict]] = None,
-        enterprise: Optional[Union[Location, dict]] = None,
-        channel: Optional[Union[Channel, dict]] = None,
-        file: Optional[Union[File, dict]] = None,
-        app: Optional[Union[App, dict]] = None,
-        usergroup: Optional[Usergroup] = None,
-        workflow: Optional[Workflow] = None,
-        barrier: Optional[InformationBarrier] = None,
-        **kwargs,
-    ) -> None:
-        self.type = type
-        self.user = User(**user) if isinstance(user, dict) else user
-        self.workspace = (
-            Location(**workspace) if isinstance(workspace, dict) else workspace
-        )
-        self.enterprise = (
-            Location(**enterprise) if isinstance(enterprise, dict) else enterprise
-        )
-        self.channel = Channel(**channel) if isinstance(channel, dict) else channel
-        self.file = File(**file) if isinstance(file, dict) else file
-        self.app = App(**app) if isinstance(app, dict) else app
-        self.usergroup = (
-            Usergroup(**usergroup) if isinstance(usergroup, dict) else usergroup
-        )
-        self.workflow = Workflow(**workflow) if isinstance(workflow, dict) else workflow
-        self.barrier = (
-            InformationBarrier(**barrier) if isinstance(barrier, dict) else barrier
-        )
-        self.unknown_fields = kwargs
-
-
-class Entry:
-    id: Optional[str]
-    date_create: Optional[int]
-    action: Optional[str]
-    actor: Optional[Actor]
-    entity: Optional[Entity]
-    context: Optional[Context]
-    details: Optional[Details]
-    unknown_fields: Dict[str, Any]
-
-    def __init__(
-        self,
-        *,
-        id: Optional[str] = None,
-        date_create: Optional[int] = None,
-        action: Optional[str] = None,
-        actor: Optional[Actor] = None,
-        entity: Optional[Entity] = None,
-        context: Optional[Context] = None,
-        details: Optional[Details] = None,
-        **kwargs,
-    ) -> None:
-        self.id = id
-        self.date_create = date_create
-        self.action = action
-        self.actor = Actor(**actor) if isinstance(actor, dict) else actor
-        self.entity = Entity(**entity) if isinstance(entity, dict) else entity
-        self.context = Context(**context) if isinstance(context, dict) else context
-        self.details = Details(**details) if isinstance(details, dict) else details
-        self.unknown_fields = kwargs
-
-
-class ResponseMetadata:
-    next_cursor: Optional[str]
-    unknown_fields: Dict[str, Any]
-
-    def __init__(
-        self,
-        *,
-        next_cursor: Optional[str] = None,
-        **kwargs,
-    ) -> None:
-        self.next_cursor = next_cursor
-        self.unknown_fields = kwargs
-
-
-class LogsResponse:
-    entries: Optional[List[Entry]]
-    response_metadata: Optional[ResponseMetadata]
-    ok: Optional[bool]
-    error: Optional[str]
-    needed: Optional[str]
-    provided: Optional[str]
-    unknown_fields: Dict[str, Any]
-
-    def __init__(
-        self,
-        *,
-        entries: Optional[List[Union[Entry, dict]]] = None,
-        response_metadata: Optional[Union[ResponseMetadata, dict]] = None,
-        ok: Optional[bool] = None,
-        error: Optional[str] = None,
-        needed: Optional[str] = None,
-        provided: Optional[str] = None,
-        **kwargs,
-    ) -> None:
-        self.entries = [Entry(**e) if isinstance(e, dict) else e for e in entries]
-        self.response_metadata = (
-            ResponseMetadata(**response_metadata)
-            if isinstance(response_metadata, dict)
-            else response_metadata
-        )
-        self.ok = ok
-        self.error = error
-        self.needed = needed
-        self.provided = provided
-        self.unknown_fields = kwargs
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class Actor -(type:ย Optional[str]ย =ย None, user:ย Optional[User]ย =ย None, **kwargs) -
-
-
-
- -Expand source code - -
class Actor:
-    type: Optional[str]
-    user: Optional[User]
-    unknown_fields: Dict[str, Any]
-
-    def __init__(
-        self,
-        type: Optional[str] = None,
-        user: Optional[User] = None,
-        **kwargs,
-    ) -> None:
-        self.type = type
-        self.user = User(**user) if isinstance(user, dict) else user
-        self.unknown_fields = kwargs
-
-

Class variables

-
-
var type :ย Optional[str]
-
-
-
-
var unknown_fields :ย Dict[str,ย Any]
-
-
-
-
var user :ย Optional[User]
-
-
-
-
-
-
-class App -(*, id:ย Optional[str]ย =ย None, name:ย Optional[str]ย =ย None, is_distributed:ย Optional[bool]ย =ย None, is_directory_approved:ย Optional[bool]ย =ย None, is_workflow_app:ย Optional[bool]ย =ย None, scopes:ย Optional[List[str]]ย =ย None, **kwargs) -
-
-
-
- -Expand source code - -
class App:
-    id: Optional[str]
-    name: Optional[str]
-    is_distributed: Optional[bool]
-    is_directory_approved: Optional[bool]
-    is_workflow_app: Optional[bool]
-    scopes: Optional[List[str]]
-    unknown_fields: Dict[str, Any]
-
-    def __init__(
-        self,
-        *,
-        id: Optional[str] = None,
-        name: Optional[str] = None,
-        is_distributed: Optional[bool] = None,
-        is_directory_approved: Optional[bool] = None,
-        is_workflow_app: Optional[bool] = None,
-        scopes: Optional[List[str]] = None,
-        **kwargs,
-    ) -> None:
-        self.id = id
-        self.name = name
-        self.is_distributed = is_distributed
-        self.is_directory_approved = is_directory_approved
-        self.is_workflow_app = is_workflow_app
-        self.scopes = scopes
-        self.unknown_fields = kwargs
-
-

Class variables

-
-
var id :ย Optional[str]
-
-
-
-
var is_directory_approved :ย Optional[bool]
-
-
-
-
var is_distributed :ย Optional[bool]
-
-
-
-
var is_workflow_app :ย Optional[bool]
-
-
-
-
var name :ย Optional[str]
-
-
-
-
var scopes :ย Optional[List[str]]
-
-
-
-
var unknown_fields :ย Dict[str,ย Any]
-
-
-
-
-
-
-class Channel -(*, id:ย Optional[str]ย =ย None, privacy:ย Optional[str]ย =ย None, name:ย Optional[str]ย =ย None, is_shared:ย Optional[bool]ย =ย None, is_org_shared:ย Optional[bool]ย =ย None, teams_shared_with:ย Optional[List[str]]ย =ย None, original_connected_channel_id:ย Optional[str]ย =ย None, **kwargs) -
-
-
-
- -Expand source code - -
class Channel:
-    id: Optional[str]
-    privacy: Optional[str]
-    name: Optional[str]
-    is_shared: Optional[bool]
-    is_org_shared: Optional[bool]
-    teams_shared_with: Optional[List[str]]
-    original_connected_channel_id: Optional[str]
-    unknown_fields: Dict[str, Any]
-
-    def __init__(
-        self,
-        *,
-        id: Optional[str] = None,
-        privacy: Optional[str] = None,
-        name: Optional[str] = None,
-        is_shared: Optional[bool] = None,
-        is_org_shared: Optional[bool] = None,
-        teams_shared_with: Optional[List[str]] = None,
-        original_connected_channel_id: Optional[str] = None,
-        **kwargs,
-    ) -> None:
-        self.id = id
-        self.privacy = privacy
-        self.name = name
-        self.is_shared = is_shared
-        self.is_org_shared = is_org_shared
-        self.teams_shared_with = teams_shared_with
-        self.original_connected_channel_id = original_connected_channel_id
-        self.unknown_fields = kwargs
-
-

Class variables

-
-
var id :ย Optional[str]
-
-
-
-
var is_org_shared :ย Optional[bool]
-
-
-
-
var is_shared :ย Optional[bool]
-
-
-
-
var name :ย Optional[str]
-
-
-
-
var original_connected_channel_id :ย Optional[str]
-
-
-
-
var privacy :ย Optional[str]
-
-
-
-
var teams_shared_with :ย Optional[List[str]]
-
-
-
-
var unknown_fields :ย Dict[str,ย Any]
-
-
-
-
-
-
-class Context -(*, location:ย Optional[Location]ย =ย None, ua:ย Optional[str]ย =ย None, ip_address:ย Optional[str]ย =ย None, session_id:ย Optional[str]ย =ย None, **kwargs) -
-
-
-
- -Expand source code - -
class Context:
-    location: Optional[Location]
-    ua: Optional[str]
-    ip_address: Optional[str]
-    session_id: Optional[str]
-    unknown_fields: Dict[str, Any]
-
-    def __init__(
-        self,
-        *,
-        location: Optional[Location] = None,
-        ua: Optional[str] = None,
-        ip_address: Optional[str] = None,
-        session_id: Optional[str] = None,
-        **kwargs,
-    ) -> None:
-        self.location = Location(**location) if isinstance(location, dict) else location
-        self.ua = ua
-        self.ip_address = ip_address
-        self.session_id = session_id
-        self.unknown_fields = kwargs
-
-

Class variables

-
-
var ip_address :ย Optional[str]
-
-
-
-
var location :ย Optional[Location]
-
-
-
-
var session_id :ย Optional[str]
-
-
-
-
var ua :ย Optional[str]
-
-
-
-
var unknown_fields :ย Dict[str,ย Any]
-
-
-
-
-
-
-class Details -(*, name:ย Optional[str]ย =ย None, new_value:ย Union[str,ย List[str],ย Dict[str,ย Any],ย ForwardRef(None)]ย =ย None, previous_value:ย Union[str,ย List[str],ย Dict[str,ย Any],ย ForwardRef(None)]ย =ย None, expires_on:ย Optional[int]ย =ย None, mobile_only:ย Optional[bool]ย =ย None, web_only:ย Optional[bool]ย =ย None, non_sso_only:ย Optional[bool]ย =ย None, type:ย Optional[str]ย =ย None, is_workflow:ย Optional[bool]ย =ย None, inviter:ย Union[Dict[str,ย Any],ย User,ย ForwardRef(None)]ย =ย None, kicker:ย Union[Dict[str,ย Any],ย User,ย ForwardRef(None)]ย =ย None, shared_to:ย Optional[str]ย =ย None, reason:ย Optional[str]ย =ย None, origin_team:ย Optional[str]ย =ย None, target_team:ย Optional[str]ย =ย None, is_internal_integration:ย Optional[bool]ย =ย None, cleared_resolution:ย Optional[str]ย =ย None, app_owner_id:ย Optional[str]ย =ย None, bot_scopes:ย Optional[List[str]]ย =ย None, new_scopes:ย Optional[List[str]]ย =ย None, previous_scopes:ย Optional[List[str]]ย =ย None, granular_bot_token:ย Optional[bool]ย =ย None, scopes:ย Optional[List[str]]ย =ย None, resolution:ย Optional[str]ย =ย None, app_previously_resolved:ย Optional[bool]ย =ย None, admin_app_id:ย Optional[str]ย =ย None, bot_id:ย Optional[str]ย =ย None, installer_user_id:ย Optional[str]ย =ย None, approver_id:ย Optional[str]ย =ย None, approval_type:ย Optional[str]ย =ย None, app_previously_approved:ย Optional[bool]ย =ย None, old_scopes:ย Optional[List[str]]ย =ย None, channels:ย Optional[List[str]]ย =ย None, permissions:ย Optional[List[Dict[str,ย Any]]]ย =ย None, new_version_id:ย Optional[str]ย =ย None, trigger:ย Optional[str]ย =ย None, export_type:ย Optional[str]ย =ย None, export_start_ts:ย Optional[str]ย =ย None, export_end_ts:ย Optional[str]ย =ย None, barrier_id:ย Optional[str]ย =ย None, primary_usergroup_id:ย Optional[str]ย =ย None, barriered_from_usergroup_ids:ย Optional[List[str]]ย =ย None, restricted_subjects:ย Optional[List[str]]ย =ย None, duration:ย Optional[int]ย =ย None, desktop_app_browser_quit:ย Optional[bool]ย =ย None, invite_id:ย Optional[str]ย =ย None, external_organization_id:ย Optional[str]ย =ย None, external_organization_name:ย Optional[str]ย =ย None, external_user_id:ย Optional[str]ย =ย None, external_user_email:ย Optional[str]ย =ย None, channel_id:ย Optional[str]ย =ย None, added_team_id:ย Optional[str]ย =ย None, is_token_rotation_enabled_app:ย Optional[bool]ย =ย None, old_retention_policy:ย Union[Dict[str,ย Any],ย RetentionPolicy,ย ForwardRef(None)]ย =ย None, new_retention_policy:ย Union[Dict[str,ย Any],ย RetentionPolicy,ย ForwardRef(None)]ย =ย None, **kwargs) -
-
-
-
- -Expand source code - -
class Details:
-    name: Optional[str]
-    new_value: Optional[Union[str, List[str], Dict[str, Any]]]
-    previous_value: Optional[Union[str, List[str], Dict[str, Any]]]
-    expires_on: Optional[int]
-    mobile_only: Optional[bool]
-    web_only: Optional[bool]
-    non_sso_only: Optional[bool]
-    type: Optional[str]
-    is_workflow: Optional[bool]
-    inviter: Optional[User]
-    kicker: Optional[User]
-    shared_to: Optional[str]
-    reason: Optional[str]
-    origin_team: Optional[str]
-    target_team: Optional[str]
-    is_internal_integration: Optional[bool]
-    cleared_resolution: Optional[str]
-    app_owner_id: Optional[str]
-    bot_scopes: Optional[List[str]]
-    new_scopes: Optional[List[str]]
-    previous_scopes: Optional[List[str]]
-    granular_bot_token: Optional[bool]
-    scopes: Optional[List[str]]
-    resolution: Optional[str]
-    app_previously_resolved: Optional[bool]
-    admin_app_id: Optional[str]
-    bot_id: Optional[str]
-    installer_user_id: Optional[str]
-    approver_id: Optional[str]
-    approval_type: Optional[str]
-    app_previously_approved: Optional[bool]
-    old_scopes: Optional[List[str]]
-    channels: Optional[List[str]]
-    permissions: Optional[List[Dict[str, Any]]]
-    new_version_id: Optional[str]
-    trigger: Optional[str]
-    export_type: Optional[str]
-    export_start_ts: Optional[str]
-    export_end_ts: Optional[str]
-    barrier_id: Optional[str]
-    primary_usergroup_id: Optional[str]
-    barriered_from_usergroup_ids: Optional[List[str]]
-    restricted_subjects: Optional[List[str]]
-    duration: Optional[int]
-    desktop_app_browser_quit: Optional[bool]
-    invite_id: Optional[str]
-    external_organization_id: Optional[str]
-    external_organization_name: Optional[str]
-    external_user_id: Optional[str]
-    external_user_email: Optional[str]
-    channel_id: Optional[str]
-    added_team_id: Optional[str]
-    unknown_fields: Dict[str, Any]
-    is_token_rotation_enabled_app: Optional[bool]
-    old_retention_policy: Optional[RetentionPolicy]
-    new_retention_policy: Optional[RetentionPolicy]
-
-    def __init__(
-        self,
-        *,
-        name: Optional[str] = None,
-        new_value: Optional[Union[str, List[str], Dict[str, Any]]] = None,
-        previous_value: Optional[Union[str, List[str], Dict[str, Any]]] = None,
-        expires_on: Optional[int] = None,
-        mobile_only: Optional[bool] = None,
-        web_only: Optional[bool] = None,
-        non_sso_only: Optional[bool] = None,
-        type: Optional[str] = None,
-        is_workflow: Optional[bool] = None,
-        inviter: Optional[Union[Dict[str, Any], User]] = None,
-        kicker: Optional[Union[Dict[str, Any], User]] = None,
-        shared_to: Optional[str] = None,
-        reason: Optional[str] = None,
-        origin_team: Optional[str] = None,
-        target_team: Optional[str] = None,
-        is_internal_integration: Optional[bool] = None,
-        cleared_resolution: Optional[str] = None,
-        app_owner_id: Optional[str] = None,
-        bot_scopes: Optional[List[str]] = None,
-        new_scopes: Optional[List[str]] = None,
-        previous_scopes: Optional[List[str]] = None,
-        granular_bot_token: Optional[bool] = None,
-        scopes: Optional[List[str]] = None,
-        resolution: Optional[str] = None,
-        app_previously_resolved: Optional[bool] = None,
-        admin_app_id: Optional[str] = None,
-        bot_id: Optional[str] = None,
-        installer_user_id: Optional[str] = None,
-        approver_id: Optional[str] = None,
-        approval_type: Optional[str] = None,
-        app_previously_approved: Optional[bool] = None,
-        old_scopes: Optional[List[str]] = None,
-        channels: Optional[List[str]] = None,
-        permissions: Optional[List[Dict[str, Any]]] = None,
-        new_version_id: Optional[str] = None,
-        trigger: Optional[str] = None,
-        export_type: Optional[str] = None,
-        export_start_ts: Optional[str] = None,
-        export_end_ts: Optional[str] = None,
-        barrier_id: Optional[str] = None,
-        primary_usergroup_id: Optional[str] = None,
-        barriered_from_usergroup_ids: Optional[List[str]] = None,
-        restricted_subjects: Optional[List[str]] = None,
-        duration: Optional[int] = None,
-        desktop_app_browser_quit: Optional[bool] = None,
-        invite_id: Optional[str] = None,
-        external_organization_id: Optional[str] = None,
-        external_organization_name: Optional[str] = None,
-        external_user_id: Optional[str] = None,
-        external_user_email: Optional[str] = None,
-        channel_id: Optional[str] = None,
-        added_team_id: Optional[str] = None,
-        is_token_rotation_enabled_app: Optional[bool] = None,
-        old_retention_policy: Optional[Union[Dict[str, Any], RetentionPolicy]] = None,
-        new_retention_policy: Optional[Union[Dict[str, Any], RetentionPolicy]] = None,
-        **kwargs,
-    ) -> None:
-        self.name = name
-        self.new_value = new_value
-        self.previous_value = previous_value
-        self.expires_on = expires_on
-        self.mobile_only = mobile_only
-        self.web_only = web_only
-        self.non_sso_only = non_sso_only
-        self.type = type
-        self.is_workflow = is_workflow
-        self.inviter = inviter if isinstance(inviter, User) else User(**inviter)
-        self.kicker = kicker if isinstance(kicker, User) else User(**kicker)
-        self.shared_to = shared_to
-        self.reason = reason
-        self.origin_team = origin_team
-        self.target_team = target_team
-        self.is_internal_integration = is_internal_integration
-        self.cleared_resolution = cleared_resolution
-        self.app_owner_id = app_owner_id
-        self.bot_scopes = bot_scopes
-        self.new_scopes = new_scopes
-        self.previous_scopes = previous_scopes
-        self.granular_bot_token = granular_bot_token
-        self.scopes = scopes
-        self.resolution = resolution
-        self.app_previously_resolved = app_previously_resolved
-        self.admin_app_id = admin_app_id
-        self.bot_id = bot_id
-        self.unknown_fields = kwargs
-        self.installer_user_id = installer_user_id
-        self.approver_id = approver_id
-        self.approval_type = approval_type
-        self.app_previously_approved = app_previously_approved
-        self.old_scopes = old_scopes
-        self.channels = channels
-        self.permissions = permissions
-        self.new_version_id = new_version_id
-        self.trigger = trigger
-        self.export_type = export_type
-        self.export_start_ts = export_start_ts
-        self.export_end_ts = export_end_ts
-        self.barrier_id = barrier_id
-        self.primary_usergroup_id = primary_usergroup_id
-        self.barriered_from_usergroup_ids = barriered_from_usergroup_ids
-        self.restricted_subjects = restricted_subjects
-        self.duration = duration
-        self.desktop_app_browser_quit = desktop_app_browser_quit
-        self.invite_id = invite_id
-        self.external_organization_id = external_organization_id
-        self.external_organization_name = external_organization_name
-        self.external_user_id = external_user_id
-        self.external_user_email = external_user_email
-        self.channel_id = channel_id
-        self.added_team_id = added_team_id
-        self.is_token_rotation_enabled_app = is_token_rotation_enabled_app
-        self.old_retention_policy = (
-            old_retention_policy
-            if isinstance(old_retention_policy, RetentionPolicy)
-            else RetentionPolicy(**old_retention_policy)
-        )
-        self.new_retention_policy = (
-            new_retention_policy
-            if isinstance(new_retention_policy, RetentionPolicy)
-            else RetentionPolicy(**new_retention_policy)
-        )
-
-

Class variables

-
-
var added_team_id :ย Optional[str]
-
-
-
-
var admin_app_id :ย Optional[str]
-
-
-
-
var app_owner_id :ย Optional[str]
-
-
-
-
var app_previously_approved :ย Optional[bool]
-
-
-
-
var app_previously_resolved :ย Optional[bool]
-
-
-
-
var approval_type :ย Optional[str]
-
-
-
-
var approver_id :ย Optional[str]
-
-
-
-
var barrier_id :ย Optional[str]
-
-
-
-
var barriered_from_usergroup_ids :ย Optional[List[str]]
-
-
-
-
var bot_id :ย Optional[str]
-
-
-
-
var bot_scopes :ย Optional[List[str]]
-
-
-
-
var channel_id :ย Optional[str]
-
-
-
-
var channels :ย Optional[List[str]]
-
-
-
-
var cleared_resolution :ย Optional[str]
-
-
-
-
var desktop_app_browser_quit :ย Optional[bool]
-
-
-
-
var duration :ย Optional[int]
-
-
-
-
var expires_on :ย Optional[int]
-
-
-
-
var export_end_ts :ย Optional[str]
-
-
-
-
var export_start_ts :ย Optional[str]
-
-
-
-
var export_type :ย Optional[str]
-
-
-
-
var external_organization_id :ย Optional[str]
-
-
-
-
var external_organization_name :ย Optional[str]
-
-
-
-
var external_user_email :ย Optional[str]
-
-
-
-
var external_user_id :ย Optional[str]
-
-
-
-
var granular_bot_token :ย Optional[bool]
-
-
-
-
var installer_user_id :ย Optional[str]
-
-
-
-
var invite_id :ย Optional[str]
-
-
-
-
var inviter :ย Optional[User]
-
-
-
-
var is_internal_integration :ย Optional[bool]
-
-
-
-
var is_token_rotation_enabled_app :ย Optional[bool]
-
-
-
-
var is_workflow :ย Optional[bool]
-
-
-
-
var kicker :ย Optional[User]
-
-
-
-
var mobile_only :ย Optional[bool]
-
-
-
-
var name :ย Optional[str]
-
-
-
-
var new_retention_policy :ย Optional[RetentionPolicy]
-
-
-
-
var new_scopes :ย Optional[List[str]]
-
-
-
-
var new_value :ย Union[str,ย List[str],ย Dict[str,ย Any],ย ForwardRef(None)]
-
-
-
-
var new_version_id :ย Optional[str]
-
-
-
-
var non_sso_only :ย Optional[bool]
-
-
-
-
var old_retention_policy :ย Optional[RetentionPolicy]
-
-
-
-
var old_scopes :ย Optional[List[str]]
-
-
-
-
var origin_team :ย Optional[str]
-
-
-
-
var permissions :ย Optional[List[Dict[str,ย Any]]]
-
-
-
-
var previous_scopes :ย Optional[List[str]]
-
-
-
-
var previous_value :ย Union[str,ย List[str],ย Dict[str,ย Any],ย ForwardRef(None)]
-
-
-
-
var primary_usergroup_id :ย Optional[str]
-
-
-
-
var reason :ย Optional[str]
-
-
-
-
var resolution :ย Optional[str]
-
-
-
-
var restricted_subjects :ย Optional[List[str]]
-
-
-
-
var scopes :ย Optional[List[str]]
-
-
-
-
var shared_to :ย Optional[str]
-
-
-
-
var target_team :ย Optional[str]
-
-
-
-
var trigger :ย Optional[str]
-
-
-
-
var type :ย Optional[str]
-
-
-
-
var unknown_fields :ย Dict[str,ย Any]
-
-
-
-
var web_only :ย Optional[bool]
-
-
-
-
-
-
-class Entity -(*, type:ย Optional[str]ย =ย None, user:ย Union[User,ย dict,ย ForwardRef(None)]ย =ย None, workspace:ย Union[Location,ย dict,ย ForwardRef(None)]ย =ย None, enterprise:ย Union[Location,ย dict,ย ForwardRef(None)]ย =ย None, channel:ย Union[Channel,ย dict,ย ForwardRef(None)]ย =ย None, file:ย Union[File,ย dict,ย ForwardRef(None)]ย =ย None, app:ย Union[App,ย dict,ย ForwardRef(None)]ย =ย None, usergroup:ย Optional[Usergroup]ย =ย None, workflow:ย Optional[Workflow]ย =ย None, barrier:ย Optional[InformationBarrier]ย =ย None, **kwargs) -
-
-
-
- -Expand source code - -
class Entity:
-    type: Optional[str]
-    user: Optional[User]
-    workspace: Optional[Location]
-    enterprise: Optional[Location]
-    channel: Optional[Channel]
-    file: Optional[File]
-    app: Optional[App]
-    usergroup: Optional[Usergroup]
-    workflow: Optional[Workflow]
-    barrier: Optional[InformationBarrier]
-    unknown_fields: Dict[str, Any]
-
-    def __init__(
-        self,
-        *,
-        type: Optional[str] = None,
-        user: Optional[Union[User, dict]] = None,
-        workspace: Optional[Union[Location, dict]] = None,
-        enterprise: Optional[Union[Location, dict]] = None,
-        channel: Optional[Union[Channel, dict]] = None,
-        file: Optional[Union[File, dict]] = None,
-        app: Optional[Union[App, dict]] = None,
-        usergroup: Optional[Usergroup] = None,
-        workflow: Optional[Workflow] = None,
-        barrier: Optional[InformationBarrier] = None,
-        **kwargs,
-    ) -> None:
-        self.type = type
-        self.user = User(**user) if isinstance(user, dict) else user
-        self.workspace = (
-            Location(**workspace) if isinstance(workspace, dict) else workspace
-        )
-        self.enterprise = (
-            Location(**enterprise) if isinstance(enterprise, dict) else enterprise
-        )
-        self.channel = Channel(**channel) if isinstance(channel, dict) else channel
-        self.file = File(**file) if isinstance(file, dict) else file
-        self.app = App(**app) if isinstance(app, dict) else app
-        self.usergroup = (
-            Usergroup(**usergroup) if isinstance(usergroup, dict) else usergroup
-        )
-        self.workflow = Workflow(**workflow) if isinstance(workflow, dict) else workflow
-        self.barrier = (
-            InformationBarrier(**barrier) if isinstance(barrier, dict) else barrier
-        )
-        self.unknown_fields = kwargs
-
-

Class variables

-
-
var app :ย Optional[App]
-
-
-
-
var barrier :ย Optional[InformationBarrier]
-
-
-
-
var channel :ย Optional[Channel]
-
-
-
-
var enterprise :ย Optional[Location]
-
-
-
-
var file :ย Optional[File]
-
-
-
-
var type :ย Optional[str]
-
-
-
-
var unknown_fields :ย Dict[str,ย Any]
-
-
-
-
var user :ย Optional[User]
-
-
-
-
var usergroup :ย Optional[Usergroup]
-
-
-
-
var workflow :ย Optional[Workflow]
-
-
-
-
var workspace :ย Optional[Location]
-
-
-
-
-
-
-class Entry -(*, id:ย Optional[str]ย =ย None, date_create:ย Optional[int]ย =ย None, action:ย Optional[str]ย =ย None, actor:ย Optional[Actor]ย =ย None, entity:ย Optional[Entity]ย =ย None, context:ย Optional[Context]ย =ย None, details:ย Optional[Details]ย =ย None, **kwargs) -
-
-
-
- -Expand source code - -
class Entry:
-    id: Optional[str]
-    date_create: Optional[int]
-    action: Optional[str]
-    actor: Optional[Actor]
-    entity: Optional[Entity]
-    context: Optional[Context]
-    details: Optional[Details]
-    unknown_fields: Dict[str, Any]
-
-    def __init__(
-        self,
-        *,
-        id: Optional[str] = None,
-        date_create: Optional[int] = None,
-        action: Optional[str] = None,
-        actor: Optional[Actor] = None,
-        entity: Optional[Entity] = None,
-        context: Optional[Context] = None,
-        details: Optional[Details] = None,
-        **kwargs,
-    ) -> None:
-        self.id = id
-        self.date_create = date_create
-        self.action = action
-        self.actor = Actor(**actor) if isinstance(actor, dict) else actor
-        self.entity = Entity(**entity) if isinstance(entity, dict) else entity
-        self.context = Context(**context) if isinstance(context, dict) else context
-        self.details = Details(**details) if isinstance(details, dict) else details
-        self.unknown_fields = kwargs
-
-

Class variables

-
-
var action :ย Optional[str]
-
-
-
-
var actor :ย Optional[Actor]
-
-
-
-
var context :ย Optional[Context]
-
-
-
-
var date_create :ย Optional[int]
-
-
-
-
var details :ย Optional[Details]
-
-
-
-
var entity :ย Optional[Entity]
-
-
-
-
var id :ย Optional[str]
-
-
-
-
var unknown_fields :ย Dict[str,ย Any]
-
-
-
-
-
-
-class File -(*, id:ย Optional[str]ย =ย None, name:ย Optional[str]ย =ย None, filetype:ย Optional[str]ย =ย None, title:ย Optional[str]ย =ย None, **kwargs) -
-
-
-
- -Expand source code - -
class File:
-    id: Optional[str]
-    name: Optional[str]
-    filetype: Optional[str]
-    title: Optional[str]
-    unknown_fields: Dict[str, Any]
-
-    def __init__(
-        self,
-        *,
-        id: Optional[str] = None,
-        name: Optional[str] = None,
-        filetype: Optional[str] = None,
-        title: Optional[str] = None,
-        **kwargs,
-    ) -> None:
-        self.id = id
-        self.name = name
-        self.filetype = filetype
-        self.title = title
-        self.unknown_fields = kwargs
-
-

Class variables

-
-
var filetype :ย Optional[str]
-
-
-
-
var id :ย Optional[str]
-
-
-
-
var name :ย Optional[str]
-
-
-
-
var title :ย Optional[str]
-
-
-
-
var unknown_fields :ย Dict[str,ย Any]
-
-
-
-
-
-
-class InformationBarrier -(*, id:ย Optional[str]ย =ย None, primary_usergroup:ย Optional[str]ย =ย None, barriered_from_usergroups:ย Optional[List[str]]ย =ย None, restricted_subjects:ย Optional[List[str]]ย =ย None, **kwargs) -
-
-
-
- -Expand source code - -
class InformationBarrier:
-    id: Optional[str]
-    primary_usergroup: Optional[str]
-    barriered_from_usergroups: Optional[List[str]]
-    restricted_subjects: Optional[List[str]]
-    unknown_fields: Dict[str, Any]
-
-    def __init__(
-        self,
-        *,
-        id: Optional[str] = None,
-        primary_usergroup: Optional[str] = None,
-        barriered_from_usergroups: Optional[List[str]] = None,
-        restricted_subjects: Optional[List[str]] = None,
-        **kwargs,
-    ) -> None:
-        self.id = id
-        self.primary_usergroup = primary_usergroup
-        self.barriered_from_usergroups = barriered_from_usergroups
-        self.restricted_subjects = restricted_subjects
-        self.unknown_fields = kwargs
-
-

Class variables

-
-
var barriered_from_usergroups :ย Optional[List[str]]
-
-
-
-
var id :ย Optional[str]
-
-
-
-
var primary_usergroup :ย Optional[str]
-
-
-
-
var restricted_subjects :ย Optional[List[str]]
-
-
-
-
var unknown_fields :ย Dict[str,ย Any]
-
-
-
-
-
-
-class Location -(*, type:ย Optional[str]ย =ย None, id:ย Optional[str]ย =ย None, name:ย Optional[str]ย =ย None, domain:ย Optional[str]ย =ย None, **kwargs) -
-
-
-
- -Expand source code - -
class Location:
-    type: Optional[str]
-    id: Optional[str]
-    name: Optional[str]
-    domain: Optional[str]
-    unknown_fields: Dict[str, Any]
-
-    def __init__(
-        self,
-        *,
-        type: Optional[str] = None,
-        id: Optional[str] = None,
-        name: Optional[str] = None,
-        domain: Optional[str] = None,
-        **kwargs,
-    ) -> None:
-        self.type = type
-        self.id = id
-        self.name = name
-        self.domain = domain
-        self.unknown_fields = kwargs
-
-

Class variables

-
-
var domain :ย Optional[str]
-
-
-
-
var id :ย Optional[str]
-
-
-
-
var name :ย Optional[str]
-
-
-
-
var type :ย Optional[str]
-
-
-
-
var unknown_fields :ย Dict[str,ย Any]
-
-
-
-
-
-
-class LogsResponse -(*, entries:ย Optional[List[Union[Entry,ย dict]]]ย =ย None, response_metadata:ย Union[ResponseMetadata,ย dict,ย ForwardRef(None)]ย =ย None, ok:ย Optional[bool]ย =ย None, error:ย Optional[str]ย =ย None, needed:ย Optional[str]ย =ย None, provided:ย Optional[str]ย =ย None, **kwargs) -
-
-
-
- -Expand source code - -
class LogsResponse:
-    entries: Optional[List[Entry]]
-    response_metadata: Optional[ResponseMetadata]
-    ok: Optional[bool]
-    error: Optional[str]
-    needed: Optional[str]
-    provided: Optional[str]
-    unknown_fields: Dict[str, Any]
-
-    def __init__(
-        self,
-        *,
-        entries: Optional[List[Union[Entry, dict]]] = None,
-        response_metadata: Optional[Union[ResponseMetadata, dict]] = None,
-        ok: Optional[bool] = None,
-        error: Optional[str] = None,
-        needed: Optional[str] = None,
-        provided: Optional[str] = None,
-        **kwargs,
-    ) -> None:
-        self.entries = [Entry(**e) if isinstance(e, dict) else e for e in entries]
-        self.response_metadata = (
-            ResponseMetadata(**response_metadata)
-            if isinstance(response_metadata, dict)
-            else response_metadata
-        )
-        self.ok = ok
-        self.error = error
-        self.needed = needed
-        self.provided = provided
-        self.unknown_fields = kwargs
-
-

Class variables

-
-
var entries :ย Optional[List[Entry]]
-
-
-
-
var error :ย Optional[str]
-
-
-
-
var needed :ย Optional[str]
-
-
-
-
var ok :ย Optional[bool]
-
-
-
-
var provided :ย Optional[str]
-
-
-
-
var response_metadata :ย Optional[ResponseMetadata]
-
-
-
-
var unknown_fields :ย Dict[str,ย Any]
-
-
-
-
-
-
-class ResponseMetadata -(*, next_cursor:ย Optional[str]ย =ย None, **kwargs) -
-
-
-
- -Expand source code - -
class ResponseMetadata:
-    next_cursor: Optional[str]
-    unknown_fields: Dict[str, Any]
-
-    def __init__(
-        self,
-        *,
-        next_cursor: Optional[str] = None,
-        **kwargs,
-    ) -> None:
-        self.next_cursor = next_cursor
-        self.unknown_fields = kwargs
-
-

Class variables

-
-
var next_cursor :ย Optional[str]
-
-
-
-
var unknown_fields :ย Dict[str,ย Any]
-
-
-
-
-
-
-class RetentionPolicy -(*, type:ย Optional[str]ย =ย None, duration_days:ย Optional[int]ย =ย None, **kwargs) -
-
-
-
- -Expand source code - -
class RetentionPolicy:
-    type: Optional[str]
-    duration_days: Optional[int]
-
-    def __init__(
-        self,
-        *,
-        type: Optional[str] = None,
-        duration_days: Optional[int] = None,
-        **kwargs,
-    ) -> None:
-        self.type = type
-        self.duration_days = duration_days
-        self.unknown_fields = kwargs
-
-

Class variables

-
-
var duration_days :ย Optional[int]
-
-
-
-
var type :ย Optional[str]
-
-
-
-
-
-
-class User -(*, id:ย Optional[str]ย =ย None, name:ย Optional[str]ย =ย None, email:ย Optional[str]ย =ย None, team:ย Optional[str]ย =ย None, **kwargs) -
-
-
-
- -Expand source code - -
class User:
-    id: Optional[str]
-    name: Optional[str]
-    email: Optional[str]
-    team: Optional[str]
-    unknown_fields: Dict[str, Any]
-
-    def __init__(
-        self,
-        *,
-        id: Optional[str] = None,
-        name: Optional[str] = None,
-        email: Optional[str] = None,
-        team: Optional[str] = None,
-        **kwargs,
-    ) -> None:
-        self.id = id
-        self.name = name
-        self.email = email
-        self.team = team
-        self.unknown_fields = kwargs
-
-

Class variables

-
-
var email :ย Optional[str]
-
-
-
-
var id :ย Optional[str]
-
-
-
-
var name :ย Optional[str]
-
-
-
-
var team :ย Optional[str]
-
-
-
-
var unknown_fields :ย Dict[str,ย Any]
-
-
-
-
-
-
-class Usergroup -(*, id:ย Optional[str]ย =ย None, name:ย Optional[str]ย =ย None, **kwargs) -
-
-
-
- -Expand source code - -
class Usergroup:
-    id: Optional[str]
-    name: Optional[str]
-    unknown_fields: Dict[str, Any]
-
-    def __init__(
-        self,
-        *,
-        id: Optional[str] = None,
-        name: Optional[str] = None,
-        **kwargs,
-    ) -> None:
-        self.id = id
-        self.name = name
-        self.unknown_fields = kwargs
-
-

Class variables

-
-
var id :ย Optional[str]
-
-
-
-
var name :ย Optional[str]
-
-
-
-
var unknown_fields :ย Dict[str,ย Any]
-
-
-
-
-
-
-class Workflow -(*, id:ย Optional[str]ย =ย None, name:ย Optional[str]ย =ย None, domain:ย Optional[str]ย =ย None, **kwargs) -
-
-
-
- -Expand source code - -
class Workflow:
-    id: Optional[str]
-    name: Optional[str]
-    domain: Optional[str]
-    unknown_fields: Dict[str, Any]
-
-    def __init__(
-        self,
-        *,
-        id: Optional[str] = None,
-        name: Optional[str] = None,
-        domain: Optional[str] = None,
-        **kwargs,
-    ) -> None:
-        self.id = id
-        self.name = name
-        self.domain = domain
-        self.unknown_fields = kwargs
-
-

Class variables

-
-
var domain :ย Optional[str]
-
-
-
-
var id :ย Optional[str]
-
-
-
-
var name :ย Optional[str]
-
-
-
-
var unknown_fields :ย Dict[str,ย Any]
-
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/audit_logs/v1/response.html b/docs/api-docs/slack_sdk/audit_logs/v1/response.html deleted file mode 100644 index 0ba82c6b6..000000000 --- a/docs/api-docs/slack_sdk/audit_logs/v1/response.html +++ /dev/null @@ -1,190 +0,0 @@ - - - - - - -slack_sdk.audit_logs.v1.response API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.audit_logs.v1.response

-
-
-
- -Expand source code - -
import json
-from typing import Dict, Any
-
-from slack_sdk.audit_logs.v1.logs import LogsResponse
-
-
-class AuditLogsResponse:
-    url: str
-    status_code: int
-    headers: Dict[str, Any]
-    raw_body: str
-    body: Dict[str, Any]
-    typed_body: LogsResponse
-
-    @property
-    def typed_body(self) -> LogsResponse:
-        return LogsResponse(**self.body)
-
-    def __init__(
-        self,
-        *,
-        url: str,
-        status_code: int,
-        raw_body: str,
-        headers: dict,
-    ):
-        self.url = url
-        self.status_code = status_code
-        self.headers = headers
-        self.raw_body = raw_body
-        self.body = (
-            json.loads(raw_body)
-            if raw_body is not None and raw_body.startswith("{")
-            else None
-        )
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class AuditLogsResponse -(*, url:ย str, status_code:ย int, raw_body:ย str, headers:ย dict) -
-
-
-
- -Expand source code - -
class AuditLogsResponse:
-    url: str
-    status_code: int
-    headers: Dict[str, Any]
-    raw_body: str
-    body: Dict[str, Any]
-    typed_body: LogsResponse
-
-    @property
-    def typed_body(self) -> LogsResponse:
-        return LogsResponse(**self.body)
-
-    def __init__(
-        self,
-        *,
-        url: str,
-        status_code: int,
-        raw_body: str,
-        headers: dict,
-    ):
-        self.url = url
-        self.status_code = status_code
-        self.headers = headers
-        self.raw_body = raw_body
-        self.body = (
-            json.loads(raw_body)
-            if raw_body is not None and raw_body.startswith("{")
-            else None
-        )
-
-

Class variables

-
-
var body :ย Dict[str,ย Any]
-
-
-
-
var headers :ย Dict[str,ย Any]
-
-
-
-
var raw_body :ย str
-
-
-
-
var status_code :ย int
-
-
-
-
var url :ย str
-
-
-
-
-

Instance variables

-
-
var typed_body :ย LogsResponse
-
-
-
- -Expand source code - -
@property
-def typed_body(self) -> LogsResponse:
-    return LogsResponse(**self.body)
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/http_retry/async_handler.html b/docs/api-docs/slack_sdk/http_retry/async_handler.html deleted file mode 100644 index c65fb56fb..000000000 --- a/docs/api-docs/slack_sdk/http_retry/async_handler.html +++ /dev/null @@ -1,313 +0,0 @@ - - - - - - -slack_sdk.http_retry.async_handler API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.http_retry.async_handler

-
-
-

asyncio compatible RetryHandler interface. -You can pass an array of handlers to customize retry logics in supported API clients.

-
- -Expand source code - -
"""asyncio compatible RetryHandler interface.
-You can pass an array of handlers to customize retry logics in supported API clients.
-"""
-
-import asyncio
-from typing import Optional
-
-from slack_sdk.http_retry.state import RetryState
-from slack_sdk.http_retry.request import HttpRequest
-from slack_sdk.http_retry.response import HttpResponse
-from slack_sdk.http_retry.interval_calculator import RetryIntervalCalculator
-from slack_sdk.http_retry.builtin_interval_calculators import (
-    BackoffRetryIntervalCalculator,
-)
-
-default_interval_calculator = BackoffRetryIntervalCalculator()
-
-
-class AsyncRetryHandler:
-    """asyncio compatible RetryHandler interface.
-    You can pass an array of handlers to customize retry logics in supported API clients.
-    """
-
-    max_retry_count: int
-    interval_calculator: RetryIntervalCalculator
-
-    def __init__(
-        self,
-        max_retry_count: int = 1,
-        interval_calculator: RetryIntervalCalculator = default_interval_calculator,
-    ):
-        """RetryHandler interface.
-
-        Args:
-            max_retry_count: The maximum times to do retries
-            interval_calculator: Pass an interval calculator for customizing the logic
-        """
-        self.max_retry_count = max_retry_count
-        self.interval_calculator = interval_calculator
-
-    async def can_retry_async(
-        self,
-        *,
-        state: RetryState,
-        request: HttpRequest,
-        response: Optional[HttpResponse] = None,
-        error: Optional[Exception] = None,
-    ) -> bool:
-        if state.current_attempt >= self.max_retry_count:
-            return False
-        return await self._can_retry_async(
-            state=state,
-            request=request,
-            response=response,
-            error=error,
-        )
-
-    async def _can_retry_async(
-        self,
-        *,
-        state: RetryState,
-        request: HttpRequest,
-        response: Optional[HttpResponse] = None,
-        error: Optional[Exception] = None,
-    ) -> bool:
-        raise NotImplementedError()
-
-    async def prepare_for_next_attempt_async(
-        self,
-        *,
-        state: RetryState,
-        request: HttpRequest,
-        response: Optional[HttpResponse] = None,
-        error: Optional[Exception] = None,
-    ) -> None:
-        state.next_attempt_requested = True
-        duration = self.interval_calculator.calculate_sleep_duration(
-            state.current_attempt
-        )
-        await asyncio.sleep(duration)
-        state.increment_current_attempt()
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class AsyncRetryHandler -(max_retry_count:ย intย =ย 1, interval_calculator:ย RetryIntervalCalculatorย =ย <slack_sdk.http_retry.builtin_interval_calculators.BackoffRetryIntervalCalculator object>) -
-
-

asyncio compatible RetryHandler interface. -You can pass an array of handlers to customize retry logics in supported API clients.

-

RetryHandler interface.

-

Args

-
-
max_retry_count
-
The maximum times to do retries
-
interval_calculator
-
Pass an interval calculator for customizing the logic
-
-
- -Expand source code - -
class AsyncRetryHandler:
-    """asyncio compatible RetryHandler interface.
-    You can pass an array of handlers to customize retry logics in supported API clients.
-    """
-
-    max_retry_count: int
-    interval_calculator: RetryIntervalCalculator
-
-    def __init__(
-        self,
-        max_retry_count: int = 1,
-        interval_calculator: RetryIntervalCalculator = default_interval_calculator,
-    ):
-        """RetryHandler interface.
-
-        Args:
-            max_retry_count: The maximum times to do retries
-            interval_calculator: Pass an interval calculator for customizing the logic
-        """
-        self.max_retry_count = max_retry_count
-        self.interval_calculator = interval_calculator
-
-    async def can_retry_async(
-        self,
-        *,
-        state: RetryState,
-        request: HttpRequest,
-        response: Optional[HttpResponse] = None,
-        error: Optional[Exception] = None,
-    ) -> bool:
-        if state.current_attempt >= self.max_retry_count:
-            return False
-        return await self._can_retry_async(
-            state=state,
-            request=request,
-            response=response,
-            error=error,
-        )
-
-    async def _can_retry_async(
-        self,
-        *,
-        state: RetryState,
-        request: HttpRequest,
-        response: Optional[HttpResponse] = None,
-        error: Optional[Exception] = None,
-    ) -> bool:
-        raise NotImplementedError()
-
-    async def prepare_for_next_attempt_async(
-        self,
-        *,
-        state: RetryState,
-        request: HttpRequest,
-        response: Optional[HttpResponse] = None,
-        error: Optional[Exception] = None,
-    ) -> None:
-        state.next_attempt_requested = True
-        duration = self.interval_calculator.calculate_sleep_duration(
-            state.current_attempt
-        )
-        await asyncio.sleep(duration)
-        state.increment_current_attempt()
-
-

Subclasses

- -

Class variables

-
-
var interval_calculator :ย RetryIntervalCalculator
-
-
-
-
var max_retry_count :ย int
-
-
-
-
-

Methods

-
-
-async def can_retry_async(self, *, state:ย RetryState, request:ย HttpRequest, response:ย Optional[HttpResponse]ย =ย None, error:ย Optional[Exception]ย =ย None) โ€‘>ย bool -
-
-
-
- -Expand source code - -
async def can_retry_async(
-    self,
-    *,
-    state: RetryState,
-    request: HttpRequest,
-    response: Optional[HttpResponse] = None,
-    error: Optional[Exception] = None,
-) -> bool:
-    if state.current_attempt >= self.max_retry_count:
-        return False
-    return await self._can_retry_async(
-        state=state,
-        request=request,
-        response=response,
-        error=error,
-    )
-
-
-
-async def prepare_for_next_attempt_async(self, *, state:ย RetryState, request:ย HttpRequest, response:ย Optional[HttpResponse]ย =ย None, error:ย Optional[Exception]ย =ย None) โ€‘>ย None -
-
-
-
- -Expand source code - -
async def prepare_for_next_attempt_async(
-    self,
-    *,
-    state: RetryState,
-    request: HttpRequest,
-    response: Optional[HttpResponse] = None,
-    error: Optional[Exception] = None,
-) -> None:
-    state.next_attempt_requested = True
-    duration = self.interval_calculator.calculate_sleep_duration(
-        state.current_attempt
-    )
-    await asyncio.sleep(duration)
-    state.increment_current_attempt()
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/http_retry/builtin_async_handlers.html b/docs/api-docs/slack_sdk/http_retry/builtin_async_handlers.html deleted file mode 100644 index 4c179133f..000000000 --- a/docs/api-docs/slack_sdk/http_retry/builtin_async_handlers.html +++ /dev/null @@ -1,376 +0,0 @@ - - - - - - -slack_sdk.http_retry.builtin_async_handlers API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.http_retry.builtin_async_handlers

-
-
-
- -Expand source code - -
import asyncio
-import random
-from typing import Optional, List
-
-from aiohttp import ServerDisconnectedError, ServerConnectionError, ClientOSError
-
-from slack_sdk.http_retry.async_handler import AsyncRetryHandler
-from slack_sdk.http_retry.interval_calculator import RetryIntervalCalculator
-from slack_sdk.http_retry.state import RetryState
-from slack_sdk.http_retry.request import HttpRequest
-from slack_sdk.http_retry.response import HttpResponse
-from slack_sdk.http_retry.handler import default_interval_calculator
-
-
-class AsyncConnectionErrorRetryHandler(AsyncRetryHandler):
-    """RetryHandler that does retries for connectivity issues."""
-
-    def __init__(
-        self,
-        max_retry_count: int = 1,
-        interval_calculator: RetryIntervalCalculator = default_interval_calculator,
-        error_types: List[Exception] = [
-            ServerConnectionError,
-            ServerDisconnectedError,
-            # ClientOSError: [Errno 104] Connection reset by peer
-            ClientOSError,
-        ],
-    ):
-        super().__init__(max_retry_count, interval_calculator)
-        self.error_types_to_do_retries = error_types
-
-    async def _can_retry_async(
-        self,
-        *,
-        state: RetryState,
-        request: HttpRequest,
-        response: Optional[HttpResponse] = None,
-        error: Optional[Exception] = None,
-    ) -> bool:
-        if error is None:
-            return False
-
-        for error_type in self.error_types_to_do_retries:
-            if isinstance(error, error_type):
-                return True
-        return False
-
-
-class AsyncRateLimitErrorRetryHandler(AsyncRetryHandler):
-    """RetryHandler that does retries for rate limited errors."""
-
-    async def _can_retry_async(
-        self,
-        *,
-        state: RetryState,
-        request: HttpRequest,
-        response: Optional[HttpResponse],
-        error: Optional[Exception],
-    ) -> bool:
-        return response.status_code == 429
-
-    async def prepare_for_next_attempt_async(
-        self,
-        *,
-        state: RetryState,
-        request: HttpRequest,
-        response: Optional[HttpResponse] = None,
-        error: Optional[Exception] = None,
-    ) -> None:
-        if response is None:
-            raise error
-
-        state.next_attempt_requested = True
-        retry_after_header_name: Optional[str] = None
-        for k in response.headers.keys():
-            if k.lower() == "retry-after":
-                retry_after_header_name = k
-                break
-        duration = 1
-        if retry_after_header_name is None:
-            # This situation usually does not arise. Just in case.
-            duration += random.random()
-        else:
-            duration = (
-                int(response.headers.get(retry_after_header_name)[0]) + random.random()
-            )
-        await asyncio.sleep(duration)
-        state.increment_current_attempt()
-
-
-def async_default_handlers() -> List[AsyncRetryHandler]:
-    return [AsyncConnectionErrorRetryHandler()]
-
-
-
-
-
-
-
-

Functions

-
-
-def async_default_handlers() โ€‘>ย List[AsyncRetryHandler] -
-
-
-
- -Expand source code - -
def async_default_handlers() -> List[AsyncRetryHandler]:
-    return [AsyncConnectionErrorRetryHandler()]
-
-
-
-
-
-

Classes

-
-
-class AsyncConnectionErrorRetryHandler -(max_retry_count:ย intย =ย 1, interval_calculator:ย RetryIntervalCalculatorย =ย <slack_sdk.http_retry.builtin_interval_calculators.BackoffRetryIntervalCalculator object>, error_types:ย List[Exception]ย =ย [<class 'aiohttp.client_exceptions.ServerConnectionError'>, <class 'aiohttp.client_exceptions.ServerDisconnectedError'>, <class 'aiohttp.client_exceptions.ClientOSError'>]) -
-
-

RetryHandler that does retries for connectivity issues.

-

RetryHandler interface.

-

Args

-
-
max_retry_count
-
The maximum times to do retries
-
interval_calculator
-
Pass an interval calculator for customizing the logic
-
-
- -Expand source code - -
class AsyncConnectionErrorRetryHandler(AsyncRetryHandler):
-    """RetryHandler that does retries for connectivity issues."""
-
-    def __init__(
-        self,
-        max_retry_count: int = 1,
-        interval_calculator: RetryIntervalCalculator = default_interval_calculator,
-        error_types: List[Exception] = [
-            ServerConnectionError,
-            ServerDisconnectedError,
-            # ClientOSError: [Errno 104] Connection reset by peer
-            ClientOSError,
-        ],
-    ):
-        super().__init__(max_retry_count, interval_calculator)
-        self.error_types_to_do_retries = error_types
-
-    async def _can_retry_async(
-        self,
-        *,
-        state: RetryState,
-        request: HttpRequest,
-        response: Optional[HttpResponse] = None,
-        error: Optional[Exception] = None,
-    ) -> bool:
-        if error is None:
-            return False
-
-        for error_type in self.error_types_to_do_retries:
-            if isinstance(error, error_type):
-                return True
-        return False
-
-

Ancestors

- -

Class variables

-
-
var interval_calculator :ย RetryIntervalCalculator
-
-
-
-
var max_retry_count :ย int
-
-
-
-
-
-
-class AsyncRateLimitErrorRetryHandler -(max_retry_count:ย intย =ย 1, interval_calculator:ย RetryIntervalCalculatorย =ย <slack_sdk.http_retry.builtin_interval_calculators.BackoffRetryIntervalCalculator object>) -
-
-

RetryHandler that does retries for rate limited errors.

-

RetryHandler interface.

-

Args

-
-
max_retry_count
-
The maximum times to do retries
-
interval_calculator
-
Pass an interval calculator for customizing the logic
-
-
- -Expand source code - -
class AsyncRateLimitErrorRetryHandler(AsyncRetryHandler):
-    """RetryHandler that does retries for rate limited errors."""
-
-    async def _can_retry_async(
-        self,
-        *,
-        state: RetryState,
-        request: HttpRequest,
-        response: Optional[HttpResponse],
-        error: Optional[Exception],
-    ) -> bool:
-        return response.status_code == 429
-
-    async def prepare_for_next_attempt_async(
-        self,
-        *,
-        state: RetryState,
-        request: HttpRequest,
-        response: Optional[HttpResponse] = None,
-        error: Optional[Exception] = None,
-    ) -> None:
-        if response is None:
-            raise error
-
-        state.next_attempt_requested = True
-        retry_after_header_name: Optional[str] = None
-        for k in response.headers.keys():
-            if k.lower() == "retry-after":
-                retry_after_header_name = k
-                break
-        duration = 1
-        if retry_after_header_name is None:
-            # This situation usually does not arise. Just in case.
-            duration += random.random()
-        else:
-            duration = (
-                int(response.headers.get(retry_after_header_name)[0]) + random.random()
-            )
-        await asyncio.sleep(duration)
-        state.increment_current_attempt()
-
-

Ancestors

- -

Class variables

-
-
var interval_calculator :ย RetryIntervalCalculator
-
-
-
-
var max_retry_count :ย int
-
-
-
-
-

Methods

-
-
-async def prepare_for_next_attempt_async(self, *, state:ย RetryState, request:ย HttpRequest, response:ย Optional[HttpResponse]ย =ย None, error:ย Optional[Exception]ย =ย None) โ€‘>ย None -
-
-
-
- -Expand source code - -
async def prepare_for_next_attempt_async(
-    self,
-    *,
-    state: RetryState,
-    request: HttpRequest,
-    response: Optional[HttpResponse] = None,
-    error: Optional[Exception] = None,
-) -> None:
-    if response is None:
-        raise error
-
-    state.next_attempt_requested = True
-    retry_after_header_name: Optional[str] = None
-    for k in response.headers.keys():
-        if k.lower() == "retry-after":
-            retry_after_header_name = k
-            break
-    duration = 1
-    if retry_after_header_name is None:
-        # This situation usually does not arise. Just in case.
-        duration += random.random()
-    else:
-        duration = (
-            int(response.headers.get(retry_after_header_name)[0]) + random.random()
-        )
-    await asyncio.sleep(duration)
-    state.increment_current_attempt()
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/http_retry/builtin_handlers.html b/docs/api-docs/slack_sdk/http_retry/builtin_handlers.html deleted file mode 100644 index c5f4a3a82..000000000 --- a/docs/api-docs/slack_sdk/http_retry/builtin_handlers.html +++ /dev/null @@ -1,358 +0,0 @@ - - - - - - -slack_sdk.http_retry.builtin_handlers API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.http_retry.builtin_handlers

-
-
-
- -Expand source code - -
import random
-import time
-from http.client import RemoteDisconnected
-from typing import Optional, List
-from urllib.error import URLError
-
-from slack_sdk.http_retry.interval_calculator import RetryIntervalCalculator
-from slack_sdk.http_retry.state import RetryState
-from slack_sdk.http_retry.request import HttpRequest
-from slack_sdk.http_retry.response import HttpResponse
-from slack_sdk.http_retry.handler import RetryHandler, default_interval_calculator
-
-
-class ConnectionErrorRetryHandler(RetryHandler):
-    """RetryHandler that does retries for connectivity issues."""
-
-    def __init__(
-        self,
-        max_retry_count: int = 1,
-        interval_calculator: RetryIntervalCalculator = default_interval_calculator,
-        error_types: List[Exception] = [
-            # To cover URLError: <urlopen error [Errno 104] Connection reset by peer>
-            URLError,
-            ConnectionResetError,
-            RemoteDisconnected,
-        ],
-    ):
-        super().__init__(max_retry_count, interval_calculator)
-        self.error_types_to_do_retries = error_types
-
-    def _can_retry(
-        self,
-        *,
-        state: RetryState,
-        request: HttpRequest,
-        response: Optional[HttpResponse] = None,
-        error: Optional[Exception] = None,
-    ) -> bool:
-        if error is None:
-            return False
-
-        if isinstance(error, URLError):
-            if response is not None:
-                return False  # status 40x
-
-        for error_type in self.error_types_to_do_retries:
-            if isinstance(error, error_type):
-                return True
-        return False
-
-
-class RateLimitErrorRetryHandler(RetryHandler):
-    """RetryHandler that does retries for rate limited errors."""
-
-    def _can_retry(
-        self,
-        *,
-        state: RetryState,
-        request: HttpRequest,
-        response: Optional[HttpResponse] = None,
-        error: Optional[Exception] = None,
-    ) -> bool:
-        return response is not None and response.status_code == 429
-
-    def prepare_for_next_attempt(
-        self,
-        *,
-        state: RetryState,
-        request: HttpRequest,
-        response: Optional[HttpResponse] = None,
-        error: Optional[Exception] = None,
-    ) -> None:
-        if response is None:
-            raise error
-
-        state.next_attempt_requested = True
-        retry_after_header_name: Optional[str] = None
-        for k in response.headers.keys():
-            if k.lower() == "retry-after":
-                retry_after_header_name = k
-                break
-        duration = 1
-        if retry_after_header_name is None:
-            # This situation usually does not arise. Just in case.
-            duration += random.random()
-        else:
-            duration = (
-                int(response.headers.get(retry_after_header_name)[0]) + random.random()
-            )
-        time.sleep(duration)
-        state.increment_current_attempt()
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class ConnectionErrorRetryHandler -(max_retry_count:ย intย =ย 1, interval_calculator:ย RetryIntervalCalculatorย =ย <slack_sdk.http_retry.builtin_interval_calculators.BackoffRetryIntervalCalculator object>, error_types:ย List[Exception]ย =ย [<class 'urllib.error.URLError'>, <class 'ConnectionResetError'>, <class 'http.client.RemoteDisconnected'>]) -
-
-

RetryHandler that does retries for connectivity issues.

-

RetryHandler interface.

-

Args

-
-
max_retry_count
-
The maximum times to do retries
-
interval_calculator
-
Pass an interval calculator for customizing the logic
-
-
- -Expand source code - -
class ConnectionErrorRetryHandler(RetryHandler):
-    """RetryHandler that does retries for connectivity issues."""
-
-    def __init__(
-        self,
-        max_retry_count: int = 1,
-        interval_calculator: RetryIntervalCalculator = default_interval_calculator,
-        error_types: List[Exception] = [
-            # To cover URLError: <urlopen error [Errno 104] Connection reset by peer>
-            URLError,
-            ConnectionResetError,
-            RemoteDisconnected,
-        ],
-    ):
-        super().__init__(max_retry_count, interval_calculator)
-        self.error_types_to_do_retries = error_types
-
-    def _can_retry(
-        self,
-        *,
-        state: RetryState,
-        request: HttpRequest,
-        response: Optional[HttpResponse] = None,
-        error: Optional[Exception] = None,
-    ) -> bool:
-        if error is None:
-            return False
-
-        if isinstance(error, URLError):
-            if response is not None:
-                return False  # status 40x
-
-        for error_type in self.error_types_to_do_retries:
-            if isinstance(error, error_type):
-                return True
-        return False
-
-

Ancestors

- -

Class variables

-
-
var interval_calculator :ย RetryIntervalCalculator
-
-
-
-
var max_retry_count :ย int
-
-
-
-
-
-
-class RateLimitErrorRetryHandler -(max_retry_count:ย intย =ย 1, interval_calculator:ย RetryIntervalCalculatorย =ย <slack_sdk.http_retry.builtin_interval_calculators.BackoffRetryIntervalCalculator object>) -
-
-

RetryHandler that does retries for rate limited errors.

-

RetryHandler interface.

-

Args

-
-
max_retry_count
-
The maximum times to do retries
-
interval_calculator
-
Pass an interval calculator for customizing the logic
-
-
- -Expand source code - -
class RateLimitErrorRetryHandler(RetryHandler):
-    """RetryHandler that does retries for rate limited errors."""
-
-    def _can_retry(
-        self,
-        *,
-        state: RetryState,
-        request: HttpRequest,
-        response: Optional[HttpResponse] = None,
-        error: Optional[Exception] = None,
-    ) -> bool:
-        return response is not None and response.status_code == 429
-
-    def prepare_for_next_attempt(
-        self,
-        *,
-        state: RetryState,
-        request: HttpRequest,
-        response: Optional[HttpResponse] = None,
-        error: Optional[Exception] = None,
-    ) -> None:
-        if response is None:
-            raise error
-
-        state.next_attempt_requested = True
-        retry_after_header_name: Optional[str] = None
-        for k in response.headers.keys():
-            if k.lower() == "retry-after":
-                retry_after_header_name = k
-                break
-        duration = 1
-        if retry_after_header_name is None:
-            # This situation usually does not arise. Just in case.
-            duration += random.random()
-        else:
-            duration = (
-                int(response.headers.get(retry_after_header_name)[0]) + random.random()
-            )
-        time.sleep(duration)
-        state.increment_current_attempt()
-
-

Ancestors

- -

Class variables

-
-
var interval_calculator :ย RetryIntervalCalculator
-
-
-
-
var max_retry_count :ย int
-
-
-
-
-

Methods

-
-
-def prepare_for_next_attempt(self, *, state:ย RetryState, request:ย HttpRequest, response:ย Optional[HttpResponse]ย =ย None, error:ย Optional[Exception]ย =ย None) โ€‘>ย None -
-
-
-
- -Expand source code - -
def prepare_for_next_attempt(
-    self,
-    *,
-    state: RetryState,
-    request: HttpRequest,
-    response: Optional[HttpResponse] = None,
-    error: Optional[Exception] = None,
-) -> None:
-    if response is None:
-        raise error
-
-    state.next_attempt_requested = True
-    retry_after_header_name: Optional[str] = None
-    for k in response.headers.keys():
-        if k.lower() == "retry-after":
-            retry_after_header_name = k
-            break
-    duration = 1
-    if retry_after_header_name is None:
-        # This situation usually does not arise. Just in case.
-        duration += random.random()
-    else:
-        duration = (
-            int(response.headers.get(retry_after_header_name)[0]) + random.random()
-        )
-    time.sleep(duration)
-    state.increment_current_attempt()
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/http_retry/handler.html b/docs/api-docs/slack_sdk/http_retry/handler.html deleted file mode 100644 index 3a1bcd4ed..000000000 --- a/docs/api-docs/slack_sdk/http_retry/handler.html +++ /dev/null @@ -1,314 +0,0 @@ - - - - - - -slack_sdk.http_retry.handler API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.http_retry.handler

-
-
-

RetryHandler interface. -You can pass an array of handlers to customize retry logics in supported API clients.

-
- -Expand source code - -
"""RetryHandler interface.
-You can pass an array of handlers to customize retry logics in supported API clients.
-"""
-
-import time
-from typing import Optional
-
-from slack_sdk.http_retry.state import RetryState
-from slack_sdk.http_retry.request import HttpRequest
-from slack_sdk.http_retry.response import HttpResponse
-from slack_sdk.http_retry.interval_calculator import RetryIntervalCalculator
-from slack_sdk.http_retry.builtin_interval_calculators import (
-    BackoffRetryIntervalCalculator,
-)
-
-default_interval_calculator = BackoffRetryIntervalCalculator()
-
-
-# Note that you cannot add aiohttp to this class as the external dependency is optional
-class RetryHandler:
-    """RetryHandler interface.
-    You can pass an array of handlers to customize retry logics in supported API clients.
-    """
-
-    max_retry_count: int
-    interval_calculator: RetryIntervalCalculator
-
-    def __init__(
-        self,
-        max_retry_count: int = 1,
-        interval_calculator: RetryIntervalCalculator = default_interval_calculator,
-    ):
-        """RetryHandler interface.
-
-        Args:
-            max_retry_count: The maximum times to do retries
-            interval_calculator: Pass an interval calculator for customizing the logic
-        """
-        self.max_retry_count = max_retry_count
-        self.interval_calculator = interval_calculator
-
-    def can_retry(
-        self,
-        *,
-        state: RetryState,
-        request: HttpRequest,
-        response: Optional[HttpResponse] = None,
-        error: Optional[Exception] = None,
-    ) -> bool:
-        if state.current_attempt >= self.max_retry_count:
-            return False
-        return self._can_retry(
-            state=state,
-            request=request,
-            response=response,
-            error=error,
-        )
-
-    def _can_retry(
-        self,
-        *,
-        state: RetryState,
-        request: HttpRequest,
-        response: Optional[HttpResponse] = None,
-        error: Optional[Exception] = None,
-    ) -> bool:
-        raise NotImplementedError()
-
-    def prepare_for_next_attempt(
-        self,
-        *,
-        state: RetryState,
-        request: HttpRequest,
-        response: Optional[HttpResponse] = None,
-        error: Optional[Exception] = None,
-    ) -> None:
-        state.next_attempt_requested = True
-        duration = self.interval_calculator.calculate_sleep_duration(
-            state.current_attempt
-        )
-        time.sleep(duration)
-        state.increment_current_attempt()
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class RetryHandler -(max_retry_count:ย intย =ย 1, interval_calculator:ย RetryIntervalCalculatorย =ย <slack_sdk.http_retry.builtin_interval_calculators.BackoffRetryIntervalCalculator object>) -
-
-

RetryHandler interface. -You can pass an array of handlers to customize retry logics in supported API clients.

-

RetryHandler interface.

-

Args

-
-
max_retry_count
-
The maximum times to do retries
-
interval_calculator
-
Pass an interval calculator for customizing the logic
-
-
- -Expand source code - -
class RetryHandler:
-    """RetryHandler interface.
-    You can pass an array of handlers to customize retry logics in supported API clients.
-    """
-
-    max_retry_count: int
-    interval_calculator: RetryIntervalCalculator
-
-    def __init__(
-        self,
-        max_retry_count: int = 1,
-        interval_calculator: RetryIntervalCalculator = default_interval_calculator,
-    ):
-        """RetryHandler interface.
-
-        Args:
-            max_retry_count: The maximum times to do retries
-            interval_calculator: Pass an interval calculator for customizing the logic
-        """
-        self.max_retry_count = max_retry_count
-        self.interval_calculator = interval_calculator
-
-    def can_retry(
-        self,
-        *,
-        state: RetryState,
-        request: HttpRequest,
-        response: Optional[HttpResponse] = None,
-        error: Optional[Exception] = None,
-    ) -> bool:
-        if state.current_attempt >= self.max_retry_count:
-            return False
-        return self._can_retry(
-            state=state,
-            request=request,
-            response=response,
-            error=error,
-        )
-
-    def _can_retry(
-        self,
-        *,
-        state: RetryState,
-        request: HttpRequest,
-        response: Optional[HttpResponse] = None,
-        error: Optional[Exception] = None,
-    ) -> bool:
-        raise NotImplementedError()
-
-    def prepare_for_next_attempt(
-        self,
-        *,
-        state: RetryState,
-        request: HttpRequest,
-        response: Optional[HttpResponse] = None,
-        error: Optional[Exception] = None,
-    ) -> None:
-        state.next_attempt_requested = True
-        duration = self.interval_calculator.calculate_sleep_duration(
-            state.current_attempt
-        )
-        time.sleep(duration)
-        state.increment_current_attempt()
-
-

Subclasses

- -

Class variables

-
-
var interval_calculator :ย RetryIntervalCalculator
-
-
-
-
var max_retry_count :ย int
-
-
-
-
-

Methods

-
-
-def can_retry(self, *, state:ย RetryState, request:ย HttpRequest, response:ย Optional[HttpResponse]ย =ย None, error:ย Optional[Exception]ย =ย None) โ€‘>ย bool -
-
-
-
- -Expand source code - -
def can_retry(
-    self,
-    *,
-    state: RetryState,
-    request: HttpRequest,
-    response: Optional[HttpResponse] = None,
-    error: Optional[Exception] = None,
-) -> bool:
-    if state.current_attempt >= self.max_retry_count:
-        return False
-    return self._can_retry(
-        state=state,
-        request=request,
-        response=response,
-        error=error,
-    )
-
-
-
-def prepare_for_next_attempt(self, *, state:ย RetryState, request:ย HttpRequest, response:ย Optional[HttpResponse]ย =ย None, error:ย Optional[Exception]ย =ย None) โ€‘>ย None -
-
-
-
- -Expand source code - -
def prepare_for_next_attempt(
-    self,
-    *,
-    state: RetryState,
-    request: HttpRequest,
-    response: Optional[HttpResponse] = None,
-    error: Optional[Exception] = None,
-) -> None:
-    state.next_attempt_requested = True
-    duration = self.interval_calculator.calculate_sleep_duration(
-        state.current_attempt
-    )
-    time.sleep(duration)
-    state.increment_current_attempt()
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/http_retry/index.html b/docs/api-docs/slack_sdk/http_retry/index.html deleted file mode 100644 index fbab5135c..000000000 --- a/docs/api-docs/slack_sdk/http_retry/index.html +++ /dev/null @@ -1,185 +0,0 @@ - - - - - - -slack_sdk.http_retry API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.http_retry

-
-
-
- -Expand source code - -
from typing import List
-
-from .handler import RetryHandler  # noqa
-from .builtin_handlers import (
-    ConnectionErrorRetryHandler,  # noqa
-    RateLimitErrorRetryHandler,  # noqa
-)  # noqa
-from .interval_calculator import RetryIntervalCalculator  # noqa
-from .builtin_interval_calculators import (  # noqa
-    FixedValueRetryIntervalCalculator,  # noqa
-    BackoffRetryIntervalCalculator,  # noqa
-)  # noqa
-from .jitter import Jitter  # noqa
-from .request import HttpRequest  # noqa
-from .response import HttpResponse  # noqa
-from .state import RetryState  # noqa
-
-connect_error_retry_handler = ConnectionErrorRetryHandler()  # noqa
-rate_limit_error_retry_handler = RateLimitErrorRetryHandler()  # noqa
-
-
-def default_retry_handlers() -> List[RetryHandler]:
-    return [connect_error_retry_handler]
-
-
-def all_builtin_retry_handlers() -> List[RetryHandler]:
-    return [
-        connect_error_retry_handler,
-        rate_limit_error_retry_handler,
-    ]
-
-
-
-

Sub-modules

-
-
slack_sdk.http_retry.async_handler
-
-

asyncio compatible RetryHandler interface. -You can pass an array of handlers to customize retry logics in supported API clients.

-
-
slack_sdk.http_retry.builtin_async_handlers
-
-
-
-
slack_sdk.http_retry.builtin_handlers
-
-
-
-
slack_sdk.http_retry.builtin_interval_calculators
-
-
-
-
slack_sdk.http_retry.handler
-
-

RetryHandler interface. -You can pass an array of handlers to customize retry logics in supported API clients.

-
-
slack_sdk.http_retry.interval_calculator
-
-
-
-
slack_sdk.http_retry.jitter
-
-
-
-
slack_sdk.http_retry.request
-
-
-
-
slack_sdk.http_retry.response
-
-
-
-
slack_sdk.http_retry.state
-
-
-
-
-
-
-
-
-

Functions

-
-
-def all_builtin_retry_handlers() โ€‘>ย List[RetryHandler] -
-
-
-
- -Expand source code - -
def all_builtin_retry_handlers() -> List[RetryHandler]:
-    return [
-        connect_error_retry_handler,
-        rate_limit_error_retry_handler,
-    ]
-
-
-
-def default_retry_handlers() โ€‘>ย List[RetryHandler] -
-
-
-
- -Expand source code - -
def default_retry_handlers() -> List[RetryHandler]:
-    return [connect_error_retry_handler]
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/http_retry/request.html b/docs/api-docs/slack_sdk/http_retry/request.html deleted file mode 100644 index 10545e298..000000000 --- a/docs/api-docs/slack_sdk/http_retry/request.html +++ /dev/null @@ -1,211 +0,0 @@ - - - - - - -slack_sdk.http_retry.request API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.http_retry.request

-
-
-
- -Expand source code - -
from typing import Dict, Optional, List, Union, Any
-from urllib.request import Request
-
-
-class HttpRequest:
-    """HTTP request representation"""
-
-    method: str
-    url: str
-    headers: Dict[str, Union[str, List[str]]]
-    body_params: Optional[Dict[str, Any]]
-    data: Optional[bytes]
-
-    def __init__(
-        self,
-        *,
-        method: str,
-        url: str,
-        headers: Dict[str, Union[str, List[str]]],
-        body_params: Optional[Dict[str, Any]] = None,
-        data: Optional[bytes] = None,
-    ):
-        self.method = method
-        self.url = url
-        self.headers = {
-            k: v if isinstance(v, list) else [v] for k, v in headers.items()
-        }
-        self.body_params = body_params
-        self.data = data
-
-    @classmethod
-    def from_urllib_http_request(cls, req: Request) -> "HttpRequest":
-        return HttpRequest(
-            method=req.method,
-            url=req.full_url,
-            headers={
-                k: v if isinstance(v, list) else [v] for k, v in req.headers.items()
-            },
-            data=req.data,
-        )
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class HttpRequest -(*, method:ย str, url:ย str, headers:ย Dict[str,ย Union[str,ย List[str]]], body_params:ย Optional[Dict[str,ย Any]]ย =ย None, data:ย Optional[bytes]ย =ย None) -
-
-

HTTP request representation

-
- -Expand source code - -
class HttpRequest:
-    """HTTP request representation"""
-
-    method: str
-    url: str
-    headers: Dict[str, Union[str, List[str]]]
-    body_params: Optional[Dict[str, Any]]
-    data: Optional[bytes]
-
-    def __init__(
-        self,
-        *,
-        method: str,
-        url: str,
-        headers: Dict[str, Union[str, List[str]]],
-        body_params: Optional[Dict[str, Any]] = None,
-        data: Optional[bytes] = None,
-    ):
-        self.method = method
-        self.url = url
-        self.headers = {
-            k: v if isinstance(v, list) else [v] for k, v in headers.items()
-        }
-        self.body_params = body_params
-        self.data = data
-
-    @classmethod
-    def from_urllib_http_request(cls, req: Request) -> "HttpRequest":
-        return HttpRequest(
-            method=req.method,
-            url=req.full_url,
-            headers={
-                k: v if isinstance(v, list) else [v] for k, v in req.headers.items()
-            },
-            data=req.data,
-        )
-
-

Class variables

-
-
var body_params :ย Optional[Dict[str,ย Any]]
-
-
-
-
var data :ย Optional[bytes]
-
-
-
-
var headers :ย Dict[str,ย Union[str,ย List[str]]]
-
-
-
-
var method :ย str
-
-
-
-
var url :ย str
-
-
-
-
-

Static methods

-
-
-def from_urllib_http_request(req:ย urllib.request.Request) โ€‘>ย HttpRequest -
-
-
-
- -Expand source code - -
@classmethod
-def from_urllib_http_request(cls, req: Request) -> "HttpRequest":
-    return HttpRequest(
-        method=req.method,
-        url=req.full_url,
-        headers={
-            k: v if isinstance(v, list) else [v] for k, v in req.headers.items()
-        },
-        data=req.data,
-    )
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/http_retry/response.html b/docs/api-docs/slack_sdk/http_retry/response.html deleted file mode 100644 index cfd578510..000000000 --- a/docs/api-docs/slack_sdk/http_retry/response.html +++ /dev/null @@ -1,152 +0,0 @@ - - - - - - -slack_sdk.http_retry.response API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.http_retry.response

-
-
-
- -Expand source code - -
from typing import Dict, Optional, List, Union, Any
-
-
-class HttpResponse:
-    """HTTP response representation"""
-
-    status_code: int
-    headers: Dict[str, List[str]]
-    body: Optional[Dict[str, Any]]
-    data: Optional[bytes]
-
-    def __init__(
-        self,
-        *,
-        status_code: Union[int, str],
-        headers: Dict[str, Union[str, List[str]]],
-        body: Optional[Dict[str, Any]] = None,
-        data: Optional[bytes] = None,
-    ):
-        self.status_code = int(status_code)
-        self.headers = {
-            k: v if isinstance(v, list) else [v] for k, v in headers.items()
-        }
-        self.body = body
-        self.data = data
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class HttpResponse -(*, status_code:ย Union[int,ย str], headers:ย Dict[str,ย Union[str,ย List[str]]], body:ย Optional[Dict[str,ย Any]]ย =ย None, data:ย Optional[bytes]ย =ย None) -
-
-

HTTP response representation

-
- -Expand source code - -
class HttpResponse:
-    """HTTP response representation"""
-
-    status_code: int
-    headers: Dict[str, List[str]]
-    body: Optional[Dict[str, Any]]
-    data: Optional[bytes]
-
-    def __init__(
-        self,
-        *,
-        status_code: Union[int, str],
-        headers: Dict[str, Union[str, List[str]]],
-        body: Optional[Dict[str, Any]] = None,
-        data: Optional[bytes] = None,
-    ):
-        self.status_code = int(status_code)
-        self.headers = {
-            k: v if isinstance(v, list) else [v] for k, v in headers.items()
-        }
-        self.body = body
-        self.data = data
-
-

Class variables

-
-
var body :ย Optional[Dict[str,ย Any]]
-
-
-
-
var data :ย Optional[bytes]
-
-
-
-
var headers :ย Dict[str,ย List[str]]
-
-
-
-
var status_code :ย int
-
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/http_retry/state.html b/docs/api-docs/slack_sdk/http_retry/state.html deleted file mode 100644 index 998df2d67..000000000 --- a/docs/api-docs/slack_sdk/http_retry/state.html +++ /dev/null @@ -1,157 +0,0 @@ - - - - - - -slack_sdk.http_retry.state API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.http_retry.state

-
-
-
- -Expand source code - -
from typing import Optional, Any, Dict
-
-
-class RetryState:
-    next_attempt_requested: bool
-    current_attempt: int  # zero-origin
-    custom_values: Optional[Dict[str, Any]]
-
-    def __init__(
-        self,
-        *,
-        current_attempt: int = 0,
-        custom_values: Optional[Dict[str, Any]] = None,
-    ):
-        self.next_attempt_requested = False
-        self.current_attempt = current_attempt
-        self.custom_values = custom_values
-
-    def increment_current_attempt(self) -> int:
-        self.current_attempt += 1
-        return self.current_attempt
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class RetryState -(*, current_attempt:ย intย =ย 0, custom_values:ย Optional[Dict[str,ย Any]]ย =ย None) -
-
-
-
- -Expand source code - -
class RetryState:
-    next_attempt_requested: bool
-    current_attempt: int  # zero-origin
-    custom_values: Optional[Dict[str, Any]]
-
-    def __init__(
-        self,
-        *,
-        current_attempt: int = 0,
-        custom_values: Optional[Dict[str, Any]] = None,
-    ):
-        self.next_attempt_requested = False
-        self.current_attempt = current_attempt
-        self.custom_values = custom_values
-
-    def increment_current_attempt(self) -> int:
-        self.current_attempt += 1
-        return self.current_attempt
-
-

Class variables

-
-
var current_attempt :ย int
-
-
-
-
var custom_values :ย Optional[Dict[str,ย Any]]
-
-
-
-
var next_attempt_requested :ย bool
-
-
-
-
-

Methods

-
-
-def increment_current_attempt(self) โ€‘>ย int -
-
-
-
- -Expand source code - -
def increment_current_attempt(self) -> int:
-    self.current_attempt += 1
-    return self.current_attempt
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/index.html b/docs/api-docs/slack_sdk/index.html deleted file mode 100644 index 1c059bdc4..000000000 --- a/docs/api-docs/slack_sdk/index.html +++ /dev/null @@ -1,232 +0,0 @@ - - - - - - -slack_sdk API documentation - - - - - - - - - - - -
-
-
-

Package slack_sdk

-
-
- -

Here is the list of key modules in this SDK:

-

Web API Client

- -

Webhook / response_url Client

- -

Socket Mode Client

- -

OAuth

- -

Audit Logs API Client

- -

SCIM API Client

- -
- -Expand source code - -
"""
-* The SDK website: https://slack.dev/python-slack-sdk/
-* PyPI package: https://pypi.org/project/slack-sdk/
-
-Here is the list of key modules in this SDK:
-
-#### Web API Client
-
-* Web API client: `slack_sdk.web.client`
-* asyncio-based Web API client: `slack_sdk.web.async_client`
-
-#### Webhook / response_url Client
-
-* Webhook client: `slack_sdk.webhook.client`
-* asyncio-based Webhook client: `slack_sdk.webhook.async_client`
-
-#### Socket Mode Client
-
-* The built-in Socket Mode client: `slack_sdk.socket_mode.builtin.client`
-* [aiohttp](https://pypi.org/project/aiohttp/) based client: `slack_sdk.socket_mode.aiohttp`
-* [websocket_client](https://pypi.org/project/websocket-client/) based client: `slack_sdk.socket_mode.websocket_client`
-* [websockets](https://pypi.org/project/websockets/) based client: `slack_sdk.socket_mode.websockets`
-
-#### OAuth
-
-* `slack_sdk.oauth.installation_store.installation_store`
-* `slack_sdk.oauth.state_store`
-
-#### Audit Logs API Client
-
-* `slack_sdk.audit_logs.v1.client`
-* `slack_sdk.audit_logs.v1.async_client`
-
-#### SCIM API Client
-
-* `slack_sdk.scim.v1.client`
-* `slack_sdk.scim.v1.async_client`
-
-"""
-import logging
-from logging import NullHandler
-
-# from .rtm import RTMClient  # noqa
-from .web import WebClient  # noqa
-from .webhook import WebhookClient  # noqa
-
-# Set default logging handler to avoid "No handler found" warnings.
-logging.getLogger(__name__).addHandler(NullHandler())
-
-
-
-

Sub-modules

-
-
slack_sdk.aiohttp_version_checker
-
-

Internal module for checking aiohttp compatibility of async modules

-
-
slack_sdk.audit_logs
-
-

Audit Logs API is a set of APIs for monitoring whatโ€™s happening in your Enterprise Grid organization โ€ฆ

-
-
slack_sdk.errors
-
-

Errors that can be raised by this SDK

-
-
slack_sdk.http_retry
-
-
-
-
slack_sdk.models
-
-

Classes for constructing Slack-specific data structure

-
-
slack_sdk.oauth
-
-

Modules for implementing the Slack OAuth flow โ€ฆ

-
-
slack_sdk.proxy_env_variable_loader
-
-

Internal module for loading proxy-related env variables

-
-
slack_sdk.rtm
-
-

A Python module for interacting with Slack's RTM API.

-
-
slack_sdk.rtm_v2
-
-

A Python module for interacting with Slack's RTM API.

-
-
slack_sdk.scim
-
-

SCIM API is a set of APIs for provisioning and managing user accounts and groups. -SCIM is used by Single Sign-On (SSO) services and identity providers โ€ฆ

-
-
slack_sdk.signature
-
-

Slack request signature verifier

-
-
slack_sdk.socket_mode
-
-

Socket Mode is a method of connecting your app to Slackโ€™s APIs using WebSockets instead of HTTP. -You can use slack_sdk.socket_mode.SocketModeClient โ€ฆ

-
-
slack_sdk.version
-
-

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

-
-
slack_sdk.web
-
-

The Slack Web API allows you to build applications that interact with Slack -in more complex ways than the integrations we provide out of the box.

-
-
slack_sdk.webhook
-
-

You can use slack_sdk.webhook.WebhookClient for Incoming Webhooks -and message responses using response_url in payloads.

-
-
-
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/models/blocks/block_elements.html b/docs/api-docs/slack_sdk/models/blocks/block_elements.html deleted file mode 100644 index 67104ab5f..000000000 --- a/docs/api-docs/slack_sdk/models/blocks/block_elements.html +++ /dev/null @@ -1,3689 +0,0 @@ - - - - - - -slack_sdk.models.blocks.block_elements API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.models.blocks.block_elements

-
-
-
- -Expand source code - -
import copy
-import logging
-import re
-import warnings
-from abc import ABCMeta
-from typing import List, Optional, Set, Union, Sequence
-
-from slack_sdk.models import show_unknown_key_warning
-from slack_sdk.models.basic_objects import (
-    JsonObject,
-    JsonValidator,
-    EnumValidator,
-)
-from .basic_components import ButtonStyles
-from .basic_components import ConfirmObject
-from .basic_components import DispatchActionConfig
-from .basic_components import MarkdownTextObject
-from .basic_components import Option
-from .basic_components import OptionGroup
-from .basic_components import PlainTextObject
-from .basic_components import TextObject
-
-
-# -------------------------------------------------
-# Block Elements
-# -------------------------------------------------
-
-
-class BlockElement(JsonObject, metaclass=ABCMeta):
-    """Block Elements are things that exists inside of your Blocks.
-    https://api.slack.com/reference/block-kit/block-elements
-    """
-
-    attributes = {"type"}
-    logger = logging.getLogger(__name__)
-
-    def _subtype_warning(self):  # skipcq: PYL-R0201
-        warnings.warn(
-            "subtype is deprecated since slackclient 2.6.0, use type instead",
-            DeprecationWarning,
-        )
-
-    @property
-    def subtype(self) -> Optional[str]:
-        return self.type
-
-    def __init__(
-        self,
-        *,
-        type: Optional[str] = None,  # skipcq: PYL-W0622
-        subtype: Optional[str] = None,
-        **others: dict,
-    ):
-        if subtype:
-            self._subtype_warning()
-        self.type = type if type else subtype
-        show_unknown_key_warning(self, others)
-
-    @classmethod
-    def parse(
-        cls, block_element: Union[dict, "BlockElement"]
-    ) -> Optional[Union["BlockElement", TextObject]]:
-        if block_element is None:  # skipcq: PYL-R1705
-            return None
-        elif isinstance(block_element, dict):
-            if "type" in block_element:
-                d = copy.copy(block_element)
-                t = d.pop("type")
-                if t == PlainTextObject.type:  # skipcq: PYL-R1705
-                    return PlainTextObject(**d)
-                elif t == MarkdownTextObject.type:
-                    return MarkdownTextObject(**d)
-                elif t == ImageElement.type:
-                    return ImageElement(**d)
-                elif t == ButtonElement.type:
-                    return ButtonElement(**d)
-                elif t == StaticSelectElement.type:
-                    return StaticSelectElement(**d)
-                elif t == StaticMultiSelectElement.type:
-                    return StaticMultiSelectElement(**d)
-                elif t == ExternalDataSelectElement.type:
-                    return ExternalDataSelectElement(**d)
-                elif t == ExternalDataMultiSelectElement.type:
-                    return ExternalDataMultiSelectElement(**d)
-                elif t == UserSelectElement.type:
-                    return UserSelectElement(**d)
-                elif t == UserMultiSelectElement.type:
-                    return UserMultiSelectElement(**d)
-                elif t == ConversationSelectElement.type:
-                    return ConversationSelectElement(**d)
-                elif t == ConversationMultiSelectElement.type:
-                    return ConversationMultiSelectElement(**d)
-                elif t == ChannelSelectElement.type:
-                    return ChannelSelectElement(**d)
-                elif t == ChannelMultiSelectElement.type:
-                    return ChannelMultiSelectElement(**d)
-                elif t == PlainTextInputElement.type:
-                    return PlainTextInputElement(**d)
-                elif t == RadioButtonsElement.type:
-                    return RadioButtonsElement(**d)
-                elif t == CheckboxesElement.type:
-                    return CheckboxesElement(**d)
-                elif t == OverflowMenuElement.type:
-                    return OverflowMenuElement(**d)
-                elif t == DatePickerElement.type:
-                    return DatePickerElement(**d)
-                else:
-                    cls.logger.warning(
-                        f"Unknown element detected and skipped ({block_element})"
-                    )
-                    return None
-            else:
-                cls.logger.warning(
-                    f"Unknown element detected and skipped ({block_element})"
-                )
-                return None
-        elif isinstance(block_element, (TextObject, BlockElement)):
-            return block_element
-        else:
-            cls.logger.warning(
-                f"Unknown element detected and skipped ({block_element})"
-            )
-            return None
-
-    @classmethod
-    def parse_all(
-        cls, block_elements: Sequence[Union[dict, "BlockElement"]]
-    ) -> List["BlockElement"]:
-        return [cls.parse(e) for e in block_elements or []]
-
-
-# -------------------------------------------------
-# Interactive Block Elements
-# -------------------------------------------------
-
-
-class InteractiveElement(BlockElement):
-    action_id_max_length = 255
-
-    @property
-    def attributes(self) -> Set[str]:
-        return super().attributes.union({"alt_text", "action_id"})
-
-    def __init__(
-        self,
-        *,
-        action_id: Optional[str] = None,
-        type: Optional[str] = None,  # skipcq: PYL-W0622
-        subtype: Optional[str] = None,
-        **others: dict,
-    ):
-        """An interactive block element.
-
-        We generally recommend using the concrete subclasses for better supports of available properties.
-        """
-        if subtype:
-            self._subtype_warning()
-        super().__init__(type=type or subtype)
-
-        # Note that we don't intentionally have show_unknown_key_warning for the unknown key warnings here.
-        # It's fine to pass any kwargs to the held dict here although the class does not do any validation.
-        # show_unknown_key_warning(self, others)
-
-        self.action_id = action_id
-
-    @JsonValidator(
-        f"action_id attribute cannot exceed {action_id_max_length} characters"
-    )
-    def _validate_action_id_length(self):
-        return (
-            self.action_id is None or len(self.action_id) <= self.action_id_max_length
-        )
-
-
-class InputInteractiveElement(InteractiveElement, metaclass=ABCMeta):
-    placeholder_max_length = 150
-
-    attributes = {"type", "action_id", "placeholder", "confirm"}
-
-    def _subtype_warning(self):
-        warnings.warn(
-            "subtype is deprecated since slackclient 2.6.0, use type instead",
-            DeprecationWarning,
-        )
-
-    @property
-    def subtype(self) -> Optional[str]:
-        return self.type
-
-    def __init__(
-        self,
-        *,
-        action_id: Optional[str] = None,
-        placeholder: Union[str, TextObject] = None,
-        type: Optional[str] = None,  # skipcq: PYL-W0622
-        subtype: Optional[str] = None,
-        confirm: Optional[Union[dict, ConfirmObject]] = None,
-        **others: dict,
-    ):
-        """InteractiveElement that is usable in input blocks
-
-        We generally recommend using the concrete subclasses for better supports of available properties.
-        """
-        if subtype:
-            self._subtype_warning()
-        super().__init__(action_id=action_id, type=type or subtype)
-
-        # Note that we don't intentionally have show_unknown_key_warning for the unknown key warnings here.
-        # It's fine to pass any kwargs to the held dict here although the class does not do any validation.
-        # show_unknown_key_warning(self, others)
-
-        self.placeholder = TextObject.parse(placeholder)
-        self.confirm = ConfirmObject.parse(confirm)
-
-    @JsonValidator(
-        f"placeholder attribute cannot exceed {placeholder_max_length} characters"
-    )
-    def _validate_placeholder_length(self):
-        return (
-            self.placeholder is None
-            or self.placeholder.text is None
-            or len(self.placeholder.text) <= self.placeholder_max_length
-        )
-
-
-# -------------------------------------------------
-# Button
-# -------------------------------------------------
-
-
-class ButtonElement(InteractiveElement):
-    type = "button"
-    text_max_length = 75
-    url_max_length = 3000
-    value_max_length = 2000
-
-    @property
-    def attributes(self) -> Set[str]:
-        return super().attributes.union({"text", "url", "value", "style", "confirm"})
-
-    def __init__(
-        self,
-        *,
-        text: Union[str, dict, TextObject],
-        action_id: Optional[str] = None,
-        url: Optional[str] = None,
-        value: Optional[str] = None,
-        style: Optional[str] = None,  # primary, danger
-        confirm: Optional[Union[dict, ConfirmObject]] = None,
-        **others: dict,
-    ):
-        """An interactive element that inserts a button. The button can be a trigger for
-        anything from opening a simple link to starting a complex workflow.
-        https://api.slack.com/reference/block-kit/block-elements#button
-        """
-        super().__init__(action_id=action_id, type=self.type)
-        show_unknown_key_warning(self, others)
-
-        # NOTE: default_type=PlainTextObject.type here is only for backward-compatibility with version 2.5.0
-        self.text = TextObject.parse(text, default_type=PlainTextObject.type)
-        self.url = url
-        self.value = value
-        self.style = style
-        self.confirm = ConfirmObject.parse(confirm)
-
-    @JsonValidator(f"text attribute cannot exceed {text_max_length} characters")
-    def _validate_text_length(self):
-        return (
-            self.text is None
-            or self.text.text is None
-            or len(self.text.text) <= self.text_max_length
-        )
-
-    @JsonValidator(f"url attribute cannot exceed {url_max_length} characters")
-    def _validate_url_length(self):
-        return self.url is None or len(self.url) <= self.url_max_length
-
-    @JsonValidator(f"value attribute cannot exceed {value_max_length} characters")
-    def _validate_value_length(self):
-        return self.value is None or len(self.value) <= self.value_max_length
-
-    @EnumValidator("style", ButtonStyles)
-    def _validate_style_valid(self):
-        return self.style is None or self.style in ButtonStyles
-
-
-class LinkButtonElement(ButtonElement):
-    def __init__(
-        self,
-        *,
-        text: str,
-        url: str,
-        action_id: Optional[str] = None,
-        style: Optional[str] = None,
-        **others: dict,
-    ):
-        """A simple button that simply opens a given URL. You will still receive an
-        interaction payload and will need to send an acknowledgement response.
-        This is a helper class that makes creating links simpler.
-        https://api.slack.com/reference/block-kit/block-elements#button
-        """
-        super().__init__(
-            # NOTE: value must be always absent
-            text=text,
-            url=url,
-            action_id=action_id,
-            value=None,
-            style=style,
-        )
-        show_unknown_key_warning(self, others)
-
-
-# -------------------------------------------------
-# Checkboxes
-# -------------------------------------------------
-
-
-class CheckboxesElement(InputInteractiveElement):
-    type = "checkboxes"
-
-    @property
-    def attributes(self) -> Set[str]:
-        return super().attributes.union({"options", "initial_options"})
-
-    def __init__(
-        self,
-        *,
-        action_id: Optional[str] = None,
-        placeholder: Optional[str] = None,
-        options: Optional[Sequence[Union[dict, Option]]] = None,
-        initial_options: Optional[Sequence[Union[dict, Option]]] = None,
-        confirm: Optional[Union[dict, ConfirmObject]] = None,
-        **others: dict,
-    ):
-        """A checkbox group that allows a user to choose multiple items from a list of possible options.
-        https://api.slack.com/reference/block-kit/block-elements#checkboxes
-        """
-        super().__init__(
-            type=self.type,
-            action_id=action_id,
-            placeholder=TextObject.parse(placeholder, PlainTextObject.type),
-            confirm=ConfirmObject.parse(confirm),
-        )
-        show_unknown_key_warning(self, others)
-
-        self.options = Option.parse_all(options)
-        self.initial_options = Option.parse_all(initial_options)
-
-
-# -------------------------------------------------
-# DatePicker
-# -------------------------------------------------
-
-
-class DatePickerElement(InputInteractiveElement):
-    type = "datepicker"
-
-    @property
-    def attributes(self) -> Set[str]:
-        return super().attributes.union({"initial_date"})
-
-    def __init__(
-        self,
-        *,
-        action_id: Optional[str] = None,
-        placeholder: Optional[Union[str, dict, TextObject]] = None,
-        initial_date: Optional[str] = None,
-        confirm: Optional[Union[dict, ConfirmObject]] = None,
-        **others: dict,
-    ):
-        """
-        An element which lets users easily select a date from a calendar style UI.
-        Date picker elements can be used inside of SectionBlocks and ActionsBlocks.
-        https://api.slack.com/reference/block-kit/block-elements#datepicker
-        """
-        super().__init__(
-            type=self.type,
-            action_id=action_id,
-            placeholder=TextObject.parse(placeholder, PlainTextObject.type),
-            confirm=ConfirmObject.parse(confirm),
-        )
-        show_unknown_key_warning(self, others)
-
-        self.initial_date = initial_date
-
-    @JsonValidator("initial_date attribute must be in format 'YYYY-MM-DD'")
-    def _validate_initial_date_valid(self):
-        return self.initial_date is None or re.match(
-            r"\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])", self.initial_date
-        )
-
-
-# -------------------------------------------------
-# TimePicker
-# -------------------------------------------------
-
-
-class TimePickerElement(InputInteractiveElement):
-    type = "timepicker"
-
-    @property
-    def attributes(self) -> Set[str]:
-        return super().attributes.union({"initial_time"})
-
-    def __init__(
-        self,
-        *,
-        action_id: Optional[str] = None,
-        placeholder: Optional[Union[str, dict, TextObject]] = None,
-        initial_time: Optional[str] = None,
-        confirm: Optional[Union[dict, ConfirmObject]] = None,
-        **others: dict,
-    ):
-        """
-        An element which allows selection of a time of day.
-        https://api.slack.com/reference/block-kit/block-elements#timepicker
-        """
-        super().__init__(
-            type=self.type,
-            action_id=action_id,
-            placeholder=TextObject.parse(placeholder, PlainTextObject.type),
-            confirm=ConfirmObject.parse(confirm),
-        )
-        show_unknown_key_warning(self, others)
-
-        self.initial_time = initial_time
-
-    @JsonValidator("initial_time attribute must be in format 'HH:mm'")
-    def _validate_initial_time_valid(self):
-        return self.initial_time is None or re.match(
-            r"([0-1][0-9]|2[0-3]):([0-5][0-9])", self.initial_time
-        )
-
-
-# -------------------------------------------------
-# Image
-# -------------------------------------------------
-
-
-class ImageElement(BlockElement):
-    type = "image"
-    image_url_max_length = 3000
-    alt_text_max_length = 2000
-
-    @property
-    def attributes(self) -> Set[str]:
-        return super().attributes.union({"alt_text", "image_url"})
-
-    def __init__(
-        self,
-        *,
-        image_url: Optional[str] = None,
-        alt_text: Optional[str] = None,
-        **others: dict,
-    ):
-        """An element to insert an image - this element can be used in section and
-        context blocks only. If you want a block with only an image in it,
-        you're looking for the image block.
-        https://api.slack.com/reference/block-kit/block-elements#image
-        """
-        super().__init__(type=self.type)
-        show_unknown_key_warning(self, others)
-
-        self.image_url = image_url
-        self.alt_text = alt_text
-
-    @JsonValidator(
-        f"image_url attribute cannot exceed {image_url_max_length} characters"
-    )
-    def _validate_image_url_length(self):
-        return len(self.image_url) <= self.image_url_max_length
-
-    @JsonValidator(f"alt_text attribute cannot exceed {alt_text_max_length} characters")
-    def _validate_alt_text_length(self):
-        return len(self.alt_text) <= self.alt_text_max_length
-
-
-# -------------------------------------------------
-# Static Select
-# -------------------------------------------------
-
-
-class StaticSelectElement(InputInteractiveElement):
-    type = "static_select"
-    options_max_length = 100
-    option_groups_max_length = 100
-
-    @property
-    def attributes(self) -> Set[str]:
-        return super().attributes.union({"options", "option_groups", "initial_option"})
-
-    def __init__(
-        self,
-        *,
-        placeholder: Optional[Union[str, dict, TextObject]] = None,
-        action_id: Optional[str] = None,
-        options: Optional[Sequence[Union[dict, Option]]] = None,
-        option_groups: Optional[Sequence[Union[dict, OptionGroup]]] = None,
-        initial_option: Optional[Union[dict, Option]] = None,
-        confirm: Optional[Union[dict, ConfirmObject]] = None,
-        **others: dict,
-    ):
-        """This is the simplest form of select menu, with a static list of options passed in when defining the element.
-        https://api.slack.com/reference/block-kit/block-elements#static_select
-        """
-        super().__init__(
-            type=self.type,
-            action_id=action_id,
-            placeholder=TextObject.parse(placeholder, PlainTextObject.type),
-            confirm=ConfirmObject.parse(confirm),
-        )
-        show_unknown_key_warning(self, others)
-
-        self.options = options
-        self.option_groups = option_groups
-        self.initial_option = initial_option
-
-    @JsonValidator(f"options attribute cannot exceed {options_max_length} elements")
-    def _validate_options_length(self):
-        return self.options is None or len(self.options) <= self.options_max_length
-
-    @JsonValidator(
-        f"option_groups attribute cannot exceed {option_groups_max_length} elements"
-    )
-    def _validate_option_groups_length(self):
-        return (
-            self.option_groups is None
-            or len(self.option_groups) <= self.option_groups_max_length
-        )
-
-    @JsonValidator("options and option_groups cannot both be specified")
-    def _validate_options_and_option_groups_both_specified(self):
-        return not (self.options is not None and self.option_groups is not None)
-
-    @JsonValidator("options or option_groups must be specified")
-    def _validate_neither_options_or_option_groups_is_specified(self):
-        return self.options is not None or self.option_groups is not None
-
-
-class StaticMultiSelectElement(InputInteractiveElement):
-    type = "multi_static_select"
-    options_max_length = 100
-    option_groups_max_length = 100
-
-    @property
-    def attributes(self) -> Set[str]:
-        return super().attributes.union(
-            {"options", "option_groups", "initial_options", "max_selected_items"}
-        )
-
-    def __init__(
-        self,
-        *,
-        placeholder: Optional[Union[str, dict, TextObject]] = None,
-        action_id: Optional[str] = None,
-        options: Optional[Sequence[Option]] = None,
-        option_groups: Optional[Sequence[OptionGroup]] = None,
-        initial_options: Optional[Sequence[Option]] = None,
-        confirm: Optional[Union[dict, ConfirmObject]] = None,
-        max_selected_items: Optional[int] = None,
-        **others: dict,
-    ):
-        """
-        This is the simplest form of select menu, with a static list of options passed in when defining the element.
-        https://api.slack.com/reference/block-kit/block-elements#static_multi_select
-        """
-        super().__init__(
-            type=self.type,
-            action_id=action_id,
-            placeholder=TextObject.parse(placeholder, PlainTextObject.type),
-            confirm=ConfirmObject.parse(confirm),
-        )
-        show_unknown_key_warning(self, others)
-
-        self.options = Option.parse_all(options)
-        self.option_groups = OptionGroup.parse_all(option_groups)
-        self.initial_options = Option.parse_all(initial_options)
-        self.max_selected_items = max_selected_items
-
-    @JsonValidator(f"options attribute cannot exceed {options_max_length} elements")
-    def _validate_options_length(self):
-        return self.options is None or len(self.options) <= self.options_max_length
-
-    @JsonValidator(
-        f"option_groups attribute cannot exceed {option_groups_max_length} elements"
-    )
-    def _validate_option_groups_length(self):
-        return (
-            self.option_groups is None
-            or len(self.option_groups) <= self.option_groups_max_length
-        )
-
-    @JsonValidator("options and option_groups cannot both be specified")
-    def _validate_options_and_option_groups_both_specified(self):
-        return self.options is None or self.option_groups is None
-
-    @JsonValidator("options or option_groups must be specified")
-    def _validate_neither_options_or_option_groups_is_specified(self):
-        return self.options is not None or self.option_groups is not None
-
-
-# SelectElement will be deprecated in version 3, use StaticSelectElement instead
-class SelectElement(InputInteractiveElement):
-    type = "static_select"
-    options_max_length = 100
-    option_groups_max_length = 100
-
-    @property
-    def attributes(self) -> Set[str]:
-        return super().attributes.union({"options", "option_groups", "initial_option"})
-
-    def __init__(
-        self,
-        *,
-        action_id: Optional[str] = None,
-        placeholder: Optional[str] = None,
-        options: Optional[Sequence[Option]] = None,
-        option_groups: Optional[Sequence[OptionGroup]] = None,
-        initial_option: Optional[Option] = None,
-        confirm: Optional[Union[dict, ConfirmObject]] = None,
-        **others: dict,
-    ):
-        """This is the simplest form of select menu, with a static list of options passed in when defining the element.
-        https://api.slack.com/reference/block-kit/block-elements#static_select
-        """
-        super().__init__(
-            type=self.type,
-            action_id=action_id,
-            placeholder=TextObject.parse(placeholder, PlainTextObject.type),
-            confirm=ConfirmObject.parse(confirm),
-        )
-        show_unknown_key_warning(self, others)
-
-        self.options = options
-        self.option_groups = option_groups
-        self.initial_option = initial_option
-
-    @JsonValidator(f"options attribute cannot exceed {options_max_length} elements")
-    def _validate_options_length(self):
-        return self.options is None or len(self.options) <= self.options_max_length
-
-    @JsonValidator(
-        f"option_groups attribute cannot exceed {option_groups_max_length} elements"
-    )
-    def _validate_option_groups_length(self):
-        return (
-            self.option_groups is None
-            or len(self.option_groups) <= self.option_groups_max_length
-        )
-
-    @JsonValidator("options and option_groups cannot both be specified")
-    def _validate_options_and_option_groups_both_specified(self):
-        return not (self.options is not None and self.option_groups is not None)
-
-    @JsonValidator("options or option_groups must be specified")
-    def _validate_neither_options_or_option_groups_is_specified(self):
-        return self.options is not None or self.option_groups is not None
-
-
-# -------------------------------------------------
-# External Data Source Select
-# -------------------------------------------------
-
-
-class ExternalDataSelectElement(InputInteractiveElement):
-    type = "external_select"
-
-    @property
-    def attributes(self) -> Set[str]:
-        return super().attributes.union({"min_query_length", "initial_option"})
-
-    def __init__(
-        self,
-        *,
-        action_id: Optional[str] = None,
-        placeholder: Union[str, TextObject] = None,
-        initial_option: Union[Optional[Option], Optional[OptionGroup]] = None,
-        min_query_length: Optional[int] = None,
-        confirm: Optional[Union[dict, ConfirmObject]] = None,
-        **others: dict,
-    ):
-        """
-        This select menu will load its options from an external data source, allowing
-        for a dynamic list of options.
-        https://api.slack.com/reference/block-kit/block-elements#external_select
-        """
-        super().__init__(
-            type=self.type,
-            action_id=action_id,
-            placeholder=TextObject.parse(placeholder, PlainTextObject.type),
-            confirm=ConfirmObject.parse(confirm),
-        )
-        show_unknown_key_warning(self, others)
-
-        self.min_query_length = min_query_length
-        self.initial_option = initial_option
-
-
-class ExternalDataMultiSelectElement(InputInteractiveElement):
-    type = "multi_external_select"
-
-    @property
-    def attributes(self) -> Set[str]:
-        return super().attributes.union(
-            {"min_query_length", "initial_options", "max_selected_items"}
-        )
-
-    def __init__(
-        self,
-        *,
-        placeholder: Optional[Union[str, dict, TextObject]] = None,
-        action_id: Optional[str] = None,
-        min_query_length: Optional[int] = None,
-        initial_options: Optional[Sequence[Union[dict, Option]]] = None,
-        confirm: Optional[Union[dict, ConfirmObject]] = None,
-        max_selected_items: Optional[int] = None,
-        **others: dict,
-    ):
-        """
-        This select menu will load its options from an external data source, allowing
-        for a dynamic list of options.
-        https://api.slack.com/reference/block-kit/block-elements#external-select
-        """
-        super().__init__(
-            type=self.type,
-            action_id=action_id,
-            placeholder=TextObject.parse(placeholder, PlainTextObject.type),
-            confirm=ConfirmObject.parse(confirm),
-        )
-        show_unknown_key_warning(self, others)
-
-        self.min_query_length = min_query_length
-        self.initial_options = Option.parse_all(initial_options)
-        self.max_selected_items = max_selected_items
-
-
-# -------------------------------------------------
-# Users Select
-# -------------------------------------------------
-
-
-class UserSelectElement(InputInteractiveElement):
-    type = "users_select"
-
-    @property
-    def attributes(self) -> Set[str]:
-        return super().attributes.union({"initial_user"})
-
-    def __init__(
-        self,
-        *,
-        placeholder: Optional[Union[str, dict, TextObject]] = None,
-        action_id: Optional[str] = None,
-        initial_user: Optional[str] = None,
-        confirm: Optional[Union[dict, ConfirmObject]] = None,
-        **others: dict,
-    ):
-        """
-        This select menu will populate its options with a list of Slack users visible to
-        the current user in the active workspace.
-        https://api.slack.com/reference/block-kit/block-elements#users_select
-        """
-        super().__init__(
-            type=self.type,
-            action_id=action_id,
-            placeholder=TextObject.parse(placeholder, PlainTextObject.type),
-            confirm=ConfirmObject.parse(confirm),
-        )
-        show_unknown_key_warning(self, others)
-
-        self.initial_user = initial_user
-
-
-class UserMultiSelectElement(InputInteractiveElement):
-    type = "multi_users_select"
-
-    @property
-    def attributes(self) -> Set[str]:
-        return super().attributes.union({"initial_users", "max_selected_items"})
-
-    def __init__(
-        self,
-        *,
-        action_id: Optional[str] = None,
-        placeholder: Optional[Union[str, dict, TextObject]] = None,
-        initial_users: Optional[Sequence[str]] = None,
-        confirm: Optional[Union[dict, ConfirmObject]] = None,
-        max_selected_items: Optional[int] = None,
-        **others: dict,
-    ):
-        """
-        This select menu will populate its options with a list of Slack users visible to
-        the current user in the active workspace.
-        https://api.slack.com/reference/block-kit/block-elements#users_multi_select
-        """
-        super().__init__(
-            type=self.type,
-            action_id=action_id,
-            placeholder=TextObject.parse(placeholder, PlainTextObject.type),
-            confirm=ConfirmObject.parse(confirm),
-        )
-        show_unknown_key_warning(self, others)
-
-        self.initial_users = initial_users
-        self.max_selected_items = max_selected_items
-
-
-# -------------------------------------------------
-# Conversations Select
-# -------------------------------------------------
-
-
-class ConversationFilter(JsonObject):
-    attributes = {"include", "exclude_bot_users", "exclude_external_shared_channels"}
-    logger = logging.getLogger(__name__)
-
-    def __init__(
-        self,
-        *,
-        include: Optional[Sequence[str]] = None,
-        exclude_bot_users: Optional[bool] = None,
-        exclude_external_shared_channels: Optional[bool] = None,
-    ):
-        self.include = include
-        self.exclude_bot_users = exclude_bot_users
-        self.exclude_external_shared_channels = exclude_external_shared_channels
-
-    @classmethod
-    def parse(cls, filter: Union[dict, "ConversationFilter"]):  # skipcq: PYL-W0622
-        if filter is None:  # skipcq: PYL-R1705
-            return None
-        elif isinstance(filter, ConversationFilter):
-            return filter
-        elif isinstance(filter, dict):
-            d = copy.copy(filter)
-            return ConversationFilter(**d)
-        else:
-            cls.logger.warning(
-                f"Unknown conversation filter object detected and skipped ({filter})"
-            )
-            return None
-
-
-class ConversationSelectElement(InputInteractiveElement):
-    type = "conversations_select"
-
-    @property
-    def attributes(self) -> Set[str]:
-        return super().attributes.union(
-            {
-                "initial_conversation",
-                "response_url_enabled",
-                "filter",
-                "default_to_current_conversation",
-            }
-        )
-
-    def __init__(
-        self,
-        *,
-        placeholder: Optional[Union[str, dict, TextObject]] = None,
-        action_id: Optional[str] = None,
-        initial_conversation: Optional[str] = None,
-        confirm: Optional[Union[dict, ConfirmObject]] = None,
-        response_url_enabled: Optional[bool] = None,
-        default_to_current_conversation: Optional[bool] = None,
-        filter: Optional[ConversationFilter] = None,  # skipcq: PYL-W0622
-        **others: dict,
-    ):
-        """
-        This select menu will populate its options with a list of public and private
-        channels, DMs, and MPIMs visible to the current user in the active workspace.
-        https://api.slack.com/reference/block-kit/block-elements#conversation_select
-        """
-        super().__init__(
-            type=self.type,
-            action_id=action_id,
-            placeholder=TextObject.parse(placeholder, PlainTextObject.type),
-            confirm=ConfirmObject.parse(confirm),
-        )
-        show_unknown_key_warning(self, others)
-
-        self.initial_conversation = initial_conversation
-        self.response_url_enabled = response_url_enabled
-        self.default_to_current_conversation = default_to_current_conversation
-        self.filter = filter
-
-
-class ConversationMultiSelectElement(InputInteractiveElement):
-    type = "multi_conversations_select"
-
-    @property
-    def attributes(self) -> Set[str]:
-        return super().attributes.union(
-            {
-                "initial_conversations",
-                "max_selected_items",
-                "default_to_current_conversation",
-                "filter",
-            }
-        )
-
-    def __init__(
-        self,
-        *,
-        placeholder: Optional[Union[str, dict, TextObject]] = None,
-        action_id: Optional[str] = None,
-        initial_conversations: Optional[Sequence[str]] = None,
-        confirm: Optional[Union[dict, ConfirmObject]] = None,
-        max_selected_items: Optional[int] = None,
-        default_to_current_conversation: Optional[bool] = None,
-        filter: Optional[Union[dict, ConversationFilter]] = None,  # skipcq: PYL-W0622
-        **others: dict,
-    ):
-        """
-        This multi-select menu will populate its options with a list of public and private channels,
-        DMs, and MPIMs visible to the current user in the active workspace.
-        https://api.slack.com/reference/block-kit/block-elements#conversation_multi_select
-        """
-        super().__init__(
-            type=self.type,
-            action_id=action_id,
-            placeholder=TextObject.parse(placeholder, PlainTextObject.type),
-            confirm=ConfirmObject.parse(confirm),
-        )
-        show_unknown_key_warning(self, others)
-
-        self.initial_conversations = initial_conversations
-        self.max_selected_items = max_selected_items
-        self.default_to_current_conversation = default_to_current_conversation
-        self.filter = ConversationFilter.parse(filter)
-
-
-# -------------------------------------------------
-# Channels Select
-# -------------------------------------------------
-
-
-class ChannelSelectElement(InputInteractiveElement):
-    type = "channels_select"
-
-    @property
-    def attributes(self) -> Set[str]:
-        return super().attributes.union({"initial_channel", "response_url_enabled"})
-
-    def __init__(
-        self,
-        *,
-        placeholder: Optional[Union[str, dict, TextObject]] = None,
-        action_id: Optional[str] = None,
-        initial_channel: Optional[str] = None,
-        confirm: Optional[Union[dict, ConfirmObject]] = None,
-        response_url_enabled: Optional[bool] = None,
-        **others: dict,
-    ):
-        """
-        This select menu will populate its options with a list of public channels
-        visible to the current user in the active workspace.
-        https://api.slack.com/reference/block-kit/block-elements#channel_select
-        """
-        super().__init__(
-            type=self.type,
-            action_id=action_id,
-            placeholder=TextObject.parse(placeholder, PlainTextObject.type),
-            confirm=ConfirmObject.parse(confirm),
-        )
-        show_unknown_key_warning(self, others)
-
-        self.initial_channel = initial_channel
-        self.response_url_enabled = response_url_enabled
-
-
-class ChannelMultiSelectElement(InputInteractiveElement):
-    type = "multi_channels_select"
-
-    @property
-    def attributes(self) -> Set[str]:
-        return super().attributes.union({"initial_channels", "max_selected_items"})
-
-    def __init__(
-        self,
-        *,
-        placeholder: Optional[Union[str, dict, TextObject]] = None,
-        action_id: Optional[str] = None,
-        initial_channels: Optional[Sequence[str]] = None,
-        confirm: Optional[Union[dict, ConfirmObject]] = None,
-        max_selected_items: Optional[int] = None,
-        **others: dict,
-    ):
-        """
-        This multi-select menu will populate its options with a list of public channels visible
-        to the current user in the active workspace.
-        https://api.slack.com/reference/block-kit/block-elements#channel_multi_select
-        """
-        super().__init__(
-            type=self.type,
-            action_id=action_id,
-            placeholder=TextObject.parse(placeholder, PlainTextObject.type),
-            confirm=ConfirmObject.parse(confirm),
-        )
-        show_unknown_key_warning(self, others)
-
-        self.initial_channels = initial_channels
-        self.max_selected_items = max_selected_items
-
-
-# -------------------------------------------------
-# Input Elements
-# -------------------------------------------------
-
-
-class PlainTextInputElement(InputInteractiveElement):
-    type = "plain_text_input"
-
-    @property
-    def attributes(self) -> Set[str]:
-        return super().attributes.union(
-            {
-                "initial_value",
-                "multiline",
-                "min_length",
-                "max_length",
-                "dispatch_action_config",
-            }
-        )
-
-    def __init__(
-        self,
-        *,
-        action_id: Optional[str] = None,
-        placeholder: Optional[Union[str, dict, TextObject]] = None,
-        confirm: Optional[Union[dict, ConfirmObject]] = None,
-        initial_value: Optional[str] = None,
-        multiline: Optional[bool] = None,
-        min_length: Optional[int] = None,
-        max_length: Optional[int] = None,
-        dispatch_action_config: Optional[Union[dict, DispatchActionConfig]] = None,
-        **others: dict,
-    ):
-        """
-        A plain-text input, similar to the HTML <input> tag, creates a field
-        where a user can enter freeform data. It can appear as a single-line
-        field or a larger textarea using the multiline flag. Plain-text input
-        elements can be used inside of SectionBlocks and ActionsBlocks.
-        https://api.slack.com/reference/block-kit/block-elements#input
-        """
-        super().__init__(
-            type=self.type,
-            action_id=action_id,
-            placeholder=TextObject.parse(placeholder, PlainTextObject.type),
-            confirm=ConfirmObject.parse(confirm),
-        )
-        show_unknown_key_warning(self, others)
-
-        self.initial_value = initial_value
-        self.multiline = multiline
-        self.min_length = min_length
-        self.max_length = max_length
-        self.dispatch_action_config = dispatch_action_config
-
-
-# -------------------------------------------------
-# Radio Buttons Select
-# -------------------------------------------------
-
-
-class RadioButtonsElement(InputInteractiveElement):
-    type = "radio_buttons"
-
-    @property
-    def attributes(self) -> Set[str]:
-        return super().attributes.union({"options", "initial_option"})
-
-    def __init__(
-        self,
-        *,
-        action_id: Optional[str] = None,
-        placeholder: Optional[Union[str, dict, TextObject]] = None,
-        options: Optional[Sequence[Union[dict, Option]]] = None,
-        initial_option: Optional[Union[dict, Option]] = None,
-        confirm: Optional[Union[dict, ConfirmObject]] = None,
-        **others: dict,
-    ):
-        """A radio button group that allows a user to choose one item from a list of possible options.
-        https://api.slack.com/reference/block-kit/block-elements#radio
-        """
-        super().__init__(
-            type=self.type,
-            action_id=action_id,
-            placeholder=TextObject.parse(placeholder, PlainTextObject.type),
-            confirm=ConfirmObject.parse(confirm),
-        )
-        show_unknown_key_warning(self, others)
-
-        self.options = options
-        self.initial_option = initial_option
-
-
-# -------------------------------------------------
-# Overflow Menu Select
-# -------------------------------------------------
-
-
-class OverflowMenuElement(InteractiveElement):
-    type = "overflow"
-    options_min_length = 2
-    options_max_length = 5
-
-    @property
-    def attributes(self) -> Set[str]:
-        return super().attributes.union({"confirm", "options"})
-
-    def __init__(
-        self,
-        *,
-        action_id: Optional[str] = None,
-        options: Sequence[Union[Option]],
-        confirm: Optional[Union[dict, ConfirmObject]] = None,
-        **others: dict,
-    ):
-        """
-        This is like a cross between a button and a select menu - when a user clicks
-        on this overflow button, they will be presented with a list of options to
-        choose from. Unlike the select menu, there is no typeahead field, and the
-        button always appears with an ellipsis ("โ€ฆ") rather than customisable text.
-
-        As such, it is usually used if you want a more compact layout than a select
-        menu, or to supply a list of less visually important actions after a row of
-        buttons. You can also specify simple URL links as overflow menu options,
-        instead of actions.
-
-        https://api.slack.com/reference/block-kit/block-elements#overflow
-        """
-        super().__init__(action_id=action_id, type=self.type)
-        show_unknown_key_warning(self, others)
-
-        self.options = options
-        self.confirm = ConfirmObject.parse(confirm)
-
-    @JsonValidator(
-        f"options attribute must have between {options_min_length} "
-        f"and {options_max_length} items"
-    )
-    def _validate_options_length(self):
-        return self.options_min_length <= len(self.options) <= self.options_max_length
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class BlockElement -(*, type:ย Optional[str]ย =ย None, subtype:ย Optional[str]ย =ย None, **others:ย dict) -
-
-

Block Elements are things that exists inside of your Blocks. -https://api.slack.com/reference/block-kit/block-elements

-
- -Expand source code - -
class BlockElement(JsonObject, metaclass=ABCMeta):
-    """Block Elements are things that exists inside of your Blocks.
-    https://api.slack.com/reference/block-kit/block-elements
-    """
-
-    attributes = {"type"}
-    logger = logging.getLogger(__name__)
-
-    def _subtype_warning(self):  # skipcq: PYL-R0201
-        warnings.warn(
-            "subtype is deprecated since slackclient 2.6.0, use type instead",
-            DeprecationWarning,
-        )
-
-    @property
-    def subtype(self) -> Optional[str]:
-        return self.type
-
-    def __init__(
-        self,
-        *,
-        type: Optional[str] = None,  # skipcq: PYL-W0622
-        subtype: Optional[str] = None,
-        **others: dict,
-    ):
-        if subtype:
-            self._subtype_warning()
-        self.type = type if type else subtype
-        show_unknown_key_warning(self, others)
-
-    @classmethod
-    def parse(
-        cls, block_element: Union[dict, "BlockElement"]
-    ) -> Optional[Union["BlockElement", TextObject]]:
-        if block_element is None:  # skipcq: PYL-R1705
-            return None
-        elif isinstance(block_element, dict):
-            if "type" in block_element:
-                d = copy.copy(block_element)
-                t = d.pop("type")
-                if t == PlainTextObject.type:  # skipcq: PYL-R1705
-                    return PlainTextObject(**d)
-                elif t == MarkdownTextObject.type:
-                    return MarkdownTextObject(**d)
-                elif t == ImageElement.type:
-                    return ImageElement(**d)
-                elif t == ButtonElement.type:
-                    return ButtonElement(**d)
-                elif t == StaticSelectElement.type:
-                    return StaticSelectElement(**d)
-                elif t == StaticMultiSelectElement.type:
-                    return StaticMultiSelectElement(**d)
-                elif t == ExternalDataSelectElement.type:
-                    return ExternalDataSelectElement(**d)
-                elif t == ExternalDataMultiSelectElement.type:
-                    return ExternalDataMultiSelectElement(**d)
-                elif t == UserSelectElement.type:
-                    return UserSelectElement(**d)
-                elif t == UserMultiSelectElement.type:
-                    return UserMultiSelectElement(**d)
-                elif t == ConversationSelectElement.type:
-                    return ConversationSelectElement(**d)
-                elif t == ConversationMultiSelectElement.type:
-                    return ConversationMultiSelectElement(**d)
-                elif t == ChannelSelectElement.type:
-                    return ChannelSelectElement(**d)
-                elif t == ChannelMultiSelectElement.type:
-                    return ChannelMultiSelectElement(**d)
-                elif t == PlainTextInputElement.type:
-                    return PlainTextInputElement(**d)
-                elif t == RadioButtonsElement.type:
-                    return RadioButtonsElement(**d)
-                elif t == CheckboxesElement.type:
-                    return CheckboxesElement(**d)
-                elif t == OverflowMenuElement.type:
-                    return OverflowMenuElement(**d)
-                elif t == DatePickerElement.type:
-                    return DatePickerElement(**d)
-                else:
-                    cls.logger.warning(
-                        f"Unknown element detected and skipped ({block_element})"
-                    )
-                    return None
-            else:
-                cls.logger.warning(
-                    f"Unknown element detected and skipped ({block_element})"
-                )
-                return None
-        elif isinstance(block_element, (TextObject, BlockElement)):
-            return block_element
-        else:
-            cls.logger.warning(
-                f"Unknown element detected and skipped ({block_element})"
-            )
-            return None
-
-    @classmethod
-    def parse_all(
-        cls, block_elements: Sequence[Union[dict, "BlockElement"]]
-    ) -> List["BlockElement"]:
-        return [cls.parse(e) for e in block_elements or []]
-
-

Ancestors

- -

Subclasses

- -

Class variables

-
-
var logger
-
-
-
-
-

Static methods

-
-
-def parse(block_element:ย Union[dict,ย ForwardRef('BlockElement')]) โ€‘>ย Union[BlockElement,ย TextObject,ย ForwardRef(None)] -
-
-
-
- -Expand source code - -
@classmethod
-def parse(
-    cls, block_element: Union[dict, "BlockElement"]
-) -> Optional[Union["BlockElement", TextObject]]:
-    if block_element is None:  # skipcq: PYL-R1705
-        return None
-    elif isinstance(block_element, dict):
-        if "type" in block_element:
-            d = copy.copy(block_element)
-            t = d.pop("type")
-            if t == PlainTextObject.type:  # skipcq: PYL-R1705
-                return PlainTextObject(**d)
-            elif t == MarkdownTextObject.type:
-                return MarkdownTextObject(**d)
-            elif t == ImageElement.type:
-                return ImageElement(**d)
-            elif t == ButtonElement.type:
-                return ButtonElement(**d)
-            elif t == StaticSelectElement.type:
-                return StaticSelectElement(**d)
-            elif t == StaticMultiSelectElement.type:
-                return StaticMultiSelectElement(**d)
-            elif t == ExternalDataSelectElement.type:
-                return ExternalDataSelectElement(**d)
-            elif t == ExternalDataMultiSelectElement.type:
-                return ExternalDataMultiSelectElement(**d)
-            elif t == UserSelectElement.type:
-                return UserSelectElement(**d)
-            elif t == UserMultiSelectElement.type:
-                return UserMultiSelectElement(**d)
-            elif t == ConversationSelectElement.type:
-                return ConversationSelectElement(**d)
-            elif t == ConversationMultiSelectElement.type:
-                return ConversationMultiSelectElement(**d)
-            elif t == ChannelSelectElement.type:
-                return ChannelSelectElement(**d)
-            elif t == ChannelMultiSelectElement.type:
-                return ChannelMultiSelectElement(**d)
-            elif t == PlainTextInputElement.type:
-                return PlainTextInputElement(**d)
-            elif t == RadioButtonsElement.type:
-                return RadioButtonsElement(**d)
-            elif t == CheckboxesElement.type:
-                return CheckboxesElement(**d)
-            elif t == OverflowMenuElement.type:
-                return OverflowMenuElement(**d)
-            elif t == DatePickerElement.type:
-                return DatePickerElement(**d)
-            else:
-                cls.logger.warning(
-                    f"Unknown element detected and skipped ({block_element})"
-                )
-                return None
-        else:
-            cls.logger.warning(
-                f"Unknown element detected and skipped ({block_element})"
-            )
-            return None
-    elif isinstance(block_element, (TextObject, BlockElement)):
-        return block_element
-    else:
-        cls.logger.warning(
-            f"Unknown element detected and skipped ({block_element})"
-        )
-        return None
-
-
-
-def parse_all(block_elements:ย Sequence[Union[dict,ย ForwardRef('BlockElement')]]) โ€‘>ย List[BlockElement] -
-
-
-
- -Expand source code - -
@classmethod
-def parse_all(
-    cls, block_elements: Sequence[Union[dict, "BlockElement"]]
-) -> List["BlockElement"]:
-    return [cls.parse(e) for e in block_elements or []]
-
-
-
-

Instance variables

-
-
var subtype :ย Optional[str]
-
-
-
- -Expand source code - -
@property
-def subtype(self) -> Optional[str]:
-    return self.type
-
-
-
-

Inherited members

- -
-
-class ButtonElement -(*, text:ย Union[str,ย dict,ย TextObject], action_id:ย Optional[str]ย =ย None, url:ย Optional[str]ย =ย None, value:ย Optional[str]ย =ย None, style:ย Optional[str]ย =ย None, confirm:ย Union[dict,ย ConfirmObject,ย ForwardRef(None)]ย =ย None, **others:ย dict) -
-
-

Block Elements are things that exists inside of your Blocks. -https://api.slack.com/reference/block-kit/block-elements

-

An interactive element that inserts a button. The button can be a trigger for -anything from opening a simple link to starting a complex workflow. -https://api.slack.com/reference/block-kit/block-elements#button

-
- -Expand source code - -
class ButtonElement(InteractiveElement):
-    type = "button"
-    text_max_length = 75
-    url_max_length = 3000
-    value_max_length = 2000
-
-    @property
-    def attributes(self) -> Set[str]:
-        return super().attributes.union({"text", "url", "value", "style", "confirm"})
-
-    def __init__(
-        self,
-        *,
-        text: Union[str, dict, TextObject],
-        action_id: Optional[str] = None,
-        url: Optional[str] = None,
-        value: Optional[str] = None,
-        style: Optional[str] = None,  # primary, danger
-        confirm: Optional[Union[dict, ConfirmObject]] = None,
-        **others: dict,
-    ):
-        """An interactive element that inserts a button. The button can be a trigger for
-        anything from opening a simple link to starting a complex workflow.
-        https://api.slack.com/reference/block-kit/block-elements#button
-        """
-        super().__init__(action_id=action_id, type=self.type)
-        show_unknown_key_warning(self, others)
-
-        # NOTE: default_type=PlainTextObject.type here is only for backward-compatibility with version 2.5.0
-        self.text = TextObject.parse(text, default_type=PlainTextObject.type)
-        self.url = url
-        self.value = value
-        self.style = style
-        self.confirm = ConfirmObject.parse(confirm)
-
-    @JsonValidator(f"text attribute cannot exceed {text_max_length} characters")
-    def _validate_text_length(self):
-        return (
-            self.text is None
-            or self.text.text is None
-            or len(self.text.text) <= self.text_max_length
-        )
-
-    @JsonValidator(f"url attribute cannot exceed {url_max_length} characters")
-    def _validate_url_length(self):
-        return self.url is None or len(self.url) <= self.url_max_length
-
-    @JsonValidator(f"value attribute cannot exceed {value_max_length} characters")
-    def _validate_value_length(self):
-        return self.value is None or len(self.value) <= self.value_max_length
-
-    @EnumValidator("style", ButtonStyles)
-    def _validate_style_valid(self):
-        return self.style is None or self.style in ButtonStyles
-
-

Ancestors

- -

Subclasses

- -

Class variables

-
-
var text_max_length
-
-
-
-
var type
-
-
-
-
var url_max_length
-
-
-
-
var value_max_length
-
-
-
-
-

Inherited members

- -
-
-class ChannelMultiSelectElement -(*, placeholder:ย Union[str,ย dict,ย TextObject,ย ForwardRef(None)]ย =ย None, action_id:ย Optional[str]ย =ย None, initial_channels:ย Optional[Sequence[str]]ย =ย None, confirm:ย Union[dict,ย ConfirmObject,ย ForwardRef(None)]ย =ย None, max_selected_items:ย Optional[int]ย =ย None, **others:ย dict) -
-
-

Block Elements are things that exists inside of your Blocks. -https://api.slack.com/reference/block-kit/block-elements

-

This multi-select menu will populate its options with a list of public channels visible -to the current user in the active workspace. -https://api.slack.com/reference/block-kit/block-elements#channel_multi_select

-
- -Expand source code - -
class ChannelMultiSelectElement(InputInteractiveElement):
-    type = "multi_channels_select"
-
-    @property
-    def attributes(self) -> Set[str]:
-        return super().attributes.union({"initial_channels", "max_selected_items"})
-
-    def __init__(
-        self,
-        *,
-        placeholder: Optional[Union[str, dict, TextObject]] = None,
-        action_id: Optional[str] = None,
-        initial_channels: Optional[Sequence[str]] = None,
-        confirm: Optional[Union[dict, ConfirmObject]] = None,
-        max_selected_items: Optional[int] = None,
-        **others: dict,
-    ):
-        """
-        This multi-select menu will populate its options with a list of public channels visible
-        to the current user in the active workspace.
-        https://api.slack.com/reference/block-kit/block-elements#channel_multi_select
-        """
-        super().__init__(
-            type=self.type,
-            action_id=action_id,
-            placeholder=TextObject.parse(placeholder, PlainTextObject.type),
-            confirm=ConfirmObject.parse(confirm),
-        )
-        show_unknown_key_warning(self, others)
-
-        self.initial_channels = initial_channels
-        self.max_selected_items = max_selected_items
-
-

Ancestors

- -

Class variables

-
-
var type
-
-
-
-
-

Inherited members

- -
-
-class ChannelSelectElement -(*, placeholder:ย Union[str,ย dict,ย TextObject,ย ForwardRef(None)]ย =ย None, action_id:ย Optional[str]ย =ย None, initial_channel:ย Optional[str]ย =ย None, confirm:ย Union[dict,ย ConfirmObject,ย ForwardRef(None)]ย =ย None, response_url_enabled:ย Optional[bool]ย =ย None, **others:ย dict) -
-
-

Block Elements are things that exists inside of your Blocks. -https://api.slack.com/reference/block-kit/block-elements

-

This select menu will populate its options with a list of public channels -visible to the current user in the active workspace. -https://api.slack.com/reference/block-kit/block-elements#channel_select

-
- -Expand source code - -
class ChannelSelectElement(InputInteractiveElement):
-    type = "channels_select"
-
-    @property
-    def attributes(self) -> Set[str]:
-        return super().attributes.union({"initial_channel", "response_url_enabled"})
-
-    def __init__(
-        self,
-        *,
-        placeholder: Optional[Union[str, dict, TextObject]] = None,
-        action_id: Optional[str] = None,
-        initial_channel: Optional[str] = None,
-        confirm: Optional[Union[dict, ConfirmObject]] = None,
-        response_url_enabled: Optional[bool] = None,
-        **others: dict,
-    ):
-        """
-        This select menu will populate its options with a list of public channels
-        visible to the current user in the active workspace.
-        https://api.slack.com/reference/block-kit/block-elements#channel_select
-        """
-        super().__init__(
-            type=self.type,
-            action_id=action_id,
-            placeholder=TextObject.parse(placeholder, PlainTextObject.type),
-            confirm=ConfirmObject.parse(confirm),
-        )
-        show_unknown_key_warning(self, others)
-
-        self.initial_channel = initial_channel
-        self.response_url_enabled = response_url_enabled
-
-

Ancestors

- -

Class variables

-
-
var type
-
-
-
-
-

Inherited members

- -
-
-class CheckboxesElement -(*, action_id:ย Optional[str]ย =ย None, placeholder:ย Optional[str]ย =ย None, options:ย Optional[Sequence[Union[dict,ย Option]]]ย =ย None, initial_options:ย Optional[Sequence[Union[dict,ย Option]]]ย =ย None, confirm:ย Union[dict,ย ConfirmObject,ย ForwardRef(None)]ย =ย None, **others:ย dict) -
-
-

Block Elements are things that exists inside of your Blocks. -https://api.slack.com/reference/block-kit/block-elements

-

A checkbox group that allows a user to choose multiple items from a list of possible options. -https://api.slack.com/reference/block-kit/block-elements#checkboxes

-
- -Expand source code - -
class CheckboxesElement(InputInteractiveElement):
-    type = "checkboxes"
-
-    @property
-    def attributes(self) -> Set[str]:
-        return super().attributes.union({"options", "initial_options"})
-
-    def __init__(
-        self,
-        *,
-        action_id: Optional[str] = None,
-        placeholder: Optional[str] = None,
-        options: Optional[Sequence[Union[dict, Option]]] = None,
-        initial_options: Optional[Sequence[Union[dict, Option]]] = None,
-        confirm: Optional[Union[dict, ConfirmObject]] = None,
-        **others: dict,
-    ):
-        """A checkbox group that allows a user to choose multiple items from a list of possible options.
-        https://api.slack.com/reference/block-kit/block-elements#checkboxes
-        """
-        super().__init__(
-            type=self.type,
-            action_id=action_id,
-            placeholder=TextObject.parse(placeholder, PlainTextObject.type),
-            confirm=ConfirmObject.parse(confirm),
-        )
-        show_unknown_key_warning(self, others)
-
-        self.options = Option.parse_all(options)
-        self.initial_options = Option.parse_all(initial_options)
-
-

Ancestors

- -

Class variables

-
-
var type
-
-
-
-
-

Inherited members

- -
-
-class ConversationFilter -(*, include:ย Optional[Sequence[str]]ย =ย None, exclude_bot_users:ย Optional[bool]ย =ย None, exclude_external_shared_channels:ย Optional[bool]ย =ย None) -
-
-

The base class for JSON serializable class objects

-
- -Expand source code - -
class ConversationFilter(JsonObject):
-    attributes = {"include", "exclude_bot_users", "exclude_external_shared_channels"}
-    logger = logging.getLogger(__name__)
-
-    def __init__(
-        self,
-        *,
-        include: Optional[Sequence[str]] = None,
-        exclude_bot_users: Optional[bool] = None,
-        exclude_external_shared_channels: Optional[bool] = None,
-    ):
-        self.include = include
-        self.exclude_bot_users = exclude_bot_users
-        self.exclude_external_shared_channels = exclude_external_shared_channels
-
-    @classmethod
-    def parse(cls, filter: Union[dict, "ConversationFilter"]):  # skipcq: PYL-W0622
-        if filter is None:  # skipcq: PYL-R1705
-            return None
-        elif isinstance(filter, ConversationFilter):
-            return filter
-        elif isinstance(filter, dict):
-            d = copy.copy(filter)
-            return ConversationFilter(**d)
-        else:
-            cls.logger.warning(
-                f"Unknown conversation filter object detected and skipped ({filter})"
-            )
-            return None
-
-

Ancestors

- -

Class variables

-
-
var logger
-
-
-
-
-

Static methods

-
-
-def parse(filter:ย Union[dict,ย ForwardRef('ConversationFilter')]) -
-
-
-
- -Expand source code - -
@classmethod
-def parse(cls, filter: Union[dict, "ConversationFilter"]):  # skipcq: PYL-W0622
-    if filter is None:  # skipcq: PYL-R1705
-        return None
-    elif isinstance(filter, ConversationFilter):
-        return filter
-    elif isinstance(filter, dict):
-        d = copy.copy(filter)
-        return ConversationFilter(**d)
-    else:
-        cls.logger.warning(
-            f"Unknown conversation filter object detected and skipped ({filter})"
-        )
-        return None
-
-
-
-

Inherited members

- -
-
-class ConversationMultiSelectElement -(*, placeholder:ย Union[str,ย dict,ย TextObject,ย ForwardRef(None)]ย =ย None, action_id:ย Optional[str]ย =ย None, initial_conversations:ย Optional[Sequence[str]]ย =ย None, confirm:ย Union[dict,ย ConfirmObject,ย ForwardRef(None)]ย =ย None, max_selected_items:ย Optional[int]ย =ย None, default_to_current_conversation:ย Optional[bool]ย =ย None, filter:ย Union[dict,ย ConversationFilter,ย ForwardRef(None)]ย =ย None, **others:ย dict) -
-
-

Block Elements are things that exists inside of your Blocks. -https://api.slack.com/reference/block-kit/block-elements

-

This multi-select menu will populate its options with a list of public and private channels, -DMs, and MPIMs visible to the current user in the active workspace. -https://api.slack.com/reference/block-kit/block-elements#conversation_multi_select

-
- -Expand source code - -
class ConversationMultiSelectElement(InputInteractiveElement):
-    type = "multi_conversations_select"
-
-    @property
-    def attributes(self) -> Set[str]:
-        return super().attributes.union(
-            {
-                "initial_conversations",
-                "max_selected_items",
-                "default_to_current_conversation",
-                "filter",
-            }
-        )
-
-    def __init__(
-        self,
-        *,
-        placeholder: Optional[Union[str, dict, TextObject]] = None,
-        action_id: Optional[str] = None,
-        initial_conversations: Optional[Sequence[str]] = None,
-        confirm: Optional[Union[dict, ConfirmObject]] = None,
-        max_selected_items: Optional[int] = None,
-        default_to_current_conversation: Optional[bool] = None,
-        filter: Optional[Union[dict, ConversationFilter]] = None,  # skipcq: PYL-W0622
-        **others: dict,
-    ):
-        """
-        This multi-select menu will populate its options with a list of public and private channels,
-        DMs, and MPIMs visible to the current user in the active workspace.
-        https://api.slack.com/reference/block-kit/block-elements#conversation_multi_select
-        """
-        super().__init__(
-            type=self.type,
-            action_id=action_id,
-            placeholder=TextObject.parse(placeholder, PlainTextObject.type),
-            confirm=ConfirmObject.parse(confirm),
-        )
-        show_unknown_key_warning(self, others)
-
-        self.initial_conversations = initial_conversations
-        self.max_selected_items = max_selected_items
-        self.default_to_current_conversation = default_to_current_conversation
-        self.filter = ConversationFilter.parse(filter)
-
-

Ancestors

- -

Class variables

-
-
var type
-
-
-
-
-

Inherited members

- -
-
-class ConversationSelectElement -(*, placeholder:ย Union[str,ย dict,ย TextObject,ย ForwardRef(None)]ย =ย None, action_id:ย Optional[str]ย =ย None, initial_conversation:ย Optional[str]ย =ย None, confirm:ย Union[dict,ย ConfirmObject,ย ForwardRef(None)]ย =ย None, response_url_enabled:ย Optional[bool]ย =ย None, default_to_current_conversation:ย Optional[bool]ย =ย None, filter:ย Optional[ConversationFilter]ย =ย None, **others:ย dict) -
-
-

Block Elements are things that exists inside of your Blocks. -https://api.slack.com/reference/block-kit/block-elements

-

This select menu will populate its options with a list of public and private -channels, DMs, and MPIMs visible to the current user in the active workspace. -https://api.slack.com/reference/block-kit/block-elements#conversation_select

-
- -Expand source code - -
class ConversationSelectElement(InputInteractiveElement):
-    type = "conversations_select"
-
-    @property
-    def attributes(self) -> Set[str]:
-        return super().attributes.union(
-            {
-                "initial_conversation",
-                "response_url_enabled",
-                "filter",
-                "default_to_current_conversation",
-            }
-        )
-
-    def __init__(
-        self,
-        *,
-        placeholder: Optional[Union[str, dict, TextObject]] = None,
-        action_id: Optional[str] = None,
-        initial_conversation: Optional[str] = None,
-        confirm: Optional[Union[dict, ConfirmObject]] = None,
-        response_url_enabled: Optional[bool] = None,
-        default_to_current_conversation: Optional[bool] = None,
-        filter: Optional[ConversationFilter] = None,  # skipcq: PYL-W0622
-        **others: dict,
-    ):
-        """
-        This select menu will populate its options with a list of public and private
-        channels, DMs, and MPIMs visible to the current user in the active workspace.
-        https://api.slack.com/reference/block-kit/block-elements#conversation_select
-        """
-        super().__init__(
-            type=self.type,
-            action_id=action_id,
-            placeholder=TextObject.parse(placeholder, PlainTextObject.type),
-            confirm=ConfirmObject.parse(confirm),
-        )
-        show_unknown_key_warning(self, others)
-
-        self.initial_conversation = initial_conversation
-        self.response_url_enabled = response_url_enabled
-        self.default_to_current_conversation = default_to_current_conversation
-        self.filter = filter
-
-

Ancestors

- -

Class variables

-
-
var type
-
-
-
-
-

Inherited members

- -
-
-class DatePickerElement -(*, action_id:ย Optional[str]ย =ย None, placeholder:ย Union[str,ย dict,ย TextObject,ย ForwardRef(None)]ย =ย None, initial_date:ย Optional[str]ย =ย None, confirm:ย Union[dict,ย ConfirmObject,ย ForwardRef(None)]ย =ย None, **others:ย dict) -
-
-

Block Elements are things that exists inside of your Blocks. -https://api.slack.com/reference/block-kit/block-elements

-

An element which lets users easily select a date from a calendar style UI. -Date picker elements can be used inside of SectionBlocks and ActionsBlocks. -https://api.slack.com/reference/block-kit/block-elements#datepicker

-
- -Expand source code - -
class DatePickerElement(InputInteractiveElement):
-    type = "datepicker"
-
-    @property
-    def attributes(self) -> Set[str]:
-        return super().attributes.union({"initial_date"})
-
-    def __init__(
-        self,
-        *,
-        action_id: Optional[str] = None,
-        placeholder: Optional[Union[str, dict, TextObject]] = None,
-        initial_date: Optional[str] = None,
-        confirm: Optional[Union[dict, ConfirmObject]] = None,
-        **others: dict,
-    ):
-        """
-        An element which lets users easily select a date from a calendar style UI.
-        Date picker elements can be used inside of SectionBlocks and ActionsBlocks.
-        https://api.slack.com/reference/block-kit/block-elements#datepicker
-        """
-        super().__init__(
-            type=self.type,
-            action_id=action_id,
-            placeholder=TextObject.parse(placeholder, PlainTextObject.type),
-            confirm=ConfirmObject.parse(confirm),
-        )
-        show_unknown_key_warning(self, others)
-
-        self.initial_date = initial_date
-
-    @JsonValidator("initial_date attribute must be in format 'YYYY-MM-DD'")
-    def _validate_initial_date_valid(self):
-        return self.initial_date is None or re.match(
-            r"\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])", self.initial_date
-        )
-
-

Ancestors

- -

Class variables

-
-
var type
-
-
-
-
-

Inherited members

- -
-
-class ExternalDataMultiSelectElement -(*, placeholder:ย Union[str,ย dict,ย TextObject,ย ForwardRef(None)]ย =ย None, action_id:ย Optional[str]ย =ย None, min_query_length:ย Optional[int]ย =ย None, initial_options:ย Optional[Sequence[Union[dict,ย Option]]]ย =ย None, confirm:ย Union[dict,ย ConfirmObject,ย ForwardRef(None)]ย =ย None, max_selected_items:ย Optional[int]ย =ย None, **others:ย dict) -
-
-

Block Elements are things that exists inside of your Blocks. -https://api.slack.com/reference/block-kit/block-elements

-

This select menu will load its options from an external data source, allowing -for a dynamic list of options. -https://api.slack.com/reference/block-kit/block-elements#external-select

-
- -Expand source code - -
class ExternalDataMultiSelectElement(InputInteractiveElement):
-    type = "multi_external_select"
-
-    @property
-    def attributes(self) -> Set[str]:
-        return super().attributes.union(
-            {"min_query_length", "initial_options", "max_selected_items"}
-        )
-
-    def __init__(
-        self,
-        *,
-        placeholder: Optional[Union[str, dict, TextObject]] = None,
-        action_id: Optional[str] = None,
-        min_query_length: Optional[int] = None,
-        initial_options: Optional[Sequence[Union[dict, Option]]] = None,
-        confirm: Optional[Union[dict, ConfirmObject]] = None,
-        max_selected_items: Optional[int] = None,
-        **others: dict,
-    ):
-        """
-        This select menu will load its options from an external data source, allowing
-        for a dynamic list of options.
-        https://api.slack.com/reference/block-kit/block-elements#external-select
-        """
-        super().__init__(
-            type=self.type,
-            action_id=action_id,
-            placeholder=TextObject.parse(placeholder, PlainTextObject.type),
-            confirm=ConfirmObject.parse(confirm),
-        )
-        show_unknown_key_warning(self, others)
-
-        self.min_query_length = min_query_length
-        self.initial_options = Option.parse_all(initial_options)
-        self.max_selected_items = max_selected_items
-
-

Ancestors

- -

Class variables

-
-
var type
-
-
-
-
-

Inherited members

- -
-
-class ExternalDataSelectElement -(*, action_id:ย Optional[str]ย =ย None, placeholder:ย Union[str,ย TextObject]ย =ย None, initial_option:ย Union[Option,ย ForwardRef(None),ย OptionGroup]ย =ย None, min_query_length:ย Optional[int]ย =ย None, confirm:ย Union[dict,ย ConfirmObject,ย ForwardRef(None)]ย =ย None, **others:ย dict) -
-
-

Block Elements are things that exists inside of your Blocks. -https://api.slack.com/reference/block-kit/block-elements

-

This select menu will load its options from an external data source, allowing -for a dynamic list of options. -https://api.slack.com/reference/block-kit/block-elements#external_select

-
- -Expand source code - -
class ExternalDataSelectElement(InputInteractiveElement):
-    type = "external_select"
-
-    @property
-    def attributes(self) -> Set[str]:
-        return super().attributes.union({"min_query_length", "initial_option"})
-
-    def __init__(
-        self,
-        *,
-        action_id: Optional[str] = None,
-        placeholder: Union[str, TextObject] = None,
-        initial_option: Union[Optional[Option], Optional[OptionGroup]] = None,
-        min_query_length: Optional[int] = None,
-        confirm: Optional[Union[dict, ConfirmObject]] = None,
-        **others: dict,
-    ):
-        """
-        This select menu will load its options from an external data source, allowing
-        for a dynamic list of options.
-        https://api.slack.com/reference/block-kit/block-elements#external_select
-        """
-        super().__init__(
-            type=self.type,
-            action_id=action_id,
-            placeholder=TextObject.parse(placeholder, PlainTextObject.type),
-            confirm=ConfirmObject.parse(confirm),
-        )
-        show_unknown_key_warning(self, others)
-
-        self.min_query_length = min_query_length
-        self.initial_option = initial_option
-
-

Ancestors

- -

Class variables

-
-
var type
-
-
-
-
-

Inherited members

- -
-
-class ImageElement -(*, image_url:ย Optional[str]ย =ย None, alt_text:ย Optional[str]ย =ย None, **others:ย dict) -
-
-

Block Elements are things that exists inside of your Blocks. -https://api.slack.com/reference/block-kit/block-elements

-

An element to insert an image - this element can be used in section and -context blocks only. If you want a block with only an image in it, -you're looking for the image block. -https://api.slack.com/reference/block-kit/block-elements#image

-
- -Expand source code - -
class ImageElement(BlockElement):
-    type = "image"
-    image_url_max_length = 3000
-    alt_text_max_length = 2000
-
-    @property
-    def attributes(self) -> Set[str]:
-        return super().attributes.union({"alt_text", "image_url"})
-
-    def __init__(
-        self,
-        *,
-        image_url: Optional[str] = None,
-        alt_text: Optional[str] = None,
-        **others: dict,
-    ):
-        """An element to insert an image - this element can be used in section and
-        context blocks only. If you want a block with only an image in it,
-        you're looking for the image block.
-        https://api.slack.com/reference/block-kit/block-elements#image
-        """
-        super().__init__(type=self.type)
-        show_unknown_key_warning(self, others)
-
-        self.image_url = image_url
-        self.alt_text = alt_text
-
-    @JsonValidator(
-        f"image_url attribute cannot exceed {image_url_max_length} characters"
-    )
-    def _validate_image_url_length(self):
-        return len(self.image_url) <= self.image_url_max_length
-
-    @JsonValidator(f"alt_text attribute cannot exceed {alt_text_max_length} characters")
-    def _validate_alt_text_length(self):
-        return len(self.alt_text) <= self.alt_text_max_length
-
-

Ancestors

- -

Class variables

-
-
var alt_text_max_length
-
-
-
-
var image_url_max_length
-
-
-
-
var type
-
-
-
-
-

Instance variables

-
-
var attributes :ย Set[str]
-
-

set() -> new empty set object -set(iterable) -> new set object

-

Build an unordered collection of unique elements.

-
- -Expand source code - -
@property
-def attributes(self) -> Set[str]:
-    return super().attributes.union({"alt_text", "image_url"})
-
-
-
-

Inherited members

- -
-
-class InputInteractiveElement -(*, action_id:ย Optional[str]ย =ย None, placeholder:ย Union[str,ย TextObject]ย =ย None, type:ย Optional[str]ย =ย None, subtype:ย Optional[str]ย =ย None, confirm:ย Union[dict,ย ConfirmObject,ย ForwardRef(None)]ย =ย None, **others:ย dict) -
-
-

Block Elements are things that exists inside of your Blocks. -https://api.slack.com/reference/block-kit/block-elements

-

InteractiveElement that is usable in input blocks

-

We generally recommend using the concrete subclasses for better supports of available properties.

-
- -Expand source code - -
class InputInteractiveElement(InteractiveElement, metaclass=ABCMeta):
-    placeholder_max_length = 150
-
-    attributes = {"type", "action_id", "placeholder", "confirm"}
-
-    def _subtype_warning(self):
-        warnings.warn(
-            "subtype is deprecated since slackclient 2.6.0, use type instead",
-            DeprecationWarning,
-        )
-
-    @property
-    def subtype(self) -> Optional[str]:
-        return self.type
-
-    def __init__(
-        self,
-        *,
-        action_id: Optional[str] = None,
-        placeholder: Union[str, TextObject] = None,
-        type: Optional[str] = None,  # skipcq: PYL-W0622
-        subtype: Optional[str] = None,
-        confirm: Optional[Union[dict, ConfirmObject]] = None,
-        **others: dict,
-    ):
-        """InteractiveElement that is usable in input blocks
-
-        We generally recommend using the concrete subclasses for better supports of available properties.
-        """
-        if subtype:
-            self._subtype_warning()
-        super().__init__(action_id=action_id, type=type or subtype)
-
-        # Note that we don't intentionally have show_unknown_key_warning for the unknown key warnings here.
-        # It's fine to pass any kwargs to the held dict here although the class does not do any validation.
-        # show_unknown_key_warning(self, others)
-
-        self.placeholder = TextObject.parse(placeholder)
-        self.confirm = ConfirmObject.parse(confirm)
-
-    @JsonValidator(
-        f"placeholder attribute cannot exceed {placeholder_max_length} characters"
-    )
-    def _validate_placeholder_length(self):
-        return (
-            self.placeholder is None
-            or self.placeholder.text is None
-            or len(self.placeholder.text) <= self.placeholder_max_length
-        )
-
-

Ancestors

- -

Subclasses

- -

Class variables

-
-
var placeholder_max_length
-
-
-
-
-

Instance variables

-
-
var subtype :ย Optional[str]
-
-
-
- -Expand source code - -
@property
-def subtype(self) -> Optional[str]:
-    return self.type
-
-
-
-

Inherited members

- -
-
-class InteractiveElement -(*, action_id:ย Optional[str]ย =ย None, type:ย Optional[str]ย =ย None, subtype:ย Optional[str]ย =ย None, **others:ย dict) -
-
-

Block Elements are things that exists inside of your Blocks. -https://api.slack.com/reference/block-kit/block-elements

-

An interactive block element.

-

We generally recommend using the concrete subclasses for better supports of available properties.

-
- -Expand source code - -
class InteractiveElement(BlockElement):
-    action_id_max_length = 255
-
-    @property
-    def attributes(self) -> Set[str]:
-        return super().attributes.union({"alt_text", "action_id"})
-
-    def __init__(
-        self,
-        *,
-        action_id: Optional[str] = None,
-        type: Optional[str] = None,  # skipcq: PYL-W0622
-        subtype: Optional[str] = None,
-        **others: dict,
-    ):
-        """An interactive block element.
-
-        We generally recommend using the concrete subclasses for better supports of available properties.
-        """
-        if subtype:
-            self._subtype_warning()
-        super().__init__(type=type or subtype)
-
-        # Note that we don't intentionally have show_unknown_key_warning for the unknown key warnings here.
-        # It's fine to pass any kwargs to the held dict here although the class does not do any validation.
-        # show_unknown_key_warning(self, others)
-
-        self.action_id = action_id
-
-    @JsonValidator(
-        f"action_id attribute cannot exceed {action_id_max_length} characters"
-    )
-    def _validate_action_id_length(self):
-        return (
-            self.action_id is None or len(self.action_id) <= self.action_id_max_length
-        )
-
-

Ancestors

- -

Subclasses

- -

Class variables

-
-
var action_id_max_length
-
-
-
-
-

Instance variables

-
-
var attributes :ย Set[str]
-
-

set() -> new empty set object -set(iterable) -> new set object

-

Build an unordered collection of unique elements.

-
- -Expand source code - -
@property
-def attributes(self) -> Set[str]:
-    return super().attributes.union({"alt_text", "action_id"})
-
-
-
-

Inherited members

- -
-
-class LinkButtonElement -(*, text:ย str, url:ย str, action_id:ย Optional[str]ย =ย None, style:ย Optional[str]ย =ย None, **others:ย dict) -
-
-

Block Elements are things that exists inside of your Blocks. -https://api.slack.com/reference/block-kit/block-elements

-

A simple button that simply opens a given URL. You will still receive an -interaction payload and will need to send an acknowledgement response. -This is a helper class that makes creating links simpler. -https://api.slack.com/reference/block-kit/block-elements#button

-
- -Expand source code - -
class LinkButtonElement(ButtonElement):
-    def __init__(
-        self,
-        *,
-        text: str,
-        url: str,
-        action_id: Optional[str] = None,
-        style: Optional[str] = None,
-        **others: dict,
-    ):
-        """A simple button that simply opens a given URL. You will still receive an
-        interaction payload and will need to send an acknowledgement response.
-        This is a helper class that makes creating links simpler.
-        https://api.slack.com/reference/block-kit/block-elements#button
-        """
-        super().__init__(
-            # NOTE: value must be always absent
-            text=text,
-            url=url,
-            action_id=action_id,
-            value=None,
-            style=style,
-        )
-        show_unknown_key_warning(self, others)
-
-

Ancestors

- -

Inherited members

- -
-
-class OverflowMenuElement -(*, action_id:ย Optional[str]ย =ย None, options:ย Sequence[Option], confirm:ย Union[dict,ย ConfirmObject,ย ForwardRef(None)]ย =ย None, **others:ย dict) -
-
-

Block Elements are things that exists inside of your Blocks. -https://api.slack.com/reference/block-kit/block-elements

-

This is like a cross between a button and a select menu - when a user clicks -on this overflow button, they will be presented with a list of options to -choose from. Unlike the select menu, there is no typeahead field, and the -button always appears with an ellipsis ("โ€ฆ") rather than customisable text.

-

As such, it is usually used if you want a more compact layout than a select -menu, or to supply a list of less visually important actions after a row of -buttons. You can also specify simple URL links as overflow menu options, -instead of actions.

-

https://api.slack.com/reference/block-kit/block-elements#overflow

-
- -Expand source code - -
class OverflowMenuElement(InteractiveElement):
-    type = "overflow"
-    options_min_length = 2
-    options_max_length = 5
-
-    @property
-    def attributes(self) -> Set[str]:
-        return super().attributes.union({"confirm", "options"})
-
-    def __init__(
-        self,
-        *,
-        action_id: Optional[str] = None,
-        options: Sequence[Union[Option]],
-        confirm: Optional[Union[dict, ConfirmObject]] = None,
-        **others: dict,
-    ):
-        """
-        This is like a cross between a button and a select menu - when a user clicks
-        on this overflow button, they will be presented with a list of options to
-        choose from. Unlike the select menu, there is no typeahead field, and the
-        button always appears with an ellipsis ("โ€ฆ") rather than customisable text.
-
-        As such, it is usually used if you want a more compact layout than a select
-        menu, or to supply a list of less visually important actions after a row of
-        buttons. You can also specify simple URL links as overflow menu options,
-        instead of actions.
-
-        https://api.slack.com/reference/block-kit/block-elements#overflow
-        """
-        super().__init__(action_id=action_id, type=self.type)
-        show_unknown_key_warning(self, others)
-
-        self.options = options
-        self.confirm = ConfirmObject.parse(confirm)
-
-    @JsonValidator(
-        f"options attribute must have between {options_min_length} "
-        f"and {options_max_length} items"
-    )
-    def _validate_options_length(self):
-        return self.options_min_length <= len(self.options) <= self.options_max_length
-
-

Ancestors

- -

Class variables

-
-
var options_max_length
-
-
-
-
var options_min_length
-
-
-
-
var type
-
-
-
-
-

Inherited members

- -
-
-class PlainTextInputElement -(*, action_id:ย Optional[str]ย =ย None, placeholder:ย Union[str,ย dict,ย TextObject,ย ForwardRef(None)]ย =ย None, confirm:ย Union[dict,ย ConfirmObject,ย ForwardRef(None)]ย =ย None, initial_value:ย Optional[str]ย =ย None, multiline:ย Optional[bool]ย =ย None, min_length:ย Optional[int]ย =ย None, max_length:ย Optional[int]ย =ย None, dispatch_action_config:ย Union[dict,ย DispatchActionConfig,ย ForwardRef(None)]ย =ย None, **others:ย dict) -
-
-

Block Elements are things that exists inside of your Blocks. -https://api.slack.com/reference/block-kit/block-elements

-

A plain-text input, similar to the HTML tag, creates a field -where a user can enter freeform data. It can appear as a single-line -field or a larger textarea using the multiline flag. Plain-text input -elements can be used inside of SectionBlocks and ActionsBlocks. -https://api.slack.com/reference/block-kit/block-elements#input

-
- -Expand source code - -
class PlainTextInputElement(InputInteractiveElement):
-    type = "plain_text_input"
-
-    @property
-    def attributes(self) -> Set[str]:
-        return super().attributes.union(
-            {
-                "initial_value",
-                "multiline",
-                "min_length",
-                "max_length",
-                "dispatch_action_config",
-            }
-        )
-
-    def __init__(
-        self,
-        *,
-        action_id: Optional[str] = None,
-        placeholder: Optional[Union[str, dict, TextObject]] = None,
-        confirm: Optional[Union[dict, ConfirmObject]] = None,
-        initial_value: Optional[str] = None,
-        multiline: Optional[bool] = None,
-        min_length: Optional[int] = None,
-        max_length: Optional[int] = None,
-        dispatch_action_config: Optional[Union[dict, DispatchActionConfig]] = None,
-        **others: dict,
-    ):
-        """
-        A plain-text input, similar to the HTML <input> tag, creates a field
-        where a user can enter freeform data. It can appear as a single-line
-        field or a larger textarea using the multiline flag. Plain-text input
-        elements can be used inside of SectionBlocks and ActionsBlocks.
-        https://api.slack.com/reference/block-kit/block-elements#input
-        """
-        super().__init__(
-            type=self.type,
-            action_id=action_id,
-            placeholder=TextObject.parse(placeholder, PlainTextObject.type),
-            confirm=ConfirmObject.parse(confirm),
-        )
-        show_unknown_key_warning(self, others)
-
-        self.initial_value = initial_value
-        self.multiline = multiline
-        self.min_length = min_length
-        self.max_length = max_length
-        self.dispatch_action_config = dispatch_action_config
-
-

Ancestors

- -

Class variables

-
-
var type
-
-
-
-
-

Inherited members

- -
-
-class RadioButtonsElement -(*, action_id:ย Optional[str]ย =ย None, placeholder:ย Union[str,ย dict,ย TextObject,ย ForwardRef(None)]ย =ย None, options:ย Optional[Sequence[Union[dict,ย Option]]]ย =ย None, initial_option:ย Union[dict,ย Option,ย ForwardRef(None)]ย =ย None, confirm:ย Union[dict,ย ConfirmObject,ย ForwardRef(None)]ย =ย None, **others:ย dict) -
-
-

Block Elements are things that exists inside of your Blocks. -https://api.slack.com/reference/block-kit/block-elements

-

A radio button group that allows a user to choose one item from a list of possible options. -https://api.slack.com/reference/block-kit/block-elements#radio

-
- -Expand source code - -
class RadioButtonsElement(InputInteractiveElement):
-    type = "radio_buttons"
-
-    @property
-    def attributes(self) -> Set[str]:
-        return super().attributes.union({"options", "initial_option"})
-
-    def __init__(
-        self,
-        *,
-        action_id: Optional[str] = None,
-        placeholder: Optional[Union[str, dict, TextObject]] = None,
-        options: Optional[Sequence[Union[dict, Option]]] = None,
-        initial_option: Optional[Union[dict, Option]] = None,
-        confirm: Optional[Union[dict, ConfirmObject]] = None,
-        **others: dict,
-    ):
-        """A radio button group that allows a user to choose one item from a list of possible options.
-        https://api.slack.com/reference/block-kit/block-elements#radio
-        """
-        super().__init__(
-            type=self.type,
-            action_id=action_id,
-            placeholder=TextObject.parse(placeholder, PlainTextObject.type),
-            confirm=ConfirmObject.parse(confirm),
-        )
-        show_unknown_key_warning(self, others)
-
-        self.options = options
-        self.initial_option = initial_option
-
-

Ancestors

- -

Class variables

-
-
var type
-
-
-
-
-

Inherited members

- -
-
-class SelectElement -(*, action_id:ย Optional[str]ย =ย None, placeholder:ย Optional[str]ย =ย None, options:ย Optional[Sequence[Option]]ย =ย None, option_groups:ย Optional[Sequence[OptionGroup]]ย =ย None, initial_option:ย Optional[Option]ย =ย None, confirm:ย Union[dict,ย ConfirmObject,ย ForwardRef(None)]ย =ย None, **others:ย dict) -
-
-

Block Elements are things that exists inside of your Blocks. -https://api.slack.com/reference/block-kit/block-elements

-

This is the simplest form of select menu, with a static list of options passed in when defining the element. -https://api.slack.com/reference/block-kit/block-elements#static_select

-
- -Expand source code - -
class SelectElement(InputInteractiveElement):
-    type = "static_select"
-    options_max_length = 100
-    option_groups_max_length = 100
-
-    @property
-    def attributes(self) -> Set[str]:
-        return super().attributes.union({"options", "option_groups", "initial_option"})
-
-    def __init__(
-        self,
-        *,
-        action_id: Optional[str] = None,
-        placeholder: Optional[str] = None,
-        options: Optional[Sequence[Option]] = None,
-        option_groups: Optional[Sequence[OptionGroup]] = None,
-        initial_option: Optional[Option] = None,
-        confirm: Optional[Union[dict, ConfirmObject]] = None,
-        **others: dict,
-    ):
-        """This is the simplest form of select menu, with a static list of options passed in when defining the element.
-        https://api.slack.com/reference/block-kit/block-elements#static_select
-        """
-        super().__init__(
-            type=self.type,
-            action_id=action_id,
-            placeholder=TextObject.parse(placeholder, PlainTextObject.type),
-            confirm=ConfirmObject.parse(confirm),
-        )
-        show_unknown_key_warning(self, others)
-
-        self.options = options
-        self.option_groups = option_groups
-        self.initial_option = initial_option
-
-    @JsonValidator(f"options attribute cannot exceed {options_max_length} elements")
-    def _validate_options_length(self):
-        return self.options is None or len(self.options) <= self.options_max_length
-
-    @JsonValidator(
-        f"option_groups attribute cannot exceed {option_groups_max_length} elements"
-    )
-    def _validate_option_groups_length(self):
-        return (
-            self.option_groups is None
-            or len(self.option_groups) <= self.option_groups_max_length
-        )
-
-    @JsonValidator("options and option_groups cannot both be specified")
-    def _validate_options_and_option_groups_both_specified(self):
-        return not (self.options is not None and self.option_groups is not None)
-
-    @JsonValidator("options or option_groups must be specified")
-    def _validate_neither_options_or_option_groups_is_specified(self):
-        return self.options is not None or self.option_groups is not None
-
-

Ancestors

- -

Class variables

-
-
var option_groups_max_length
-
-
-
-
var options_max_length
-
-
-
-
var type
-
-
-
-
-

Inherited members

- -
-
-class StaticMultiSelectElement -(*, placeholder:ย Union[str,ย dict,ย TextObject,ย ForwardRef(None)]ย =ย None, action_id:ย Optional[str]ย =ย None, options:ย Optional[Sequence[Option]]ย =ย None, option_groups:ย Optional[Sequence[OptionGroup]]ย =ย None, initial_options:ย Optional[Sequence[Option]]ย =ย None, confirm:ย Union[dict,ย ConfirmObject,ย ForwardRef(None)]ย =ย None, max_selected_items:ย Optional[int]ย =ย None, **others:ย dict) -
-
-

Block Elements are things that exists inside of your Blocks. -https://api.slack.com/reference/block-kit/block-elements

-

This is the simplest form of select menu, with a static list of options passed in when defining the element. -https://api.slack.com/reference/block-kit/block-elements#static_multi_select

-
- -Expand source code - -
class StaticMultiSelectElement(InputInteractiveElement):
-    type = "multi_static_select"
-    options_max_length = 100
-    option_groups_max_length = 100
-
-    @property
-    def attributes(self) -> Set[str]:
-        return super().attributes.union(
-            {"options", "option_groups", "initial_options", "max_selected_items"}
-        )
-
-    def __init__(
-        self,
-        *,
-        placeholder: Optional[Union[str, dict, TextObject]] = None,
-        action_id: Optional[str] = None,
-        options: Optional[Sequence[Option]] = None,
-        option_groups: Optional[Sequence[OptionGroup]] = None,
-        initial_options: Optional[Sequence[Option]] = None,
-        confirm: Optional[Union[dict, ConfirmObject]] = None,
-        max_selected_items: Optional[int] = None,
-        **others: dict,
-    ):
-        """
-        This is the simplest form of select menu, with a static list of options passed in when defining the element.
-        https://api.slack.com/reference/block-kit/block-elements#static_multi_select
-        """
-        super().__init__(
-            type=self.type,
-            action_id=action_id,
-            placeholder=TextObject.parse(placeholder, PlainTextObject.type),
-            confirm=ConfirmObject.parse(confirm),
-        )
-        show_unknown_key_warning(self, others)
-
-        self.options = Option.parse_all(options)
-        self.option_groups = OptionGroup.parse_all(option_groups)
-        self.initial_options = Option.parse_all(initial_options)
-        self.max_selected_items = max_selected_items
-
-    @JsonValidator(f"options attribute cannot exceed {options_max_length} elements")
-    def _validate_options_length(self):
-        return self.options is None or len(self.options) <= self.options_max_length
-
-    @JsonValidator(
-        f"option_groups attribute cannot exceed {option_groups_max_length} elements"
-    )
-    def _validate_option_groups_length(self):
-        return (
-            self.option_groups is None
-            or len(self.option_groups) <= self.option_groups_max_length
-        )
-
-    @JsonValidator("options and option_groups cannot both be specified")
-    def _validate_options_and_option_groups_both_specified(self):
-        return self.options is None or self.option_groups is None
-
-    @JsonValidator("options or option_groups must be specified")
-    def _validate_neither_options_or_option_groups_is_specified(self):
-        return self.options is not None or self.option_groups is not None
-
-

Ancestors

- -

Class variables

-
-
var option_groups_max_length
-
-
-
-
var options_max_length
-
-
-
-
var type
-
-
-
-
-

Inherited members

- -
-
-class StaticSelectElement -(*, placeholder:ย Union[str,ย dict,ย TextObject,ย ForwardRef(None)]ย =ย None, action_id:ย Optional[str]ย =ย None, options:ย Optional[Sequence[Union[dict,ย Option]]]ย =ย None, option_groups:ย Optional[Sequence[Union[dict,ย OptionGroup]]]ย =ย None, initial_option:ย Union[dict,ย Option,ย ForwardRef(None)]ย =ย None, confirm:ย Union[dict,ย ConfirmObject,ย ForwardRef(None)]ย =ย None, **others:ย dict) -
-
-

Block Elements are things that exists inside of your Blocks. -https://api.slack.com/reference/block-kit/block-elements

-

This is the simplest form of select menu, with a static list of options passed in when defining the element. -https://api.slack.com/reference/block-kit/block-elements#static_select

-
- -Expand source code - -
class StaticSelectElement(InputInteractiveElement):
-    type = "static_select"
-    options_max_length = 100
-    option_groups_max_length = 100
-
-    @property
-    def attributes(self) -> Set[str]:
-        return super().attributes.union({"options", "option_groups", "initial_option"})
-
-    def __init__(
-        self,
-        *,
-        placeholder: Optional[Union[str, dict, TextObject]] = None,
-        action_id: Optional[str] = None,
-        options: Optional[Sequence[Union[dict, Option]]] = None,
-        option_groups: Optional[Sequence[Union[dict, OptionGroup]]] = None,
-        initial_option: Optional[Union[dict, Option]] = None,
-        confirm: Optional[Union[dict, ConfirmObject]] = None,
-        **others: dict,
-    ):
-        """This is the simplest form of select menu, with a static list of options passed in when defining the element.
-        https://api.slack.com/reference/block-kit/block-elements#static_select
-        """
-        super().__init__(
-            type=self.type,
-            action_id=action_id,
-            placeholder=TextObject.parse(placeholder, PlainTextObject.type),
-            confirm=ConfirmObject.parse(confirm),
-        )
-        show_unknown_key_warning(self, others)
-
-        self.options = options
-        self.option_groups = option_groups
-        self.initial_option = initial_option
-
-    @JsonValidator(f"options attribute cannot exceed {options_max_length} elements")
-    def _validate_options_length(self):
-        return self.options is None or len(self.options) <= self.options_max_length
-
-    @JsonValidator(
-        f"option_groups attribute cannot exceed {option_groups_max_length} elements"
-    )
-    def _validate_option_groups_length(self):
-        return (
-            self.option_groups is None
-            or len(self.option_groups) <= self.option_groups_max_length
-        )
-
-    @JsonValidator("options and option_groups cannot both be specified")
-    def _validate_options_and_option_groups_both_specified(self):
-        return not (self.options is not None and self.option_groups is not None)
-
-    @JsonValidator("options or option_groups must be specified")
-    def _validate_neither_options_or_option_groups_is_specified(self):
-        return self.options is not None or self.option_groups is not None
-
-

Ancestors

- -

Class variables

-
-
var option_groups_max_length
-
-
-
-
var options_max_length
-
-
-
-
var type
-
-
-
-
-

Inherited members

- -
-
-class TimePickerElement -(*, action_id:ย Optional[str]ย =ย None, placeholder:ย Union[str,ย dict,ย TextObject,ย ForwardRef(None)]ย =ย None, initial_time:ย Optional[str]ย =ย None, confirm:ย Union[dict,ย ConfirmObject,ย ForwardRef(None)]ย =ย None, **others:ย dict) -
-
-

Block Elements are things that exists inside of your Blocks. -https://api.slack.com/reference/block-kit/block-elements

-

An element which allows selection of a time of day. -https://api.slack.com/reference/block-kit/block-elements#timepicker

-
- -Expand source code - -
class TimePickerElement(InputInteractiveElement):
-    type = "timepicker"
-
-    @property
-    def attributes(self) -> Set[str]:
-        return super().attributes.union({"initial_time"})
-
-    def __init__(
-        self,
-        *,
-        action_id: Optional[str] = None,
-        placeholder: Optional[Union[str, dict, TextObject]] = None,
-        initial_time: Optional[str] = None,
-        confirm: Optional[Union[dict, ConfirmObject]] = None,
-        **others: dict,
-    ):
-        """
-        An element which allows selection of a time of day.
-        https://api.slack.com/reference/block-kit/block-elements#timepicker
-        """
-        super().__init__(
-            type=self.type,
-            action_id=action_id,
-            placeholder=TextObject.parse(placeholder, PlainTextObject.type),
-            confirm=ConfirmObject.parse(confirm),
-        )
-        show_unknown_key_warning(self, others)
-
-        self.initial_time = initial_time
-
-    @JsonValidator("initial_time attribute must be in format 'HH:mm'")
-    def _validate_initial_time_valid(self):
-        return self.initial_time is None or re.match(
-            r"([0-1][0-9]|2[0-3]):([0-5][0-9])", self.initial_time
-        )
-
-

Ancestors

- -

Class variables

-
-
var type
-
-
-
-
-

Inherited members

- -
-
-class UserMultiSelectElement -(*, action_id:ย Optional[str]ย =ย None, placeholder:ย Union[str,ย dict,ย TextObject,ย ForwardRef(None)]ย =ย None, initial_users:ย Optional[Sequence[str]]ย =ย None, confirm:ย Union[dict,ย ConfirmObject,ย ForwardRef(None)]ย =ย None, max_selected_items:ย Optional[int]ย =ย None, **others:ย dict) -
-
-

Block Elements are things that exists inside of your Blocks. -https://api.slack.com/reference/block-kit/block-elements

-

This select menu will populate its options with a list of Slack users visible to -the current user in the active workspace. -https://api.slack.com/reference/block-kit/block-elements#users_multi_select

-
- -Expand source code - -
class UserMultiSelectElement(InputInteractiveElement):
-    type = "multi_users_select"
-
-    @property
-    def attributes(self) -> Set[str]:
-        return super().attributes.union({"initial_users", "max_selected_items"})
-
-    def __init__(
-        self,
-        *,
-        action_id: Optional[str] = None,
-        placeholder: Optional[Union[str, dict, TextObject]] = None,
-        initial_users: Optional[Sequence[str]] = None,
-        confirm: Optional[Union[dict, ConfirmObject]] = None,
-        max_selected_items: Optional[int] = None,
-        **others: dict,
-    ):
-        """
-        This select menu will populate its options with a list of Slack users visible to
-        the current user in the active workspace.
-        https://api.slack.com/reference/block-kit/block-elements#users_multi_select
-        """
-        super().__init__(
-            type=self.type,
-            action_id=action_id,
-            placeholder=TextObject.parse(placeholder, PlainTextObject.type),
-            confirm=ConfirmObject.parse(confirm),
-        )
-        show_unknown_key_warning(self, others)
-
-        self.initial_users = initial_users
-        self.max_selected_items = max_selected_items
-
-

Ancestors

- -

Class variables

-
-
var type
-
-
-
-
-

Inherited members

- -
-
-class UserSelectElement -(*, placeholder:ย Union[str,ย dict,ย TextObject,ย ForwardRef(None)]ย =ย None, action_id:ย Optional[str]ย =ย None, initial_user:ย Optional[str]ย =ย None, confirm:ย Union[dict,ย ConfirmObject,ย ForwardRef(None)]ย =ย None, **others:ย dict) -
-
-

Block Elements are things that exists inside of your Blocks. -https://api.slack.com/reference/block-kit/block-elements

-

This select menu will populate its options with a list of Slack users visible to -the current user in the active workspace. -https://api.slack.com/reference/block-kit/block-elements#users_select

-
- -Expand source code - -
class UserSelectElement(InputInteractiveElement):
-    type = "users_select"
-
-    @property
-    def attributes(self) -> Set[str]:
-        return super().attributes.union({"initial_user"})
-
-    def __init__(
-        self,
-        *,
-        placeholder: Optional[Union[str, dict, TextObject]] = None,
-        action_id: Optional[str] = None,
-        initial_user: Optional[str] = None,
-        confirm: Optional[Union[dict, ConfirmObject]] = None,
-        **others: dict,
-    ):
-        """
-        This select menu will populate its options with a list of Slack users visible to
-        the current user in the active workspace.
-        https://api.slack.com/reference/block-kit/block-elements#users_select
-        """
-        super().__init__(
-            type=self.type,
-            action_id=action_id,
-            placeholder=TextObject.parse(placeholder, PlainTextObject.type),
-            confirm=ConfirmObject.parse(confirm),
-        )
-        show_unknown_key_warning(self, others)
-
-        self.initial_user = initial_user
-
-

Ancestors

- -

Class variables

-
-
var type
-
-
-
-
-

Inherited members

- -
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/models/blocks/blocks.html b/docs/api-docs/slack_sdk/models/blocks/blocks.html deleted file mode 100644 index 9cf335a32..000000000 --- a/docs/api-docs/slack_sdk/models/blocks/blocks.html +++ /dev/null @@ -1,1624 +0,0 @@ - - - - - - -slack_sdk.models.blocks.blocks API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.models.blocks.blocks

-
-
-
- -Expand source code - -
import copy
-import logging
-import warnings
-from typing import Dict, Sequence, Optional, Set, Union, Any, List
-
-from slack_sdk.models import show_unknown_key_warning
-from slack_sdk.models.basic_objects import (
-    JsonObject,
-    JsonValidator,
-)
-from .basic_components import MarkdownTextObject
-from .basic_components import PlainTextObject
-from .basic_components import TextObject
-from .block_elements import BlockElement
-from .block_elements import ImageElement
-from .block_elements import InputInteractiveElement
-from .block_elements import InteractiveElement
-
-
-# -------------------------------------------------
-# Base Classes
-# -------------------------------------------------
-
-
-class Block(JsonObject):
-    """Blocks are a series of components that can be combined
-    to create visually rich and compellingly interactive messages.
-    https://api.slack.com/reference/block-kit/blocks
-    """
-
-    attributes = {"block_id", "type"}
-    block_id_max_length = 255
-    logger = logging.getLogger(__name__)
-
-    def _subtype_warning(self):  # skipcq: PYL-R0201
-        warnings.warn(
-            "subtype is deprecated since slackclient 2.6.0, use type instead",
-            DeprecationWarning,
-        )
-
-    @property
-    def subtype(self) -> Optional[str]:
-        return self.type
-
-    def __init__(
-        self,
-        *,
-        type: Optional[str] = None,  # skipcq: PYL-W0622
-        subtype: Optional[str] = None,  # deprecated
-        block_id: Optional[str] = None,
-    ):
-        if subtype:
-            self._subtype_warning()
-        self.type = type if type else subtype
-        self.block_id = block_id
-        self.color = None
-
-    @JsonValidator(f"block_id cannot exceed {block_id_max_length} characters")
-    def _validate_block_id_length(self):
-        return self.block_id is None or len(self.block_id) <= self.block_id_max_length
-
-    @classmethod
-    def parse(cls, block: Union[dict, "Block"]) -> Optional["Block"]:
-        if block is None:  # skipcq: PYL-R1705
-            return None
-        elif isinstance(block, Block):
-            return block
-        else:
-            if "type" in block:
-                type = block["type"]  # skipcq: PYL-W0622
-                if type == SectionBlock.type:  # skipcq: PYL-R1705
-                    return SectionBlock(**block)
-                elif type == DividerBlock.type:
-                    return DividerBlock(**block)
-                elif type == ImageBlock.type:
-                    return ImageBlock(**block)
-                elif type == ActionsBlock.type:
-                    return ActionsBlock(**block)
-                elif type == ContextBlock.type:
-                    return ContextBlock(**block)
-                elif type == InputBlock.type:
-                    return InputBlock(**block)
-                elif type == FileBlock.type:
-                    return FileBlock(**block)
-                elif type == CallBlock.type:
-                    return CallBlock(**block)
-                elif type == HeaderBlock.type:
-                    return HeaderBlock(**block)
-                else:
-                    cls.logger.warning(f"Unknown block detected and skipped ({block})")
-                    return None
-            else:
-                cls.logger.warning(f"Unknown block detected and skipped ({block})")
-                return None
-
-    @classmethod
-    def parse_all(
-        cls, blocks: Optional[Sequence[Union[dict, "Block"]]]
-    ) -> List["Block"]:
-        return [cls.parse(b) for b in blocks or []]
-
-
-# -------------------------------------------------
-# Block Classes
-# -------------------------------------------------
-
-
-class SectionBlock(Block):
-    type = "section"
-    fields_max_length = 10
-    text_max_length = 3000
-
-    @property
-    def attributes(self) -> Set[str]:
-        return super().attributes.union({"text", "fields", "accessory"})
-
-    def __init__(
-        self,
-        *,
-        block_id: Optional[str] = None,
-        text: Union[str, dict, TextObject] = None,
-        fields: Sequence[Union[str, dict, TextObject]] = None,
-        accessory: Optional[Union[dict, BlockElement]] = None,
-        **others: dict,
-    ):
-        """A section is one of the most flexible blocks available.
-        https://api.slack.com/reference/block-kit/blocks#section
-        """
-        super().__init__(type=self.type, block_id=block_id)
-        show_unknown_key_warning(self, others)
-
-        self.text = TextObject.parse(text)
-        field_objects = []
-        for f in fields or []:
-            if isinstance(f, str):
-                field_objects.append(MarkdownTextObject.from_str(f))
-            elif isinstance(f, TextObject):
-                field_objects.append(f)
-            elif isinstance(f, dict) and "type" in f:
-                d = copy.copy(f)
-                t = d.pop("type")
-                if t == MarkdownTextObject.type:
-                    field_objects.append(MarkdownTextObject(**d))
-                else:
-                    field_objects.append(PlainTextObject(**d))
-            else:
-                self.logger.warning(f"Unsupported filed detected and skipped {f}")
-        self.fields = field_objects
-        self.accessory = BlockElement.parse(accessory)
-
-    @JsonValidator("text or fields attribute must be specified")
-    def _validate_text_or_fields_populated(self):
-        return self.text is not None or self.fields
-
-    @JsonValidator(f"fields attribute cannot exceed {fields_max_length} items")
-    def _validate_fields_length(self):
-        return self.fields is None or len(self.fields) <= self.fields_max_length
-
-    @JsonValidator(f"text attribute cannot exceed {text_max_length} characters")
-    def _validate_alt_text_length(self):
-        return self.text is None or len(self.text.text) <= self.text_max_length
-
-
-class DividerBlock(Block):
-    type = "divider"
-
-    def __init__(
-        self,
-        *,
-        block_id: Optional[str] = None,
-        **others: dict,
-    ):
-        """A content divider, like an <hr>, to split up different blocks inside of a message.
-        https://api.slack.com/reference/block-kit/blocks#divider
-        """
-        super().__init__(type=self.type, block_id=block_id)
-        show_unknown_key_warning(self, others)
-
-
-class ImageBlock(Block):
-    type = "image"
-
-    @property
-    def attributes(self) -> Set[str]:
-        return super().attributes.union({"alt_text", "image_url", "title"})
-
-    image_url_max_length = 3000
-    alt_text_max_length = 2000
-    title_max_length = 2000
-
-    def __init__(
-        self,
-        *,
-        image_url: str,
-        alt_text: str,
-        title: Optional[Union[str, dict, TextObject]] = None,
-        block_id: Optional[str] = None,
-        **others: dict,
-    ):
-        """A simple image block, designed to make those cat photos really pop.
-        https://api.slack.com/reference/block-kit/blocks#image
-        """
-        super().__init__(type=self.type, block_id=block_id)
-        show_unknown_key_warning(self, others)
-
-        self.image_url = image_url
-        self.alt_text = alt_text
-        self.title = TextObject.parse(title)
-
-    @JsonValidator(
-        f"image_url attribute cannot exceed {image_url_max_length} characters"
-    )
-    def _validate_image_url_length(self):
-        return len(self.image_url) <= self.image_url_max_length
-
-    @JsonValidator(f"alt_text attribute cannot exceed {alt_text_max_length} characters")
-    def _validate_alt_text_length(self):
-        return len(self.alt_text) <= self.alt_text_max_length
-
-    @JsonValidator(f"title attribute cannot exceed {title_max_length} characters")
-    def _validate_title_length(self):
-        return (
-            self.title is None
-            or self.title.text is None
-            or len(self.title.text) <= self.title_max_length
-        )
-
-
-class ActionsBlock(Block):
-    type = "actions"
-    elements_max_length = 5
-
-    @property
-    def attributes(self) -> Set[str]:
-        return super().attributes.union({"elements"})
-
-    def __init__(
-        self,
-        *,
-        elements: Sequence[Union[dict, InteractiveElement]],
-        block_id: Optional[str] = None,
-        **others: dict,
-    ):
-        """A block that is used to hold interactive elements.
-        https://api.slack.com/reference/block-kit/blocks#actions
-        """
-        super().__init__(type=self.type, block_id=block_id)
-        show_unknown_key_warning(self, others)
-
-        self.elements = BlockElement.parse_all(elements)
-
-    @JsonValidator(f"elements attribute cannot exceed {elements_max_length} elements")
-    def _validate_elements_length(self):
-        return self.elements is None or len(self.elements) <= self.elements_max_length
-
-
-class ContextBlock(Block):
-    type = "context"
-    elements_max_length = 10
-
-    @property
-    def attributes(self) -> Set[str]:
-        return super().attributes.union({"elements"})
-
-    def __init__(
-        self,
-        *,
-        elements: Sequence[Union[dict, ImageElement, TextObject]],
-        block_id: Optional[str] = None,
-        **others: dict,
-    ):
-        """Displays message context, which can include both images and text.
-        https://api.slack.com/reference/block-kit/blocks#context
-        """
-        super().__init__(type=self.type, block_id=block_id)
-        show_unknown_key_warning(self, others)
-
-        self.elements = BlockElement.parse_all(elements)
-
-    @JsonValidator(f"elements attribute cannot exceed {elements_max_length} elements")
-    def _validate_elements_length(self):
-        return self.elements is None or len(self.elements) <= self.elements_max_length
-
-
-class InputBlock(Block):
-    type = "input"
-    label_max_length = 2000
-    hint_max_length = 2000
-
-    @property
-    def attributes(self) -> Set[str]:
-        return super().attributes.union(
-            {"label", "hint", "element", "optional", "dispatch_action"}
-        )
-
-    def __init__(
-        self,
-        *,
-        label: Union[str, dict, PlainTextObject],
-        element: Union[str, dict, InputInteractiveElement],
-        block_id: Optional[str] = None,
-        hint: Optional[Union[str, dict, PlainTextObject]] = None,
-        dispatch_action: Optional[bool] = None,
-        optional: Optional[bool] = None,
-        **others: dict,
-    ):
-        """A block that collects information from users - it can hold a plain-text input element,
-        a select menu element, a multi-select menu element, or a datepicker.
-        https://api.slack.com/reference/block-kit/blocks#input
-        """
-        super().__init__(type=self.type, block_id=block_id)
-        show_unknown_key_warning(self, others)
-
-        self.label = TextObject.parse(label, default_type=PlainTextObject.type)
-        self.element = BlockElement.parse(element)
-        self.hint = TextObject.parse(hint, default_type=PlainTextObject.type)
-        self.dispatch_action = dispatch_action
-        self.optional = optional
-
-    @JsonValidator(f"label attribute cannot exceed {label_max_length} characters")
-    def _validate_label_length(self):
-        return (
-            self.label is None
-            or self.label.text is None
-            or len(self.label.text) <= self.label_max_length
-        )
-
-    @JsonValidator(f"hint attribute cannot exceed {hint_max_length} characters")
-    def _validate_hint_length(self):
-        return (
-            self.hint is None
-            or self.hint.text is None
-            or len(self.hint.text) <= self.label_max_length
-        )
-
-    @JsonValidator(
-        (
-            "element attribute must be a string, select element, multi-select element, "
-            "or a datepicker. (Sub-classes of InputInteractiveElement)"
-        )
-    )
-    def _validate_element_type(self):
-        return self.element is None or isinstance(
-            self.element, (str, InputInteractiveElement)
-        )
-
-
-class FileBlock(Block):
-    type = "file"
-
-    @property
-    def attributes(self) -> Set[str]:
-        return super().attributes.union({"external_id", "source"})
-
-    def __init__(
-        self,
-        *,
-        external_id: str,
-        source: str = "remote",
-        block_id: Optional[str] = None,
-        **others: dict,
-    ):
-        """Displays a remote file.
-        https://api.slack.com/reference/block-kit/blocks#file
-        """
-        super().__init__(type=self.type, block_id=block_id)
-        show_unknown_key_warning(self, others)
-
-        self.external_id = external_id
-        self.source = source
-
-
-class CallBlock(Block):
-    type = "call"
-
-    @property
-    def attributes(self) -> Set[str]:
-        return super().attributes.union({"call_id", "api_decoration_available", "call"})
-
-    def __init__(
-        self,
-        *,
-        call_id: str,
-        api_decoration_available: Optional[bool] = None,
-        call: Optional[Dict[str, Dict[str, Any]]] = None,
-        block_id: Optional[str] = None,
-        **others: dict,
-    ):
-        """Displays a call information
-        https://api.slack.com/reference/block-kit/blocks#call
-        """
-        super().__init__(type=self.type, block_id=block_id)
-        show_unknown_key_warning(self, others)
-
-        self.call_id = call_id
-        self.api_decoration_available = api_decoration_available
-        self.call = call
-
-
-class HeaderBlock(Block):
-    type = "header"
-    text_max_length = 150
-
-    @property
-    def attributes(self) -> Set[str]:
-        return super().attributes.union({"text"})
-
-    def __init__(
-        self,
-        *,
-        block_id: Optional[str] = None,
-        text: Union[str, dict, TextObject] = None,
-        **others: dict,
-    ):
-        """A header is a plain-text block that displays in a larger, bold font.
-        https://api.slack.com/reference/block-kit/blocks#header
-        """
-        super().__init__(type=self.type, block_id=block_id)
-        show_unknown_key_warning(self, others)
-
-        self.text = TextObject.parse(text, default_type=PlainTextObject.type)
-
-    @JsonValidator("text attribute must be specified")
-    def _validate_text(self):
-        return self.text is not None
-
-    @JsonValidator(f"text attribute cannot exceed {text_max_length} characters")
-    def _validate_alt_text_length(self):
-        return self.text is None or len(self.text.text) <= self.text_max_length
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class ActionsBlock -(*, elements:ย Sequence[Union[dict,ย InteractiveElement]], block_id:ย Optional[str]ย =ย None, **others:ย dict) -
-
-

Blocks are a series of components that can be combined -to create visually rich and compellingly interactive messages. -https://api.slack.com/reference/block-kit/blocks

-

A block that is used to hold interactive elements. -https://api.slack.com/reference/block-kit/blocks#actions

-
- -Expand source code - -
class ActionsBlock(Block):
-    type = "actions"
-    elements_max_length = 5
-
-    @property
-    def attributes(self) -> Set[str]:
-        return super().attributes.union({"elements"})
-
-    def __init__(
-        self,
-        *,
-        elements: Sequence[Union[dict, InteractiveElement]],
-        block_id: Optional[str] = None,
-        **others: dict,
-    ):
-        """A block that is used to hold interactive elements.
-        https://api.slack.com/reference/block-kit/blocks#actions
-        """
-        super().__init__(type=self.type, block_id=block_id)
-        show_unknown_key_warning(self, others)
-
-        self.elements = BlockElement.parse_all(elements)
-
-    @JsonValidator(f"elements attribute cannot exceed {elements_max_length} elements")
-    def _validate_elements_length(self):
-        return self.elements is None or len(self.elements) <= self.elements_max_length
-
-

Ancestors

- -

Class variables

-
-
var elements_max_length
-
-
-
-
var type
-
-
-
-
-

Instance variables

-
-
var attributes :ย Set[str]
-
-

set() -> new empty set object -set(iterable) -> new set object

-

Build an unordered collection of unique elements.

-
- -Expand source code - -
@property
-def attributes(self) -> Set[str]:
-    return super().attributes.union({"elements"})
-
-
-
-

Inherited members

- -
-
-class Block -(*, type:ย Optional[str]ย =ย None, subtype:ย Optional[str]ย =ย None, block_id:ย Optional[str]ย =ย None) -
-
-

Blocks are a series of components that can be combined -to create visually rich and compellingly interactive messages. -https://api.slack.com/reference/block-kit/blocks

-
- -Expand source code - -
class Block(JsonObject):
-    """Blocks are a series of components that can be combined
-    to create visually rich and compellingly interactive messages.
-    https://api.slack.com/reference/block-kit/blocks
-    """
-
-    attributes = {"block_id", "type"}
-    block_id_max_length = 255
-    logger = logging.getLogger(__name__)
-
-    def _subtype_warning(self):  # skipcq: PYL-R0201
-        warnings.warn(
-            "subtype is deprecated since slackclient 2.6.0, use type instead",
-            DeprecationWarning,
-        )
-
-    @property
-    def subtype(self) -> Optional[str]:
-        return self.type
-
-    def __init__(
-        self,
-        *,
-        type: Optional[str] = None,  # skipcq: PYL-W0622
-        subtype: Optional[str] = None,  # deprecated
-        block_id: Optional[str] = None,
-    ):
-        if subtype:
-            self._subtype_warning()
-        self.type = type if type else subtype
-        self.block_id = block_id
-        self.color = None
-
-    @JsonValidator(f"block_id cannot exceed {block_id_max_length} characters")
-    def _validate_block_id_length(self):
-        return self.block_id is None or len(self.block_id) <= self.block_id_max_length
-
-    @classmethod
-    def parse(cls, block: Union[dict, "Block"]) -> Optional["Block"]:
-        if block is None:  # skipcq: PYL-R1705
-            return None
-        elif isinstance(block, Block):
-            return block
-        else:
-            if "type" in block:
-                type = block["type"]  # skipcq: PYL-W0622
-                if type == SectionBlock.type:  # skipcq: PYL-R1705
-                    return SectionBlock(**block)
-                elif type == DividerBlock.type:
-                    return DividerBlock(**block)
-                elif type == ImageBlock.type:
-                    return ImageBlock(**block)
-                elif type == ActionsBlock.type:
-                    return ActionsBlock(**block)
-                elif type == ContextBlock.type:
-                    return ContextBlock(**block)
-                elif type == InputBlock.type:
-                    return InputBlock(**block)
-                elif type == FileBlock.type:
-                    return FileBlock(**block)
-                elif type == CallBlock.type:
-                    return CallBlock(**block)
-                elif type == HeaderBlock.type:
-                    return HeaderBlock(**block)
-                else:
-                    cls.logger.warning(f"Unknown block detected and skipped ({block})")
-                    return None
-            else:
-                cls.logger.warning(f"Unknown block detected and skipped ({block})")
-                return None
-
-    @classmethod
-    def parse_all(
-        cls, blocks: Optional[Sequence[Union[dict, "Block"]]]
-    ) -> List["Block"]:
-        return [cls.parse(b) for b in blocks or []]
-
-

Ancestors

- -

Subclasses

- -

Class variables

-
-
var block_id_max_length
-
-
-
-
var logger
-
-
-
-
-

Static methods

-
-
-def parse(block:ย Union[dict,ย ForwardRef('Block')]) โ€‘>ย Optional[Block] -
-
-
-
- -Expand source code - -
@classmethod
-def parse(cls, block: Union[dict, "Block"]) -> Optional["Block"]:
-    if block is None:  # skipcq: PYL-R1705
-        return None
-    elif isinstance(block, Block):
-        return block
-    else:
-        if "type" in block:
-            type = block["type"]  # skipcq: PYL-W0622
-            if type == SectionBlock.type:  # skipcq: PYL-R1705
-                return SectionBlock(**block)
-            elif type == DividerBlock.type:
-                return DividerBlock(**block)
-            elif type == ImageBlock.type:
-                return ImageBlock(**block)
-            elif type == ActionsBlock.type:
-                return ActionsBlock(**block)
-            elif type == ContextBlock.type:
-                return ContextBlock(**block)
-            elif type == InputBlock.type:
-                return InputBlock(**block)
-            elif type == FileBlock.type:
-                return FileBlock(**block)
-            elif type == CallBlock.type:
-                return CallBlock(**block)
-            elif type == HeaderBlock.type:
-                return HeaderBlock(**block)
-            else:
-                cls.logger.warning(f"Unknown block detected and skipped ({block})")
-                return None
-        else:
-            cls.logger.warning(f"Unknown block detected and skipped ({block})")
-            return None
-
-
-
-def parse_all(blocks:ย Optional[Sequence[Union[dict,ย ForwardRef('Block')]]]) โ€‘>ย List[Block] -
-
-
-
- -Expand source code - -
@classmethod
-def parse_all(
-    cls, blocks: Optional[Sequence[Union[dict, "Block"]]]
-) -> List["Block"]:
-    return [cls.parse(b) for b in blocks or []]
-
-
-
-

Instance variables

-
-
var subtype :ย Optional[str]
-
-
-
- -Expand source code - -
@property
-def subtype(self) -> Optional[str]:
-    return self.type
-
-
-
-

Inherited members

- -
-
-class CallBlock -(*, call_id:ย str, api_decoration_available:ย Optional[bool]ย =ย None, call:ย Optional[Dict[str,ย Dict[str,ย Any]]]ย =ย None, block_id:ย Optional[str]ย =ย None, **others:ย dict) -
-
-

Blocks are a series of components that can be combined -to create visually rich and compellingly interactive messages. -https://api.slack.com/reference/block-kit/blocks

-

Displays a call information -https://api.slack.com/reference/block-kit/blocks#call

-
- -Expand source code - -
class CallBlock(Block):
-    type = "call"
-
-    @property
-    def attributes(self) -> Set[str]:
-        return super().attributes.union({"call_id", "api_decoration_available", "call"})
-
-    def __init__(
-        self,
-        *,
-        call_id: str,
-        api_decoration_available: Optional[bool] = None,
-        call: Optional[Dict[str, Dict[str, Any]]] = None,
-        block_id: Optional[str] = None,
-        **others: dict,
-    ):
-        """Displays a call information
-        https://api.slack.com/reference/block-kit/blocks#call
-        """
-        super().__init__(type=self.type, block_id=block_id)
-        show_unknown_key_warning(self, others)
-
-        self.call_id = call_id
-        self.api_decoration_available = api_decoration_available
-        self.call = call
-
-

Ancestors

- -

Class variables

-
-
var type
-
-
-
-
-

Instance variables

-
-
var attributes :ย Set[str]
-
-

set() -> new empty set object -set(iterable) -> new set object

-

Build an unordered collection of unique elements.

-
- -Expand source code - -
@property
-def attributes(self) -> Set[str]:
-    return super().attributes.union({"call_id", "api_decoration_available", "call"})
-
-
-
-

Inherited members

- -
-
-class ContextBlock -(*, elements:ย Sequence[Union[dict,ย ImageElement,ย TextObject]], block_id:ย Optional[str]ย =ย None, **others:ย dict) -
-
-

Blocks are a series of components that can be combined -to create visually rich and compellingly interactive messages. -https://api.slack.com/reference/block-kit/blocks

-

Displays message context, which can include both images and text. -https://api.slack.com/reference/block-kit/blocks#context

-
- -Expand source code - -
class ContextBlock(Block):
-    type = "context"
-    elements_max_length = 10
-
-    @property
-    def attributes(self) -> Set[str]:
-        return super().attributes.union({"elements"})
-
-    def __init__(
-        self,
-        *,
-        elements: Sequence[Union[dict, ImageElement, TextObject]],
-        block_id: Optional[str] = None,
-        **others: dict,
-    ):
-        """Displays message context, which can include both images and text.
-        https://api.slack.com/reference/block-kit/blocks#context
-        """
-        super().__init__(type=self.type, block_id=block_id)
-        show_unknown_key_warning(self, others)
-
-        self.elements = BlockElement.parse_all(elements)
-
-    @JsonValidator(f"elements attribute cannot exceed {elements_max_length} elements")
-    def _validate_elements_length(self):
-        return self.elements is None or len(self.elements) <= self.elements_max_length
-
-

Ancestors

- -

Class variables

-
-
var elements_max_length
-
-
-
-
var type
-
-
-
-
-

Instance variables

-
-
var attributes :ย Set[str]
-
-

set() -> new empty set object -set(iterable) -> new set object

-

Build an unordered collection of unique elements.

-
- -Expand source code - -
@property
-def attributes(self) -> Set[str]:
-    return super().attributes.union({"elements"})
-
-
-
-

Inherited members

- -
-
-class DividerBlock -(*, block_id:ย Optional[str]ย =ย None, **others:ย dict) -
-
-

Blocks are a series of components that can be combined -to create visually rich and compellingly interactive messages. -https://api.slack.com/reference/block-kit/blocks

-

A content divider, like an


, to split up different blocks inside of a message. -https://api.slack.com/reference/block-kit/blocks#divider

-
- -Expand source code - -
class DividerBlock(Block):
-    type = "divider"
-
-    def __init__(
-        self,
-        *,
-        block_id: Optional[str] = None,
-        **others: dict,
-    ):
-        """A content divider, like an <hr>, to split up different blocks inside of a message.
-        https://api.slack.com/reference/block-kit/blocks#divider
-        """
-        super().__init__(type=self.type, block_id=block_id)
-        show_unknown_key_warning(self, others)
-
-

Ancestors

- -

Class variables

-
-
var type
-
-
-
-
-

Inherited members

- -
-
-class FileBlock -(*, external_id:ย str, source:ย strย =ย 'remote', block_id:ย Optional[str]ย =ย None, **others:ย dict) -
-
-

Blocks are a series of components that can be combined -to create visually rich and compellingly interactive messages. -https://api.slack.com/reference/block-kit/blocks

-

Displays a remote file. -https://api.slack.com/reference/block-kit/blocks#file

-
- -Expand source code - -
class FileBlock(Block):
-    type = "file"
-
-    @property
-    def attributes(self) -> Set[str]:
-        return super().attributes.union({"external_id", "source"})
-
-    def __init__(
-        self,
-        *,
-        external_id: str,
-        source: str = "remote",
-        block_id: Optional[str] = None,
-        **others: dict,
-    ):
-        """Displays a remote file.
-        https://api.slack.com/reference/block-kit/blocks#file
-        """
-        super().__init__(type=self.type, block_id=block_id)
-        show_unknown_key_warning(self, others)
-
-        self.external_id = external_id
-        self.source = source
-
-

Ancestors

- -

Class variables

-
-
var type
-
-
-
-
-

Instance variables

-
-
var attributes :ย Set[str]
-
-

set() -> new empty set object -set(iterable) -> new set object

-

Build an unordered collection of unique elements.

-
- -Expand source code - -
@property
-def attributes(self) -> Set[str]:
-    return super().attributes.union({"external_id", "source"})
-
-
-
-

Inherited members

- -
-
-class HeaderBlock -(*, block_id:ย Optional[str]ย =ย None, text:ย Union[str,ย dict,ย TextObject]ย =ย None, **others:ย dict) -
-
-

Blocks are a series of components that can be combined -to create visually rich and compellingly interactive messages. -https://api.slack.com/reference/block-kit/blocks

-

A header is a plain-text block that displays in a larger, bold font. -https://api.slack.com/reference/block-kit/blocks#header

-
- -Expand source code - -
class HeaderBlock(Block):
-    type = "header"
-    text_max_length = 150
-
-    @property
-    def attributes(self) -> Set[str]:
-        return super().attributes.union({"text"})
-
-    def __init__(
-        self,
-        *,
-        block_id: Optional[str] = None,
-        text: Union[str, dict, TextObject] = None,
-        **others: dict,
-    ):
-        """A header is a plain-text block that displays in a larger, bold font.
-        https://api.slack.com/reference/block-kit/blocks#header
-        """
-        super().__init__(type=self.type, block_id=block_id)
-        show_unknown_key_warning(self, others)
-
-        self.text = TextObject.parse(text, default_type=PlainTextObject.type)
-
-    @JsonValidator("text attribute must be specified")
-    def _validate_text(self):
-        return self.text is not None
-
-    @JsonValidator(f"text attribute cannot exceed {text_max_length} characters")
-    def _validate_alt_text_length(self):
-        return self.text is None or len(self.text.text) <= self.text_max_length
-
-

Ancestors

- -

Class variables

-
-
var text_max_length
-
-
-
-
var type
-
-
-
-
-

Instance variables

-
-
var attributes :ย Set[str]
-
-

set() -> new empty set object -set(iterable) -> new set object

-

Build an unordered collection of unique elements.

-
- -Expand source code - -
@property
-def attributes(self) -> Set[str]:
-    return super().attributes.union({"text"})
-
-
-
-

Inherited members

- -
-
-class ImageBlock -(*, image_url:ย str, alt_text:ย str, title:ย Union[str,ย dict,ย TextObject,ย ForwardRef(None)]ย =ย None, block_id:ย Optional[str]ย =ย None, **others:ย dict) -
-
-

Blocks are a series of components that can be combined -to create visually rich and compellingly interactive messages. -https://api.slack.com/reference/block-kit/blocks

-

A simple image block, designed to make those cat photos really pop. -https://api.slack.com/reference/block-kit/blocks#image

-
- -Expand source code - -
class ImageBlock(Block):
-    type = "image"
-
-    @property
-    def attributes(self) -> Set[str]:
-        return super().attributes.union({"alt_text", "image_url", "title"})
-
-    image_url_max_length = 3000
-    alt_text_max_length = 2000
-    title_max_length = 2000
-
-    def __init__(
-        self,
-        *,
-        image_url: str,
-        alt_text: str,
-        title: Optional[Union[str, dict, TextObject]] = None,
-        block_id: Optional[str] = None,
-        **others: dict,
-    ):
-        """A simple image block, designed to make those cat photos really pop.
-        https://api.slack.com/reference/block-kit/blocks#image
-        """
-        super().__init__(type=self.type, block_id=block_id)
-        show_unknown_key_warning(self, others)
-
-        self.image_url = image_url
-        self.alt_text = alt_text
-        self.title = TextObject.parse(title)
-
-    @JsonValidator(
-        f"image_url attribute cannot exceed {image_url_max_length} characters"
-    )
-    def _validate_image_url_length(self):
-        return len(self.image_url) <= self.image_url_max_length
-
-    @JsonValidator(f"alt_text attribute cannot exceed {alt_text_max_length} characters")
-    def _validate_alt_text_length(self):
-        return len(self.alt_text) <= self.alt_text_max_length
-
-    @JsonValidator(f"title attribute cannot exceed {title_max_length} characters")
-    def _validate_title_length(self):
-        return (
-            self.title is None
-            or self.title.text is None
-            or len(self.title.text) <= self.title_max_length
-        )
-
-

Ancestors

- -

Class variables

-
-
var alt_text_max_length
-
-
-
-
var image_url_max_length
-
-
-
-
var title_max_length
-
-
-
-
var type
-
-
-
-
-

Instance variables

-
-
var attributes :ย Set[str]
-
-

set() -> new empty set object -set(iterable) -> new set object

-

Build an unordered collection of unique elements.

-
- -Expand source code - -
@property
-def attributes(self) -> Set[str]:
-    return super().attributes.union({"alt_text", "image_url", "title"})
-
-
-
-

Inherited members

- -
-
-class InputBlock -(*, label:ย Union[str,ย dict,ย PlainTextObject], element:ย Union[str,ย dict,ย InputInteractiveElement], block_id:ย Optional[str]ย =ย None, hint:ย Union[str,ย dict,ย PlainTextObject,ย ForwardRef(None)]ย =ย None, dispatch_action:ย Optional[bool]ย =ย None, optional:ย Optional[bool]ย =ย None, **others:ย dict) -
-
-

Blocks are a series of components that can be combined -to create visually rich and compellingly interactive messages. -https://api.slack.com/reference/block-kit/blocks

-

A block that collects information from users - it can hold a plain-text input element, -a select menu element, a multi-select menu element, or a datepicker. -https://api.slack.com/reference/block-kit/blocks#input

-
- -Expand source code - -
class InputBlock(Block):
-    type = "input"
-    label_max_length = 2000
-    hint_max_length = 2000
-
-    @property
-    def attributes(self) -> Set[str]:
-        return super().attributes.union(
-            {"label", "hint", "element", "optional", "dispatch_action"}
-        )
-
-    def __init__(
-        self,
-        *,
-        label: Union[str, dict, PlainTextObject],
-        element: Union[str, dict, InputInteractiveElement],
-        block_id: Optional[str] = None,
-        hint: Optional[Union[str, dict, PlainTextObject]] = None,
-        dispatch_action: Optional[bool] = None,
-        optional: Optional[bool] = None,
-        **others: dict,
-    ):
-        """A block that collects information from users - it can hold a plain-text input element,
-        a select menu element, a multi-select menu element, or a datepicker.
-        https://api.slack.com/reference/block-kit/blocks#input
-        """
-        super().__init__(type=self.type, block_id=block_id)
-        show_unknown_key_warning(self, others)
-
-        self.label = TextObject.parse(label, default_type=PlainTextObject.type)
-        self.element = BlockElement.parse(element)
-        self.hint = TextObject.parse(hint, default_type=PlainTextObject.type)
-        self.dispatch_action = dispatch_action
-        self.optional = optional
-
-    @JsonValidator(f"label attribute cannot exceed {label_max_length} characters")
-    def _validate_label_length(self):
-        return (
-            self.label is None
-            or self.label.text is None
-            or len(self.label.text) <= self.label_max_length
-        )
-
-    @JsonValidator(f"hint attribute cannot exceed {hint_max_length} characters")
-    def _validate_hint_length(self):
-        return (
-            self.hint is None
-            or self.hint.text is None
-            or len(self.hint.text) <= self.label_max_length
-        )
-
-    @JsonValidator(
-        (
-            "element attribute must be a string, select element, multi-select element, "
-            "or a datepicker. (Sub-classes of InputInteractiveElement)"
-        )
-    )
-    def _validate_element_type(self):
-        return self.element is None or isinstance(
-            self.element, (str, InputInteractiveElement)
-        )
-
-

Ancestors

- -

Class variables

-
-
var hint_max_length
-
-
-
-
var label_max_length
-
-
-
-
var type
-
-
-
-
-

Instance variables

-
-
var attributes :ย Set[str]
-
-

set() -> new empty set object -set(iterable) -> new set object

-

Build an unordered collection of unique elements.

-
- -Expand source code - -
@property
-def attributes(self) -> Set[str]:
-    return super().attributes.union(
-        {"label", "hint", "element", "optional", "dispatch_action"}
-    )
-
-
-
-

Inherited members

- -
-
-class SectionBlock -(*, block_id:ย Optional[str]ย =ย None, text:ย Union[str,ย dict,ย TextObject]ย =ย None, fields:ย Sequence[Union[str,ย dict,ย TextObject]]ย =ย None, accessory:ย Union[dict,ย BlockElement,ย ForwardRef(None)]ย =ย None, **others:ย dict) -
-
-

Blocks are a series of components that can be combined -to create visually rich and compellingly interactive messages. -https://api.slack.com/reference/block-kit/blocks

-

A section is one of the most flexible blocks available. -https://api.slack.com/reference/block-kit/blocks#section

-
- -Expand source code - -
class SectionBlock(Block):
-    type = "section"
-    fields_max_length = 10
-    text_max_length = 3000
-
-    @property
-    def attributes(self) -> Set[str]:
-        return super().attributes.union({"text", "fields", "accessory"})
-
-    def __init__(
-        self,
-        *,
-        block_id: Optional[str] = None,
-        text: Union[str, dict, TextObject] = None,
-        fields: Sequence[Union[str, dict, TextObject]] = None,
-        accessory: Optional[Union[dict, BlockElement]] = None,
-        **others: dict,
-    ):
-        """A section is one of the most flexible blocks available.
-        https://api.slack.com/reference/block-kit/blocks#section
-        """
-        super().__init__(type=self.type, block_id=block_id)
-        show_unknown_key_warning(self, others)
-
-        self.text = TextObject.parse(text)
-        field_objects = []
-        for f in fields or []:
-            if isinstance(f, str):
-                field_objects.append(MarkdownTextObject.from_str(f))
-            elif isinstance(f, TextObject):
-                field_objects.append(f)
-            elif isinstance(f, dict) and "type" in f:
-                d = copy.copy(f)
-                t = d.pop("type")
-                if t == MarkdownTextObject.type:
-                    field_objects.append(MarkdownTextObject(**d))
-                else:
-                    field_objects.append(PlainTextObject(**d))
-            else:
-                self.logger.warning(f"Unsupported filed detected and skipped {f}")
-        self.fields = field_objects
-        self.accessory = BlockElement.parse(accessory)
-
-    @JsonValidator("text or fields attribute must be specified")
-    def _validate_text_or_fields_populated(self):
-        return self.text is not None or self.fields
-
-    @JsonValidator(f"fields attribute cannot exceed {fields_max_length} items")
-    def _validate_fields_length(self):
-        return self.fields is None or len(self.fields) <= self.fields_max_length
-
-    @JsonValidator(f"text attribute cannot exceed {text_max_length} characters")
-    def _validate_alt_text_length(self):
-        return self.text is None or len(self.text.text) <= self.text_max_length
-
-

Ancestors

- -

Class variables

-
-
var fields_max_length
-
-
-
-
var text_max_length
-
-
-
-
var type
-
-
-
-
-

Instance variables

-
-
var attributes :ย Set[str]
-
-

set() -> new empty set object -set(iterable) -> new set object

-

Build an unordered collection of unique elements.

-
- -Expand source code - -
@property
-def attributes(self) -> Set[str]:
-    return super().attributes.union({"text", "fields", "accessory"})
-
-
-
-

Inherited members

- -
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/models/blocks/index.html b/docs/api-docs/slack_sdk/models/blocks/index.html deleted file mode 100644 index 6248cef5a..000000000 --- a/docs/api-docs/slack_sdk/models/blocks/index.html +++ /dev/null @@ -1,137 +0,0 @@ - - - - - - -slack_sdk.models.blocks API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.models.blocks

-
-
-

Block Kit data model objects

-

To learn more about Block Kit, please check the following resources and tools:

- -
- -Expand source code - -
"""Block Kit data model objects
-
-To learn more about Block Kit, please check the following resources and tools:
-
-* https://api.slack.com/block-kit
-* https://api.slack.com/reference/block-kit/blocks
-* https://app.slack.com/block-kit-builder
-"""
-from .basic_components import ButtonStyles  # noqa
-from .basic_components import ConfirmObject  # noqa
-from .basic_components import DynamicSelectElementTypes  # noqa
-from .basic_components import MarkdownTextObject  # noqa
-from .basic_components import Option  # noqa
-from .basic_components import OptionGroup  # noqa
-from .basic_components import PlainTextObject  # noqa
-from .basic_components import TextObject  # noqa
-from .block_elements import BlockElement  # noqa
-from .block_elements import ButtonElement  # noqa
-from .block_elements import ChannelMultiSelectElement  # noqa
-from .block_elements import ChannelSelectElement  # noqa
-from .block_elements import CheckboxesElement  # noqa
-from .block_elements import ConversationFilter  # noqa
-from .block_elements import ConversationMultiSelectElement  # noqa
-from .block_elements import ConversationSelectElement  # noqa
-from .block_elements import DatePickerElement  # noqa
-from .block_elements import TimePickerElement  # noqa
-from .block_elements import ExternalDataMultiSelectElement  # noqa
-from .block_elements import ExternalDataSelectElement  # noqa
-from .block_elements import ImageElement  # noqa
-from .block_elements import InputInteractiveElement  # noqa
-from .block_elements import InteractiveElement  # noqa
-from .block_elements import LinkButtonElement  # noqa
-from .block_elements import OverflowMenuElement  # noqa
-from .block_elements import PlainTextInputElement  # noqa
-from .block_elements import RadioButtonsElement  # noqa
-from .block_elements import SelectElement  # noqa
-from .block_elements import StaticMultiSelectElement  # noqa
-from .block_elements import StaticSelectElement  # noqa
-from .block_elements import UserMultiSelectElement  # noqa
-from .block_elements import UserSelectElement  # noqa
-from .blocks import ActionsBlock  # noqa
-from .blocks import Block  # noqa
-from .blocks import CallBlock  # noqa
-from .blocks import ContextBlock  # noqa
-from .blocks import DividerBlock  # noqa
-from .blocks import FileBlock  # noqa
-from .blocks import HeaderBlock  # noqa
-from .blocks import ImageBlock  # noqa
-from .blocks import InputBlock  # noqa
-from .blocks import SectionBlock  # noqa
-
-
-
-

Sub-modules

-
-
slack_sdk.models.blocks.basic_components
-
-
-
-
slack_sdk.models.blocks.block_elements
-
-
-
-
slack_sdk.models.blocks.blocks
-
-
-
-
-
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/models/dialoags.html b/docs/api-docs/slack_sdk/models/dialoags.html deleted file mode 100644 index 1f523ddef..000000000 --- a/docs/api-docs/slack_sdk/models/dialoags.html +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - -slack_sdk.models.dialoags API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.models.dialoags

-
-
-
- -Expand source code - -
from slack_sdk.models.dialogs import AbstractDialogSelector  # noqa
-from slack_sdk.models.dialogs import DialogChannelSelector  # noqa
-from slack_sdk.models.dialogs import DialogConversationSelector  # noqa
-from slack_sdk.models.dialogs import DialogExternalSelector  # noqa
-from slack_sdk.models.dialogs import DialogStaticSelector  # noqa
-from slack_sdk.models.dialogs import DialogTextArea  # noqa
-from slack_sdk.models.dialogs import DialogTextComponent  # noqa
-from slack_sdk.models.dialogs import DialogTextField  # noqa
-from slack_sdk.models.dialogs import DialogUserSelector  # noqa
-from slack_sdk.models.dialogs import TextElementSubtypes  # noqa
-from slack_sdk.models.dialogs import DialogBuilder  # noqa
-
-from slack import deprecation
-
-deprecation.show_message(__name__, "slack_sdk.models.dialogs")
-
-
-
-
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/models/index.html b/docs/api-docs/slack_sdk/models/index.html deleted file mode 100644 index ec635c990..000000000 --- a/docs/api-docs/slack_sdk/models/index.html +++ /dev/null @@ -1,230 +0,0 @@ - - - - - - -slack_sdk.models API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.models

-
-
-

Classes for constructing Slack-specific data structure

-
- -Expand source code - -
"""Classes for constructing Slack-specific data structure"""
-
-import logging
-from typing import Union, Dict, Any, Sequence, List
-
-from .basic_objects import BaseObject  # noqa
-from .basic_objects import EnumValidator  # noqa
-from .basic_objects import JsonObject  # noqa
-from .basic_objects import JsonValidator  # noqa
-
-
-# NOTE: used only for legacy components - don't use this for Block Kit
-def extract_json(
-    item_or_items: Union[JsonObject, Sequence[JsonObject]], *format_args
-) -> Union[Dict[Any, Any], List[Dict[Any, Any]]]:
-    """
-    Given a sequence (or single item), attempt to call the to_dict() method on each
-    item and return a plain list. If item is not the expected type, return it
-    unmodified, in case it's already a plain dict or some other user created class.
-
-    Args:
-      item_or_items: item(s) to go through
-      format_args: Any formatting specifiers to pass into the object's to_dict
-            method
-    """
-    try:
-        return [
-            elem.to_dict(*format_args) if isinstance(elem, JsonObject) else elem
-            for elem in item_or_items
-        ]
-    except TypeError:  # not iterable, so try returning it as a single item
-        return (
-            item_or_items.to_dict(*format_args)
-            if isinstance(item_or_items, JsonObject)
-            else item_or_items
-        )
-
-
-def show_unknown_key_warning(name: Union[str, object], others: dict):
-    if "type" in others:
-        others.pop("type")
-    if len(others) > 0:
-        keys = ", ".join(others.keys())
-        logger = logging.getLogger(__name__)
-        if isinstance(name, object):
-            name = name.__class__.__name__
-        logger.debug(
-            f"!!! {name}'s constructor args ({keys}) were ignored."
-            f"If they should be supported by this library, report this issue to the project :bow: "
-            f"https://github.com/slackapi/python-slack-sdk/issues"
-        )
-
-
-
-

Sub-modules

-
-
slack_sdk.models.attachments
-
-
-
-
slack_sdk.models.basic_objects
-
-
-
-
slack_sdk.models.blocks
-
-

Block Kit data model objects โ€ฆ

-
-
slack_sdk.models.dialoags
-
-
-
-
slack_sdk.models.dialogs
-
-
-
-
slack_sdk.models.messages
-
-
-
-
slack_sdk.models.views
-
-
-
-
-
-
-
-
-

Functions

-
-
-def extract_json(item_or_items:ย Union[JsonObject,ย Sequence[JsonObject]], *format_args) โ€‘>ย Union[Dict[Any,ย Any],ย List[Dict[Any,ย Any]]] -
-
-

Given a sequence (or single item), attempt to call the to_dict() method on each -item and return a plain list. If item is not the expected type, return it -unmodified, in case it's already a plain dict or some other user created class.

-

Args

-
-
item_or_items
-
item(s) to go through
-
format_args
-
Any formatting specifiers to pass into the object's to_dict -method
-
-
- -Expand source code - -
def extract_json(
-    item_or_items: Union[JsonObject, Sequence[JsonObject]], *format_args
-) -> Union[Dict[Any, Any], List[Dict[Any, Any]]]:
-    """
-    Given a sequence (or single item), attempt to call the to_dict() method on each
-    item and return a plain list. If item is not the expected type, return it
-    unmodified, in case it's already a plain dict or some other user created class.
-
-    Args:
-      item_or_items: item(s) to go through
-      format_args: Any formatting specifiers to pass into the object's to_dict
-            method
-    """
-    try:
-        return [
-            elem.to_dict(*format_args) if isinstance(elem, JsonObject) else elem
-            for elem in item_or_items
-        ]
-    except TypeError:  # not iterable, so try returning it as a single item
-        return (
-            item_or_items.to_dict(*format_args)
-            if isinstance(item_or_items, JsonObject)
-            else item_or_items
-        )
-
-
-
-def show_unknown_key_warning(name:ย Union[str,ย object], others:ย dict) -
-
-
-
- -Expand source code - -
def show_unknown_key_warning(name: Union[str, object], others: dict):
-    if "type" in others:
-        others.pop("type")
-    if len(others) > 0:
-        keys = ", ".join(others.keys())
-        logger = logging.getLogger(__name__)
-        if isinstance(name, object):
-            name = name.__class__.__name__
-        logger.debug(
-            f"!!! {name}'s constructor args ({keys}) were ignored."
-            f"If they should be supported by this library, report this issue to the project :bow: "
-            f"https://github.com/slackapi/python-slack-sdk/issues"
-        )
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/models/messages/message.html b/docs/api-docs/slack_sdk/models/messages/message.html deleted file mode 100644 index 2e6f6d8b5..000000000 --- a/docs/api-docs/slack_sdk/models/messages/message.html +++ /dev/null @@ -1,297 +0,0 @@ - - - - - - -slack_sdk.models.messages.message API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.models.messages.message

-
-
-
- -Expand source code - -
import logging
-import os
-import warnings
-from typing import Optional, Sequence
-
-from slack_sdk.models import extract_json
-from slack_sdk.models.attachments import Attachment
-from slack_sdk.models.basic_objects import (
-    JsonObject,
-    JsonValidator,
-)
-from slack_sdk.models.blocks import Block
-
-LOGGER = logging.getLogger(__name__)
-
-skip_warn = os.environ.get("SLACKCLIENT_SKIP_DEPRECATION")  # for unit tests etc.
-if not skip_warn:
-    message = (
-        "This class is no longer actively maintained. "
-        "Please use a dict object for building message data instead."
-    )
-    warnings.warn(message)
-
-
-class Message(JsonObject):
-    attributes = {"text"}
-
-    attachments_max_length = 100
-
-    def __init__(
-        self,
-        *,
-        text: str,
-        attachments: Optional[Sequence[Attachment]] = None,
-        blocks: Optional[Sequence[Block]] = None,
-        markdown: bool = True,
-    ):
-        """
-        Create a message.
-
-        https://api.slack.com/messaging/composing#message-structure
-
-        Args:
-            text: Plain or Slack Markdown-like text to display in the message.
-            attachments: A list of Attachment objects to display after the rest of
-                the message's content. More than 20 is not recommended, but the actual
-                limit is 100
-            blocks: A list of Block objects to attach to this message. If
-                specified, the 'text' property is ignored (more specifically, it's used
-                as a fallback on clients that can't render blocks)
-            markdown: Whether to parse markdown into formatting such as
-                bold/italics, or leave text completely unmodified.
-        """
-        self.text = text
-        self.attachments = attachments or []
-        self.blocks = blocks or []
-        self.markdown = markdown
-
-    @JsonValidator(
-        f"attachments attribute cannot exceed {attachments_max_length} items"
-    )
-    def attachments_length(self):
-        return (
-            self.attachments is None
-            or len(self.attachments) <= self.attachments_max_length
-        )
-
-    def to_dict(self) -> dict:  # skipcq: PYL-W0221
-        json = super().to_dict()
-        if len(self.text) > 40000:
-            LOGGER.error(
-                "Messages over 40,000 characters are automatically truncated by Slack"
-            )
-        # The following limitation used to be true in the past.
-        # As of Feb 2021, having both is recommended
-        # -----------------
-        # if self.text and self.blocks:
-        #     #  Slack doesn't render the text property if there are blocks, so:
-        #     LOGGER.info(q
-        #         "text attribute is treated as fallback text if blocks are attached to "
-        #         "a message - insert text as a new SectionBlock if you want it to be "
-        #         "displayed "
-        #     )
-        json["attachments"] = extract_json(self.attachments)
-        json["blocks"] = extract_json(self.blocks)
-        json["mrkdwn"] = self.markdown
-        return json
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class Message -(*, text:ย str, attachments:ย Optional[Sequence[Attachment]]ย =ย None, blocks:ย Optional[Sequence[Block]]ย =ย None, markdown:ย boolย =ย True) -
-
-

The base class for JSON serializable class objects

-

Create a message.

-

https://api.slack.com/messaging/composing#message-structure

-

Args

-
-
text
-
Plain or Slack Markdown-like text to display in the message.
-
attachments
-
A list of Attachment objects to display after the rest of -the message's content. More than 20 is not recommended, but the actual -limit is 100
-
blocks
-
A list of Block objects to attach to this message. If -specified, the 'text' property is ignored (more specifically, it's used -as a fallback on clients that can't render blocks)
-
markdown
-
Whether to parse markdown into formatting such as -bold/italics, or leave text completely unmodified.
-
-
- -Expand source code - -
class Message(JsonObject):
-    attributes = {"text"}
-
-    attachments_max_length = 100
-
-    def __init__(
-        self,
-        *,
-        text: str,
-        attachments: Optional[Sequence[Attachment]] = None,
-        blocks: Optional[Sequence[Block]] = None,
-        markdown: bool = True,
-    ):
-        """
-        Create a message.
-
-        https://api.slack.com/messaging/composing#message-structure
-
-        Args:
-            text: Plain or Slack Markdown-like text to display in the message.
-            attachments: A list of Attachment objects to display after the rest of
-                the message's content. More than 20 is not recommended, but the actual
-                limit is 100
-            blocks: A list of Block objects to attach to this message. If
-                specified, the 'text' property is ignored (more specifically, it's used
-                as a fallback on clients that can't render blocks)
-            markdown: Whether to parse markdown into formatting such as
-                bold/italics, or leave text completely unmodified.
-        """
-        self.text = text
-        self.attachments = attachments or []
-        self.blocks = blocks or []
-        self.markdown = markdown
-
-    @JsonValidator(
-        f"attachments attribute cannot exceed {attachments_max_length} items"
-    )
-    def attachments_length(self):
-        return (
-            self.attachments is None
-            or len(self.attachments) <= self.attachments_max_length
-        )
-
-    def to_dict(self) -> dict:  # skipcq: PYL-W0221
-        json = super().to_dict()
-        if len(self.text) > 40000:
-            LOGGER.error(
-                "Messages over 40,000 characters are automatically truncated by Slack"
-            )
-        # The following limitation used to be true in the past.
-        # As of Feb 2021, having both is recommended
-        # -----------------
-        # if self.text and self.blocks:
-        #     #  Slack doesn't render the text property if there are blocks, so:
-        #     LOGGER.info(q
-        #         "text attribute is treated as fallback text if blocks are attached to "
-        #         "a message - insert text as a new SectionBlock if you want it to be "
-        #         "displayed "
-        #     )
-        json["attachments"] = extract_json(self.attachments)
-        json["blocks"] = extract_json(self.blocks)
-        json["mrkdwn"] = self.markdown
-        return json
-
-

Ancestors

- -

Class variables

-
-
var attachments_max_length
-
-
-
-
-

Methods

-
-
-def attachments_length(self) -
-
-
-
- -Expand source code - -
@JsonValidator(
-    f"attachments attribute cannot exceed {attachments_max_length} items"
-)
-def attachments_length(self):
-    return (
-        self.attachments is None
-        or len(self.attachments) <= self.attachments_max_length
-    )
-
-
-
-

Inherited members

- -
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/models/views/index.html b/docs/api-docs/slack_sdk/models/views/index.html deleted file mode 100644 index 7c6a50595..000000000 --- a/docs/api-docs/slack_sdk/models/views/index.html +++ /dev/null @@ -1,689 +0,0 @@ - - - - - - -slack_sdk.models.views API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.models.views

-
-
-
- -Expand source code - -
import copy
-import logging
-from typing import Optional, Union, Dict, Sequence
-
-from slack_sdk.models.basic_objects import JsonObject, JsonValidator
-from slack_sdk.models.blocks import Block, TextObject, PlainTextObject, Option
-
-
-class View(JsonObject):
-    """View object for modals and Home tabs.
-
-    https://api.slack.com/reference/surfaces/views
-    """
-
-    types = ["modal", "home", "workflow_step"]
-
-    attributes = {
-        "type",
-        "id",
-        "callback_id",
-        "external_id",
-        "team_id",
-        "bot_id",
-        "app_id",
-        "root_view_id",
-        "previous_view_id",
-        "title",
-        "submit",
-        "close",
-        "blocks",
-        "private_metadata",
-        "state",
-        "hash",
-        "clear_on_close",
-        "notify_on_close",
-    }
-
-    def __init__(
-        self,
-        # "modal", "home", and "workflow_step"
-        type: str,  # skipcq: PYL-W0622
-        id: Optional[str] = None,  # skipcq: PYL-W0622
-        callback_id: Optional[str] = None,
-        external_id: Optional[str] = None,
-        team_id: Optional[str] = None,
-        bot_id: Optional[str] = None,
-        app_id: Optional[str] = None,
-        root_view_id: Optional[str] = None,
-        previous_view_id: Optional[str] = None,
-        title: Union[str, dict, PlainTextObject] = None,
-        submit: Optional[Union[str, dict, PlainTextObject]] = None,
-        close: Optional[Union[str, dict, PlainTextObject]] = None,
-        blocks: Optional[Sequence[Union[dict, Block]]] = None,
-        private_metadata: Optional[str] = None,
-        state: Optional[Union[dict, "ViewState"]] = None,
-        hash: Optional[str] = None,  # skipcq: PYL-W0622
-        clear_on_close: Optional[bool] = None,
-        notify_on_close: Optional[bool] = None,
-        **kwargs,
-    ):
-        self.type = type
-        self.id = id
-        self.callback_id = callback_id
-        self.external_id = external_id
-        self.team_id = team_id
-        self.bot_id = bot_id
-        self.app_id = app_id
-        self.root_view_id = root_view_id
-        self.previous_view_id = previous_view_id
-        self.title = TextObject.parse(title, default_type=PlainTextObject.type)
-        self.submit = TextObject.parse(submit, default_type=PlainTextObject.type)
-        self.close = TextObject.parse(close, default_type=PlainTextObject.type)
-        self.blocks = Block.parse_all(blocks)
-        self.private_metadata = private_metadata
-        self.state = state
-        if self.state is not None and isinstance(self.state, dict):
-            self.state = ViewState(**self.state)
-        self.hash = hash
-        self.clear_on_close = clear_on_close
-        self.notify_on_close = notify_on_close
-        self.additional_attributes = kwargs
-
-    title_max_length = 24
-    blocks_max_length = 100
-    close_max_length = 24
-    submit_max_length = 24
-    private_metadata_max_length = 3000
-    callback_id_max_length: int = 255
-
-    @JsonValidator('type must be either "modal", "home" or "workflow_step"')
-    def _validate_type(self):
-        return self.type is not None and self.type in self.types
-
-    @JsonValidator(f"title must be between 1 and {title_max_length} characters")
-    def _validate_title_length(self):
-        return self.title is None or 1 <= len(self.title.text) <= self.title_max_length
-
-    @JsonValidator(f"views must contain between 1 and {blocks_max_length} blocks")
-    def _validate_blocks_length(self):
-        return self.blocks is None or 0 < len(self.blocks) <= self.blocks_max_length
-
-    @JsonValidator("home view cannot have submit and close")
-    def _validate_home_tab_structure(self):
-        return self.type != "home" or (
-            self.type == "home" and self.close is None and self.submit is None
-        )
-
-    @JsonValidator(f"close cannot exceed {close_max_length} characters")
-    def _validate_close_length(self):
-        return self.close is None or len(self.close.text) <= self.close_max_length
-
-    @JsonValidator(f"submit cannot exceed {submit_max_length} characters")
-    def _validate_submit_length(self):
-        return self.submit is None or len(self.submit.text) <= int(
-            self.submit_max_length
-        )
-
-    @JsonValidator(
-        f"private_metadata cannot exceed {private_metadata_max_length} characters"
-    )
-    def _validate_private_metadata_max_length(self):
-        return (
-            self.private_metadata is None
-            or len(self.private_metadata) <= self.private_metadata_max_length
-        )
-
-    @JsonValidator(f"callback_id cannot exceed {callback_id_max_length} characters")
-    def _validate_callback_id_max_length(self):
-        return (
-            self.callback_id is None
-            or len(self.callback_id) <= self.callback_id_max_length
-        )
-
-    def __str__(self):
-        return str(self.get_non_null_attributes())
-
-    def __repr__(self):
-        return self.__str__()
-
-
-class ViewState(JsonObject):
-    attributes = {"values"}
-    logger = logging.getLogger(__name__)
-
-    @classmethod
-    def _show_warning_about_unknown(cls, value):
-        c = value.__class__
-        name = ".".join([c.__module__, c.__name__])
-        cls.logger.warning(
-            f"Unknown type for view.state.values detected ({name}) and ViewState skipped to add it"
-        )
-
-    def __init__(
-        self,
-        *,
-        values: Dict[str, Dict[str, Union[dict, "ViewStateValue"]]],
-    ):
-        value_objects: Dict[str, Dict[str, ViewStateValue]] = {}
-        new_state_values = copy.copy(values)
-        if isinstance(new_state_values, dict):  # just in case
-            for block_id, actions in new_state_values.items():
-                if actions is None:  # skipcq: PYL-R1724
-                    continue
-                elif isinstance(actions, dict):
-                    new_actions = copy.copy(actions)
-                    for action_id, v in actions.items():
-                        if isinstance(v, dict):
-                            d = copy.copy(v)
-                            value_object = ViewStateValue(**d)
-                        elif isinstance(v, ViewStateValue):
-                            value_object = v
-                        else:
-                            self._show_warning_about_unknown(v)
-                            continue
-                        new_actions[action_id] = value_object
-                    value_objects[block_id] = new_actions
-                else:
-                    self._show_warning_about_unknown(v)
-        self.values = value_objects
-
-    def to_dict(self, *args) -> Dict[str, Dict[str, Dict[str, dict]]]:  # type: ignore
-        self.validate_json()
-        if self.values is not None:
-            dict_values: Dict[str, Dict[str, dict]] = {}
-            for block_id, actions in self.values.items():
-                if actions:
-                    dict_value: Dict[str, dict] = {
-                        action_id: value.to_dict()  # type: ignore
-                        for action_id, value in actions.items()  # type: ignore
-                    }
-                    dict_values[block_id] = dict_value
-            return {"values": dict_values}  # type: ignore
-        else:
-            return {}
-
-
-class ViewStateValue(JsonObject):
-    attributes = {
-        "type",
-        "value",
-        "selected_date",
-        "selected_conversation",
-        "selected_channel",
-        "selected_user",
-        "selected_option",
-        "selected_conversations",
-        "selected_channels",
-        "selected_users",
-        "selected_options",
-    }
-
-    def __init__(
-        self,
-        *,
-        type: Optional[str] = None,  # skipcq: PYL-W0622
-        value: Optional[str] = None,
-        selected_date: Optional[str] = None,
-        selected_conversation: Optional[str] = None,
-        selected_channel: Optional[str] = None,
-        selected_user: Optional[str] = None,
-        selected_option: Optional[str] = None,
-        selected_conversations: Optional[Sequence[str]] = None,
-        selected_channels: Optional[Sequence[str]] = None,
-        selected_users: Optional[Sequence[str]] = None,
-        selected_options: Optional[Sequence[Union[dict, Option]]] = None,
-    ):
-        self.type = type
-        self.value = value
-        self.selected_date = selected_date
-        self.selected_conversation = selected_conversation
-        self.selected_channel = selected_channel
-        self.selected_user = selected_user
-        self.selected_option = selected_option
-        self.selected_conversations = selected_conversations
-        self.selected_channels = selected_channels
-        self.selected_users = selected_users
-
-        if isinstance(selected_options, list):
-            self.selected_options = []
-            for option in selected_options:
-                if isinstance(option, Option):
-                    self.selected_options.append(option)
-                elif isinstance(option, dict):
-                    self.selected_options.append(Option(**option))
-        else:
-            self.selected_options = selected_options
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class View -(type:ย str, id:ย Optional[str]ย =ย None, callback_id:ย Optional[str]ย =ย None, external_id:ย Optional[str]ย =ย None, team_id:ย Optional[str]ย =ย None, bot_id:ย Optional[str]ย =ย None, app_id:ย Optional[str]ย =ย None, root_view_id:ย Optional[str]ย =ย None, previous_view_id:ย Optional[str]ย =ย None, title:ย Union[str,ย dict,ย PlainTextObject]ย =ย None, submit:ย Union[str,ย dict,ย PlainTextObject,ย ForwardRef(None)]ย =ย None, close:ย Union[str,ย dict,ย PlainTextObject,ย ForwardRef(None)]ย =ย None, blocks:ย Optional[Sequence[Union[dict,ย Block]]]ย =ย None, private_metadata:ย Optional[str]ย =ย None, state:ย Union[dict,ย ForwardRef('ViewState'),ย ForwardRef(None)]ย =ย None, hash:ย Optional[str]ย =ย None, clear_on_close:ย Optional[bool]ย =ย None, notify_on_close:ย Optional[bool]ย =ย None, **kwargs) -
-
-

View object for modals and Home tabs.

-

https://api.slack.com/reference/surfaces/views

-
- -Expand source code - -
class View(JsonObject):
-    """View object for modals and Home tabs.
-
-    https://api.slack.com/reference/surfaces/views
-    """
-
-    types = ["modal", "home", "workflow_step"]
-
-    attributes = {
-        "type",
-        "id",
-        "callback_id",
-        "external_id",
-        "team_id",
-        "bot_id",
-        "app_id",
-        "root_view_id",
-        "previous_view_id",
-        "title",
-        "submit",
-        "close",
-        "blocks",
-        "private_metadata",
-        "state",
-        "hash",
-        "clear_on_close",
-        "notify_on_close",
-    }
-
-    def __init__(
-        self,
-        # "modal", "home", and "workflow_step"
-        type: str,  # skipcq: PYL-W0622
-        id: Optional[str] = None,  # skipcq: PYL-W0622
-        callback_id: Optional[str] = None,
-        external_id: Optional[str] = None,
-        team_id: Optional[str] = None,
-        bot_id: Optional[str] = None,
-        app_id: Optional[str] = None,
-        root_view_id: Optional[str] = None,
-        previous_view_id: Optional[str] = None,
-        title: Union[str, dict, PlainTextObject] = None,
-        submit: Optional[Union[str, dict, PlainTextObject]] = None,
-        close: Optional[Union[str, dict, PlainTextObject]] = None,
-        blocks: Optional[Sequence[Union[dict, Block]]] = None,
-        private_metadata: Optional[str] = None,
-        state: Optional[Union[dict, "ViewState"]] = None,
-        hash: Optional[str] = None,  # skipcq: PYL-W0622
-        clear_on_close: Optional[bool] = None,
-        notify_on_close: Optional[bool] = None,
-        **kwargs,
-    ):
-        self.type = type
-        self.id = id
-        self.callback_id = callback_id
-        self.external_id = external_id
-        self.team_id = team_id
-        self.bot_id = bot_id
-        self.app_id = app_id
-        self.root_view_id = root_view_id
-        self.previous_view_id = previous_view_id
-        self.title = TextObject.parse(title, default_type=PlainTextObject.type)
-        self.submit = TextObject.parse(submit, default_type=PlainTextObject.type)
-        self.close = TextObject.parse(close, default_type=PlainTextObject.type)
-        self.blocks = Block.parse_all(blocks)
-        self.private_metadata = private_metadata
-        self.state = state
-        if self.state is not None and isinstance(self.state, dict):
-            self.state = ViewState(**self.state)
-        self.hash = hash
-        self.clear_on_close = clear_on_close
-        self.notify_on_close = notify_on_close
-        self.additional_attributes = kwargs
-
-    title_max_length = 24
-    blocks_max_length = 100
-    close_max_length = 24
-    submit_max_length = 24
-    private_metadata_max_length = 3000
-    callback_id_max_length: int = 255
-
-    @JsonValidator('type must be either "modal", "home" or "workflow_step"')
-    def _validate_type(self):
-        return self.type is not None and self.type in self.types
-
-    @JsonValidator(f"title must be between 1 and {title_max_length} characters")
-    def _validate_title_length(self):
-        return self.title is None or 1 <= len(self.title.text) <= self.title_max_length
-
-    @JsonValidator(f"views must contain between 1 and {blocks_max_length} blocks")
-    def _validate_blocks_length(self):
-        return self.blocks is None or 0 < len(self.blocks) <= self.blocks_max_length
-
-    @JsonValidator("home view cannot have submit and close")
-    def _validate_home_tab_structure(self):
-        return self.type != "home" or (
-            self.type == "home" and self.close is None and self.submit is None
-        )
-
-    @JsonValidator(f"close cannot exceed {close_max_length} characters")
-    def _validate_close_length(self):
-        return self.close is None or len(self.close.text) <= self.close_max_length
-
-    @JsonValidator(f"submit cannot exceed {submit_max_length} characters")
-    def _validate_submit_length(self):
-        return self.submit is None or len(self.submit.text) <= int(
-            self.submit_max_length
-        )
-
-    @JsonValidator(
-        f"private_metadata cannot exceed {private_metadata_max_length} characters"
-    )
-    def _validate_private_metadata_max_length(self):
-        return (
-            self.private_metadata is None
-            or len(self.private_metadata) <= self.private_metadata_max_length
-        )
-
-    @JsonValidator(f"callback_id cannot exceed {callback_id_max_length} characters")
-    def _validate_callback_id_max_length(self):
-        return (
-            self.callback_id is None
-            or len(self.callback_id) <= self.callback_id_max_length
-        )
-
-    def __str__(self):
-        return str(self.get_non_null_attributes())
-
-    def __repr__(self):
-        return self.__str__()
-
-

Ancestors

- -

Class variables

-
-
var blocks_max_length
-
-
-
-
var callback_id_max_length :ย int
-
-
-
-
var close_max_length
-
-
-
-
var private_metadata_max_length
-
-
-
-
var submit_max_length
-
-
-
-
var title_max_length
-
-
-
-
var types
-
-
-
-
-

Inherited members

- -
-
-class ViewState -(*, values:ย Dict[str,ย Dict[str,ย Union[dict,ย ForwardRef('ViewStateValue')]]]) -
-
-

The base class for JSON serializable class objects

-
- -Expand source code - -
class ViewState(JsonObject):
-    attributes = {"values"}
-    logger = logging.getLogger(__name__)
-
-    @classmethod
-    def _show_warning_about_unknown(cls, value):
-        c = value.__class__
-        name = ".".join([c.__module__, c.__name__])
-        cls.logger.warning(
-            f"Unknown type for view.state.values detected ({name}) and ViewState skipped to add it"
-        )
-
-    def __init__(
-        self,
-        *,
-        values: Dict[str, Dict[str, Union[dict, "ViewStateValue"]]],
-    ):
-        value_objects: Dict[str, Dict[str, ViewStateValue]] = {}
-        new_state_values = copy.copy(values)
-        if isinstance(new_state_values, dict):  # just in case
-            for block_id, actions in new_state_values.items():
-                if actions is None:  # skipcq: PYL-R1724
-                    continue
-                elif isinstance(actions, dict):
-                    new_actions = copy.copy(actions)
-                    for action_id, v in actions.items():
-                        if isinstance(v, dict):
-                            d = copy.copy(v)
-                            value_object = ViewStateValue(**d)
-                        elif isinstance(v, ViewStateValue):
-                            value_object = v
-                        else:
-                            self._show_warning_about_unknown(v)
-                            continue
-                        new_actions[action_id] = value_object
-                    value_objects[block_id] = new_actions
-                else:
-                    self._show_warning_about_unknown(v)
-        self.values = value_objects
-
-    def to_dict(self, *args) -> Dict[str, Dict[str, Dict[str, dict]]]:  # type: ignore
-        self.validate_json()
-        if self.values is not None:
-            dict_values: Dict[str, Dict[str, dict]] = {}
-            for block_id, actions in self.values.items():
-                if actions:
-                    dict_value: Dict[str, dict] = {
-                        action_id: value.to_dict()  # type: ignore
-                        for action_id, value in actions.items()  # type: ignore
-                    }
-                    dict_values[block_id] = dict_value
-            return {"values": dict_values}  # type: ignore
-        else:
-            return {}
-
-

Ancestors

- -

Class variables

-
-
var logger
-
-
-
-
-

Inherited members

- -
-
-class ViewStateValue -(*, type:ย Optional[str]ย =ย None, value:ย Optional[str]ย =ย None, selected_date:ย Optional[str]ย =ย None, selected_conversation:ย Optional[str]ย =ย None, selected_channel:ย Optional[str]ย =ย None, selected_user:ย Optional[str]ย =ย None, selected_option:ย Optional[str]ย =ย None, selected_conversations:ย Optional[Sequence[str]]ย =ย None, selected_channels:ย Optional[Sequence[str]]ย =ย None, selected_users:ย Optional[Sequence[str]]ย =ย None, selected_options:ย Optional[Sequence[Union[dict,ย Option]]]ย =ย None) -
-
-

The base class for JSON serializable class objects

-
- -Expand source code - -
class ViewStateValue(JsonObject):
-    attributes = {
-        "type",
-        "value",
-        "selected_date",
-        "selected_conversation",
-        "selected_channel",
-        "selected_user",
-        "selected_option",
-        "selected_conversations",
-        "selected_channels",
-        "selected_users",
-        "selected_options",
-    }
-
-    def __init__(
-        self,
-        *,
-        type: Optional[str] = None,  # skipcq: PYL-W0622
-        value: Optional[str] = None,
-        selected_date: Optional[str] = None,
-        selected_conversation: Optional[str] = None,
-        selected_channel: Optional[str] = None,
-        selected_user: Optional[str] = None,
-        selected_option: Optional[str] = None,
-        selected_conversations: Optional[Sequence[str]] = None,
-        selected_channels: Optional[Sequence[str]] = None,
-        selected_users: Optional[Sequence[str]] = None,
-        selected_options: Optional[Sequence[Union[dict, Option]]] = None,
-    ):
-        self.type = type
-        self.value = value
-        self.selected_date = selected_date
-        self.selected_conversation = selected_conversation
-        self.selected_channel = selected_channel
-        self.selected_user = selected_user
-        self.selected_option = selected_option
-        self.selected_conversations = selected_conversations
-        self.selected_channels = selected_channels
-        self.selected_users = selected_users
-
-        if isinstance(selected_options, list):
-            self.selected_options = []
-            for option in selected_options:
-                if isinstance(option, Option):
-                    self.selected_options.append(option)
-                elif isinstance(option, dict):
-                    self.selected_options.append(Option(**option))
-        else:
-            self.selected_options = selected_options
-
-

Ancestors

- -

Inherited members

- -
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/oauth/index.html b/docs/api-docs/slack_sdk/oauth/index.html deleted file mode 100644 index 426ff56dd..000000000 --- a/docs/api-docs/slack_sdk/oauth/index.html +++ /dev/null @@ -1,107 +0,0 @@ - - - - - - -slack_sdk.oauth API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.oauth

-
-
-

Modules for implementing the Slack OAuth flow

-

https://slack.dev/python-slack-sdk/oauth/

-
- -Expand source code - -
"""Modules for implementing the Slack OAuth flow
-
-https://slack.dev/python-slack-sdk/oauth/
-"""
-from .authorize_url_generator import AuthorizeUrlGenerator  # noqa
-from .authorize_url_generator import OpenIDConnectAuthorizeUrlGenerator  # noqa
-from .installation_store import InstallationStore  # noqa
-from .redirect_uri_page_renderer import RedirectUriPageRenderer  # noqa
-from .state_store import OAuthStateStore  # noqa
-from .state_utils import OAuthStateUtils  # noqa
-
-
-
-

Sub-modules

-
-
slack_sdk.oauth.authorize_url_generator
-
-
-
-
slack_sdk.oauth.installation_store
-
-
-
-
slack_sdk.oauth.redirect_uri_page_renderer
-
-
-
-
slack_sdk.oauth.state_store
-
-

OAuth state parameter data store โ€ฆ

-
-
slack_sdk.oauth.state_utils
-
-
-
-
slack_sdk.oauth.token_rotation
-
-
-
-
-
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/oauth/installation_store/index.html b/docs/api-docs/slack_sdk/oauth/installation_store/index.html deleted file mode 100644 index d94d1fc1f..000000000 --- a/docs/api-docs/slack_sdk/oauth/installation_store/index.html +++ /dev/null @@ -1,118 +0,0 @@ - - - - - - -slack_sdk.oauth.installation_store API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.oauth.installation_store

-
-
-
- -Expand source code - -
from .file import FileInstallationStore  # noqa
-from .installation_store import InstallationStore  # noqa
-from .models import Bot, Installation  # noqa
-
-
-
-

Sub-modules

-
-
slack_sdk.oauth.installation_store.amazon_s3
-
-
-
-
slack_sdk.oauth.installation_store.async_cacheable_installation_store
-
-
-
-
slack_sdk.oauth.installation_store.async_installation_store
-
-
-
-
slack_sdk.oauth.installation_store.cacheable_installation_store
-
-
-
-
slack_sdk.oauth.installation_store.file
-
-
-
-
slack_sdk.oauth.installation_store.installation_store
-
-

Slack installation data store โ€ฆ

-
-
slack_sdk.oauth.installation_store.internals
-
-
-
-
slack_sdk.oauth.installation_store.models
-
-
-
-
slack_sdk.oauth.installation_store.sqlalchemy
-
-
-
-
slack_sdk.oauth.installation_store.sqlite3
-
-
-
-
-
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/oauth/installation_store/internals.html b/docs/api-docs/slack_sdk/oauth/installation_store/internals.html deleted file mode 100644 index 8b246a6a3..000000000 --- a/docs/api-docs/slack_sdk/oauth/installation_store/internals.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - -slack_sdk.oauth.installation_store.internals API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.oauth.installation_store.internals

-
-
-
- -Expand source code - -
import platform
-import datetime
-
-(major, minor, patch) = platform.python_version_tuple()
-is_python_3_6: bool = int(major) == 3 and int(minor) >= 6
-
-utc_timezone = datetime.timezone.utc
-
-
-def _from_iso_format_to_datetime(iso_datetime_str: str) -> datetime:
-    if is_python_3_6:
-        elements = iso_datetime_str.split(" ")
-        ymd = elements[0].split("-")
-        hms = elements[1].split(":")
-        return datetime.datetime(
-            int(ymd[0]),
-            int(ymd[1]),
-            int(ymd[2]),
-            int(hms[0]),
-            int(hms[1]),
-            int(hms[2]),
-            0,
-            utc_timezone,
-        )
-    else:
-        if "+" not in iso_datetime_str:
-            iso_datetime_str += "+00:00"
-        return datetime.datetime.fromisoformat(iso_datetime_str)
-
-
-def _from_iso_format_to_unix_timestamp(iso_datetime_str: str) -> float:
-    return _from_iso_format_to_datetime(iso_datetime_str).timestamp()
-
-
-
-
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/oauth/installation_store/models/bot.html b/docs/api-docs/slack_sdk/oauth/installation_store/models/bot.html deleted file mode 100644 index 3f533ac65..000000000 --- a/docs/api-docs/slack_sdk/oauth/installation_store/models/bot.html +++ /dev/null @@ -1,469 +0,0 @@ - - - - - - -slack_sdk.oauth.installation_store.models.bot API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.oauth.installation_store.models.bot

-
-
-
- -Expand source code - -
import re
-from datetime import datetime
-from time import time
-from typing import Optional, Union, Dict, Any, Sequence
-
-from slack_sdk.oauth.installation_store.internals import (
-    _from_iso_format_to_unix_timestamp,
-)
-
-
-class Bot:
-    app_id: Optional[str]
-    enterprise_id: Optional[str]
-    enterprise_name: Optional[str]
-    team_id: Optional[str]
-    team_name: Optional[str]
-    bot_token: str
-    bot_id: str
-    bot_user_id: str
-    bot_scopes: Sequence[str]
-    # only when token rotation is enabled
-    bot_refresh_token: Optional[str]
-    # only when token rotation is enabled
-    bot_token_expires_at: Optional[int]
-    is_enterprise_install: bool
-    installed_at: float
-
-    custom_values: Dict[str, Any]
-
-    def __init__(
-        self,
-        *,
-        app_id: Optional[str] = None,
-        # org / workspace
-        enterprise_id: Optional[str] = None,
-        enterprise_name: Optional[str] = None,
-        team_id: Optional[str] = None,
-        team_name: Optional[str] = None,
-        # bot
-        bot_token: str,
-        bot_id: str,
-        bot_user_id: str,
-        bot_scopes: Union[str, Sequence[str]] = "",
-        # only when token rotation is enabled
-        bot_refresh_token: Optional[str] = None,
-        # only when token rotation is enabled
-        bot_token_expires_in: Optional[int] = None,
-        # only for duplicating this object
-        # only when token rotation is enabled
-        bot_token_expires_at: Optional[Union[int, datetime, str]] = None,
-        is_enterprise_install: Optional[bool] = False,
-        # timestamps
-        # The expected value type is float but the internals handle other types too
-        # for str values, we supports only ISO datetime format.
-        installed_at: Union[float, datetime, str],
-        # custom values
-        custom_values: Optional[Dict[str, Any]] = None,
-    ):
-        self.app_id = app_id
-        self.enterprise_id = enterprise_id
-        self.enterprise_name = enterprise_name
-        self.team_id = team_id
-        self.team_name = team_name
-
-        self.bot_token = bot_token
-        self.bot_id = bot_id
-        self.bot_user_id = bot_user_id
-        if isinstance(bot_scopes, str):
-            self.bot_scopes = bot_scopes.split(",") if len(bot_scopes) > 0 else []
-        else:
-            self.bot_scopes = bot_scopes
-        self.bot_refresh_token = bot_refresh_token
-        if bot_token_expires_at is not None:
-            if type(bot_token_expires_at) == datetime:
-                self.bot_token_expires_at = int(bot_token_expires_at.timestamp())
-            elif type(bot_token_expires_at) == str and not re.match(
-                "^\\d+$", bot_token_expires_at
-            ):
-                self.bot_token_expires_at = int(
-                    _from_iso_format_to_unix_timestamp(bot_token_expires_at)
-                )
-            else:
-                self.bot_token_expires_at = int(bot_token_expires_at)
-        elif bot_token_expires_in is not None:
-            self.bot_token_expires_at = int(time()) + bot_token_expires_in
-        else:
-            self.bot_token_expires_at = None
-        self.is_enterprise_install = is_enterprise_install or False
-
-        if type(installed_at) == float:
-            self.installed_at = installed_at
-        elif type(installed_at) == datetime:
-            self.installed_at = installed_at.timestamp()
-        elif type(installed_at) == str:
-            if re.match("^\\d+.\\d+$", installed_at):
-                self.installed_at = float(installed_at)
-            else:
-                self.installed_at = _from_iso_format_to_unix_timestamp(installed_at)
-        else:
-            raise ValueError(f"Unsupported data format for installed_at {installed_at}")
-
-        self.custom_values = custom_values if custom_values is not None else {}
-
-    def set_custom_value(self, name: str, value: Any):
-        self.custom_values[name] = value
-
-    def get_custom_value(self, name: str) -> Optional[Any]:
-        return self.custom_values.get(name)
-
-    def to_dict(self) -> Dict[str, Any]:
-        standard_values = {
-            "app_id": self.app_id,
-            "enterprise_id": self.enterprise_id,
-            "enterprise_name": self.enterprise_name,
-            "team_id": self.team_id,
-            "team_name": self.team_name,
-            "bot_token": self.bot_token,
-            "bot_id": self.bot_id,
-            "bot_user_id": self.bot_user_id,
-            "bot_scopes": ",".join(self.bot_scopes) if self.bot_scopes else None,
-            "bot_refresh_token": self.bot_refresh_token,
-            "bot_token_expires_at": datetime.utcfromtimestamp(self.bot_token_expires_at)
-            if self.bot_token_expires_at is not None
-            else None,
-            "is_enterprise_install": self.is_enterprise_install,
-            "installed_at": datetime.utcfromtimestamp(self.installed_at),
-        }
-        # prioritize standard_values over custom_values
-        # when the same keys exist in both
-        return {**self.custom_values, **standard_values}
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class Bot -(*, app_id:ย Optional[str]ย =ย None, enterprise_id:ย Optional[str]ย =ย None, enterprise_name:ย Optional[str]ย =ย None, team_id:ย Optional[str]ย =ย None, team_name:ย Optional[str]ย =ย None, bot_token:ย str, bot_id:ย str, bot_user_id:ย str, bot_scopes:ย Union[str,ย Sequence[str]]ย =ย '', bot_refresh_token:ย Optional[str]ย =ย None, bot_token_expires_in:ย Optional[int]ย =ย None, bot_token_expires_at:ย Union[int,ย datetime.datetime,ย str,ย ForwardRef(None)]ย =ย None, is_enterprise_install:ย Optional[bool]ย =ย False, installed_at:ย Union[float,ย datetime.datetime,ย str], custom_values:ย Optional[Dict[str,ย Any]]ย =ย None) -
-
-
-
- -Expand source code - -
class Bot:
-    app_id: Optional[str]
-    enterprise_id: Optional[str]
-    enterprise_name: Optional[str]
-    team_id: Optional[str]
-    team_name: Optional[str]
-    bot_token: str
-    bot_id: str
-    bot_user_id: str
-    bot_scopes: Sequence[str]
-    # only when token rotation is enabled
-    bot_refresh_token: Optional[str]
-    # only when token rotation is enabled
-    bot_token_expires_at: Optional[int]
-    is_enterprise_install: bool
-    installed_at: float
-
-    custom_values: Dict[str, Any]
-
-    def __init__(
-        self,
-        *,
-        app_id: Optional[str] = None,
-        # org / workspace
-        enterprise_id: Optional[str] = None,
-        enterprise_name: Optional[str] = None,
-        team_id: Optional[str] = None,
-        team_name: Optional[str] = None,
-        # bot
-        bot_token: str,
-        bot_id: str,
-        bot_user_id: str,
-        bot_scopes: Union[str, Sequence[str]] = "",
-        # only when token rotation is enabled
-        bot_refresh_token: Optional[str] = None,
-        # only when token rotation is enabled
-        bot_token_expires_in: Optional[int] = None,
-        # only for duplicating this object
-        # only when token rotation is enabled
-        bot_token_expires_at: Optional[Union[int, datetime, str]] = None,
-        is_enterprise_install: Optional[bool] = False,
-        # timestamps
-        # The expected value type is float but the internals handle other types too
-        # for str values, we supports only ISO datetime format.
-        installed_at: Union[float, datetime, str],
-        # custom values
-        custom_values: Optional[Dict[str, Any]] = None,
-    ):
-        self.app_id = app_id
-        self.enterprise_id = enterprise_id
-        self.enterprise_name = enterprise_name
-        self.team_id = team_id
-        self.team_name = team_name
-
-        self.bot_token = bot_token
-        self.bot_id = bot_id
-        self.bot_user_id = bot_user_id
-        if isinstance(bot_scopes, str):
-            self.bot_scopes = bot_scopes.split(",") if len(bot_scopes) > 0 else []
-        else:
-            self.bot_scopes = bot_scopes
-        self.bot_refresh_token = bot_refresh_token
-        if bot_token_expires_at is not None:
-            if type(bot_token_expires_at) == datetime:
-                self.bot_token_expires_at = int(bot_token_expires_at.timestamp())
-            elif type(bot_token_expires_at) == str and not re.match(
-                "^\\d+$", bot_token_expires_at
-            ):
-                self.bot_token_expires_at = int(
-                    _from_iso_format_to_unix_timestamp(bot_token_expires_at)
-                )
-            else:
-                self.bot_token_expires_at = int(bot_token_expires_at)
-        elif bot_token_expires_in is not None:
-            self.bot_token_expires_at = int(time()) + bot_token_expires_in
-        else:
-            self.bot_token_expires_at = None
-        self.is_enterprise_install = is_enterprise_install or False
-
-        if type(installed_at) == float:
-            self.installed_at = installed_at
-        elif type(installed_at) == datetime:
-            self.installed_at = installed_at.timestamp()
-        elif type(installed_at) == str:
-            if re.match("^\\d+.\\d+$", installed_at):
-                self.installed_at = float(installed_at)
-            else:
-                self.installed_at = _from_iso_format_to_unix_timestamp(installed_at)
-        else:
-            raise ValueError(f"Unsupported data format for installed_at {installed_at}")
-
-        self.custom_values = custom_values if custom_values is not None else {}
-
-    def set_custom_value(self, name: str, value: Any):
-        self.custom_values[name] = value
-
-    def get_custom_value(self, name: str) -> Optional[Any]:
-        return self.custom_values.get(name)
-
-    def to_dict(self) -> Dict[str, Any]:
-        standard_values = {
-            "app_id": self.app_id,
-            "enterprise_id": self.enterprise_id,
-            "enterprise_name": self.enterprise_name,
-            "team_id": self.team_id,
-            "team_name": self.team_name,
-            "bot_token": self.bot_token,
-            "bot_id": self.bot_id,
-            "bot_user_id": self.bot_user_id,
-            "bot_scopes": ",".join(self.bot_scopes) if self.bot_scopes else None,
-            "bot_refresh_token": self.bot_refresh_token,
-            "bot_token_expires_at": datetime.utcfromtimestamp(self.bot_token_expires_at)
-            if self.bot_token_expires_at is not None
-            else None,
-            "is_enterprise_install": self.is_enterprise_install,
-            "installed_at": datetime.utcfromtimestamp(self.installed_at),
-        }
-        # prioritize standard_values over custom_values
-        # when the same keys exist in both
-        return {**self.custom_values, **standard_values}
-
-

Class variables

-
-
var app_id :ย Optional[str]
-
-
-
-
var bot_id :ย str
-
-
-
-
var bot_refresh_token :ย Optional[str]
-
-
-
-
var bot_scopes :ย Sequence[str]
-
-
-
-
var bot_token :ย str
-
-
-
-
var bot_token_expires_at :ย Optional[int]
-
-
-
-
var bot_user_id :ย str
-
-
-
-
var custom_values :ย Dict[str,ย Any]
-
-
-
-
var enterprise_id :ย Optional[str]
-
-
-
-
var enterprise_name :ย Optional[str]
-
-
-
-
var installed_at :ย float
-
-
-
-
var is_enterprise_install :ย bool
-
-
-
-
var team_id :ย Optional[str]
-
-
-
-
var team_name :ย Optional[str]
-
-
-
-
-

Methods

-
-
-def get_custom_value(self, name:ย str) โ€‘>ย Optional[Any] -
-
-
-
- -Expand source code - -
def get_custom_value(self, name: str) -> Optional[Any]:
-    return self.custom_values.get(name)
-
-
-
-def set_custom_value(self, name:ย str, value:ย Any) -
-
-
-
- -Expand source code - -
def set_custom_value(self, name: str, value: Any):
-    self.custom_values[name] = value
-
-
-
-def to_dict(self) โ€‘>ย Dict[str,ย Any] -
-
-
-
- -Expand source code - -
def to_dict(self) -> Dict[str, Any]:
-    standard_values = {
-        "app_id": self.app_id,
-        "enterprise_id": self.enterprise_id,
-        "enterprise_name": self.enterprise_name,
-        "team_id": self.team_id,
-        "team_name": self.team_name,
-        "bot_token": self.bot_token,
-        "bot_id": self.bot_id,
-        "bot_user_id": self.bot_user_id,
-        "bot_scopes": ",".join(self.bot_scopes) if self.bot_scopes else None,
-        "bot_refresh_token": self.bot_refresh_token,
-        "bot_token_expires_at": datetime.utcfromtimestamp(self.bot_token_expires_at)
-        if self.bot_token_expires_at is not None
-        else None,
-        "is_enterprise_install": self.is_enterprise_install,
-        "installed_at": datetime.utcfromtimestamp(self.installed_at),
-    }
-    # prioritize standard_values over custom_values
-    # when the same keys exist in both
-    return {**self.custom_values, **standard_values}
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/oauth/installation_store/models/index.html b/docs/api-docs/slack_sdk/oauth/installation_store/models/index.html deleted file mode 100644 index c55681e95..000000000 --- a/docs/api-docs/slack_sdk/oauth/installation_store/models/index.html +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - -slack_sdk.oauth.installation_store.models API documentation - - - - - - - - - - - -
- - -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/oauth/installation_store/sqlalchemy/index.html b/docs/api-docs/slack_sdk/oauth/installation_store/sqlalchemy/index.html deleted file mode 100644 index 9bd3843ab..000000000 --- a/docs/api-docs/slack_sdk/oauth/installation_store/sqlalchemy/index.html +++ /dev/null @@ -1,971 +0,0 @@ - - - - - - -slack_sdk.oauth.installation_store.sqlalchemy API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.oauth.installation_store.sqlalchemy

-
-
-
- -Expand source code - -
import logging
-from logging import Logger
-from typing import Optional
-
-import sqlalchemy
-from sqlalchemy import (
-    Table,
-    Column,
-    Integer,
-    String,
-    DateTime,
-    Index,
-    and_,
-    desc,
-    MetaData,
-)
-from sqlalchemy.engine import Engine
-from sqlalchemy.sql.sqltypes import Boolean
-
-from slack_sdk.oauth.installation_store.installation_store import InstallationStore
-from slack_sdk.oauth.installation_store.models.bot import Bot
-from slack_sdk.oauth.installation_store.models.installation import Installation
-
-
-class SQLAlchemyInstallationStore(InstallationStore):
-    default_bots_table_name: str = "slack_bots"
-    default_installations_table_name: str = "slack_installations"
-
-    client_id: str
-    engine: Engine
-    metadata: MetaData
-    installations: Table
-
-    @classmethod
-    def build_installations_table(cls, metadata: MetaData, table_name: str) -> Table:
-        return sqlalchemy.Table(
-            table_name,
-            metadata,
-            Column("id", Integer, primary_key=True, autoincrement=True),
-            Column("client_id", String(32), nullable=False),
-            Column("app_id", String(32), nullable=False),
-            Column("enterprise_id", String(32)),
-            Column("enterprise_name", String(200)),
-            Column("enterprise_url", String(200)),
-            Column("team_id", String(32)),
-            Column("team_name", String(200)),
-            Column("bot_token", String(200)),
-            Column("bot_id", String(32)),
-            Column("bot_user_id", String(32)),
-            Column("bot_scopes", String(1000)),
-            Column("bot_refresh_token", String(200)),  # added in v3.8.0
-            Column("bot_token_expires_at", DateTime),  # added in v3.8.0
-            Column("user_id", String(32), nullable=False),
-            Column("user_token", String(200)),
-            Column("user_scopes", String(1000)),
-            Column("user_refresh_token", String(200)),  # added in v3.8.0
-            Column("user_token_expires_at", DateTime),  # added in v3.8.0
-            Column("incoming_webhook_url", String(200)),
-            Column("incoming_webhook_channel", String(200)),
-            Column("incoming_webhook_channel_id", String(200)),
-            Column("incoming_webhook_configuration_url", String(200)),
-            Column("is_enterprise_install", Boolean, default=False, nullable=False),
-            Column("token_type", String(32)),
-            Column(
-                "installed_at",
-                DateTime,
-                nullable=False,
-                default=sqlalchemy.sql.func.now(),
-            ),
-            Index(
-                f"{table_name}_idx",
-                "client_id",
-                "enterprise_id",
-                "team_id",
-                "user_id",
-                "installed_at",
-            ),
-        )
-
-    @classmethod
-    def build_bots_table(cls, metadata: MetaData, table_name: str) -> Table:
-        return Table(
-            table_name,
-            metadata,
-            Column("id", Integer, primary_key=True, autoincrement=True),
-            Column("client_id", String(32), nullable=False),
-            Column("app_id", String(32), nullable=False),
-            Column("enterprise_id", String(32)),
-            Column("enterprise_name", String(200)),
-            Column("team_id", String(32)),
-            Column("team_name", String(200)),
-            Column("bot_token", String(200)),
-            Column("bot_id", String(32)),
-            Column("bot_user_id", String(32)),
-            Column("bot_scopes", String(1000)),
-            Column("bot_refresh_token", String(200)),  # added in v3.8.0
-            Column("bot_token_expires_at", DateTime),  # added in v3.8.0
-            Column("is_enterprise_install", Boolean, default=False, nullable=False),
-            Column(
-                "installed_at",
-                DateTime,
-                nullable=False,
-                default=sqlalchemy.sql.func.now(),
-            ),
-            Index(
-                f"{table_name}_idx",
-                "client_id",
-                "enterprise_id",
-                "team_id",
-                "installed_at",
-            ),
-        )
-
-    def __init__(
-        self,
-        client_id: str,
-        engine: Engine,
-        bots_table_name: str = default_bots_table_name,
-        installations_table_name: str = default_installations_table_name,
-        logger: Logger = logging.getLogger(__name__),
-    ):
-        self.metadata = sqlalchemy.MetaData()
-        self.bots = self.build_bots_table(
-            metadata=self.metadata, table_name=bots_table_name
-        )
-        self.installations = self.build_installations_table(
-            metadata=self.metadata, table_name=installations_table_name
-        )
-        self.client_id = client_id
-        self._logger = logger
-        self.engine = engine
-
-    def create_tables(self):
-        self.metadata.create_all(self.engine)
-
-    @property
-    def logger(self) -> Logger:
-        return self._logger
-
-    def save(self, installation: Installation):
-        with self.engine.begin() as conn:
-            i = installation.to_dict()
-            i["client_id"] = self.client_id
-
-            i_column = self.installations.c
-            installations_rows = conn.execute(
-                sqlalchemy.select([i_column.id])
-                .where(
-                    and_(
-                        i_column.client_id == self.client_id,
-                        i_column.enterprise_id == installation.enterprise_id,
-                        i_column.team_id == installation.team_id,
-                        i_column.installed_at == i.get("installed_at"),
-                    )
-                )
-                .limit(1)
-            )
-            installations_row_id: Optional[str] = None
-            for row in installations_rows:
-                installations_row_id = row["id"]
-            if installations_row_id is None:
-                conn.execute(self.installations.insert(), i)
-            else:
-                update_statement = (
-                    self.installations.update()
-                    .where(i_column.id == installations_row_id)
-                    .values(**i)
-                )
-                conn.execute(update_statement, i)
-
-        # bots
-        self.save_bot(installation.to_bot())
-
-    def save_bot(self, bot: Bot):
-        with self.engine.begin() as conn:
-            # bots
-            b = bot.to_dict()
-            b["client_id"] = self.client_id
-
-            b_column = self.bots.c
-            bots_rows = conn.execute(
-                sqlalchemy.select([b_column.id])
-                .where(
-                    and_(
-                        b_column.client_id == self.client_id,
-                        b_column.enterprise_id == bot.enterprise_id,
-                        b_column.team_id == bot.team_id,
-                        b_column.installed_at == b.get("installed_at"),
-                    )
-                )
-                .limit(1)
-            )
-            bots_row_id: Optional[str] = None
-            for row in bots_rows:
-                bots_row_id = row["id"]
-            if bots_row_id is None:
-                conn.execute(self.bots.insert(), b)
-            else:
-                update_statement = (
-                    self.bots.update().where(b_column.id == bots_row_id).values(**b)
-                )
-                conn.execute(update_statement, b)
-
-    def find_bot(
-        self,
-        *,
-        enterprise_id: Optional[str],
-        team_id: Optional[str],
-        is_enterprise_install: Optional[bool] = False,
-    ) -> Optional[Bot]:
-        if is_enterprise_install or team_id is None:
-            team_id = None
-
-        c = self.bots.c
-        query = (
-            self.bots.select()
-            .where(
-                and_(
-                    c.client_id == self.client_id,
-                    c.enterprise_id == enterprise_id,
-                    c.team_id == team_id,
-                )
-            )
-            .order_by(desc(c.installed_at))
-            .limit(1)
-        )
-
-        with self.engine.connect() as conn:
-            result: object = conn.execute(query)
-            for row in result:
-                return Bot(
-                    app_id=row["app_id"],
-                    enterprise_id=row["enterprise_id"],
-                    enterprise_name=row["enterprise_name"],
-                    team_id=row["team_id"],
-                    team_name=row["team_name"],
-                    bot_token=row["bot_token"],
-                    bot_id=row["bot_id"],
-                    bot_user_id=row["bot_user_id"],
-                    bot_scopes=row["bot_scopes"],
-                    bot_refresh_token=row["bot_refresh_token"],
-                    bot_token_expires_at=row["bot_token_expires_at"],
-                    is_enterprise_install=row["is_enterprise_install"],
-                    installed_at=row["installed_at"],
-                )
-            return None
-
-    def find_installation(
-        self,
-        *,
-        enterprise_id: Optional[str],
-        team_id: Optional[str],
-        user_id: Optional[str] = None,
-        is_enterprise_install: Optional[bool] = False,
-    ) -> Optional[Installation]:
-        if is_enterprise_install or team_id is None:
-            team_id = None
-
-        c = self.installations.c
-        where_clause = and_(c.enterprise_id == enterprise_id, c.team_id == team_id)
-        if user_id is not None:
-            where_clause = and_(
-                c.client_id == self.client_id,
-                c.enterprise_id == enterprise_id,
-                c.team_id == team_id,
-                c.user_id == user_id,
-            )
-
-        query = (
-            self.installations.select()
-            .where(where_clause)
-            .order_by(desc(c.installed_at))
-            .limit(1)
-        )
-
-        with self.engine.connect() as conn:
-            result: object = conn.execute(query)
-            for row in result:
-                return Installation(
-                    app_id=row["app_id"],
-                    enterprise_id=row["enterprise_id"],
-                    enterprise_name=row["enterprise_name"],
-                    enterprise_url=row["enterprise_url"],
-                    team_id=row["team_id"],
-                    team_name=row["team_name"],
-                    bot_token=row["bot_token"],
-                    bot_id=row["bot_id"],
-                    bot_user_id=row["bot_user_id"],
-                    bot_scopes=row["bot_scopes"],
-                    bot_refresh_token=row["bot_refresh_token"],
-                    bot_token_expires_at=row["bot_token_expires_at"],
-                    user_id=row["user_id"],
-                    user_token=row["user_token"],
-                    user_scopes=row["user_scopes"],
-                    user_refresh_token=row["user_refresh_token"],
-                    user_token_expires_at=row["user_token_expires_at"],
-                    # Only the incoming webhook issued in the latest installation is set in this logic
-                    incoming_webhook_url=row["incoming_webhook_url"],
-                    incoming_webhook_channel=row["incoming_webhook_channel"],
-                    incoming_webhook_channel_id=row["incoming_webhook_channel_id"],
-                    incoming_webhook_configuration_url=row[
-                        "incoming_webhook_configuration_url"
-                    ],
-                    is_enterprise_install=row["is_enterprise_install"],
-                    token_type=row["token_type"],
-                    installed_at=row["installed_at"],
-                )
-            return None
-
-    def delete_bot(
-        self, *, enterprise_id: Optional[str], team_id: Optional[str]
-    ) -> None:
-        table = self.bots
-        c = table.c
-        with self.engine.begin() as conn:
-            deletion = table.delete().where(
-                and_(
-                    c.client_id == self.client_id,
-                    c.enterprise_id == enterprise_id,
-                    c.team_id == team_id,
-                )
-            )
-            conn.execute(deletion)
-
-    def delete_installation(
-        self,
-        *,
-        enterprise_id: Optional[str],
-        team_id: Optional[str],
-        user_id: Optional[str] = None,
-    ) -> None:
-        table = self.installations
-        c = table.c
-        with self.engine.begin() as conn:
-            if user_id is not None:
-                deletion = table.delete().where(
-                    and_(
-                        c.client_id == self.client_id,
-                        c.enterprise_id == enterprise_id,
-                        c.team_id == team_id,
-                        c.user_id == user_id,
-                    )
-                )
-                conn.execute(deletion)
-            else:
-                deletion = table.delete().where(
-                    and_(
-                        c.client_id == self.client_id,
-                        c.enterprise_id == enterprise_id,
-                        c.team_id == team_id,
-                    )
-                )
-                conn.execute(deletion)
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class SQLAlchemyInstallationStore -(client_id:ย str, engine:ย sqlalchemy.engine.base.Engine, bots_table_name:ย strย =ย 'slack_bots', installations_table_name:ย strย =ย 'slack_installations', logger:ย logging.Loggerย =ย <Logger slack_sdk.oauth.installation_store.sqlalchemy (WARNING)>) -
-
-

The installation store interface.

-

The minimum required methods are:

-
    -
  • save(installation)
  • -
  • find_installation(enterprise_id, team_id, user_id, is_enterprise_install)
  • -
-

If you would like to properly handle app uninstallations and token revocations, -the following methods should be implemented.

-
    -
  • delete_installation(enterprise_id, team_id, user_id)
  • -
  • delete_all(enterprise_id, team_id)
  • -
-

If your app needs only bot scope installations, the simpler way to implement would be:

-
    -
  • save(installation)
  • -
  • find_bot(enterprise_id, team_id, is_enterprise_install)
  • -
  • delete_bot(enterprise_id, team_id)
  • -
  • delete_all(enterprise_id, team_id)
  • -
-
- -Expand source code - -
class SQLAlchemyInstallationStore(InstallationStore):
-    default_bots_table_name: str = "slack_bots"
-    default_installations_table_name: str = "slack_installations"
-
-    client_id: str
-    engine: Engine
-    metadata: MetaData
-    installations: Table
-
-    @classmethod
-    def build_installations_table(cls, metadata: MetaData, table_name: str) -> Table:
-        return sqlalchemy.Table(
-            table_name,
-            metadata,
-            Column("id", Integer, primary_key=True, autoincrement=True),
-            Column("client_id", String(32), nullable=False),
-            Column("app_id", String(32), nullable=False),
-            Column("enterprise_id", String(32)),
-            Column("enterprise_name", String(200)),
-            Column("enterprise_url", String(200)),
-            Column("team_id", String(32)),
-            Column("team_name", String(200)),
-            Column("bot_token", String(200)),
-            Column("bot_id", String(32)),
-            Column("bot_user_id", String(32)),
-            Column("bot_scopes", String(1000)),
-            Column("bot_refresh_token", String(200)),  # added in v3.8.0
-            Column("bot_token_expires_at", DateTime),  # added in v3.8.0
-            Column("user_id", String(32), nullable=False),
-            Column("user_token", String(200)),
-            Column("user_scopes", String(1000)),
-            Column("user_refresh_token", String(200)),  # added in v3.8.0
-            Column("user_token_expires_at", DateTime),  # added in v3.8.0
-            Column("incoming_webhook_url", String(200)),
-            Column("incoming_webhook_channel", String(200)),
-            Column("incoming_webhook_channel_id", String(200)),
-            Column("incoming_webhook_configuration_url", String(200)),
-            Column("is_enterprise_install", Boolean, default=False, nullable=False),
-            Column("token_type", String(32)),
-            Column(
-                "installed_at",
-                DateTime,
-                nullable=False,
-                default=sqlalchemy.sql.func.now(),
-            ),
-            Index(
-                f"{table_name}_idx",
-                "client_id",
-                "enterprise_id",
-                "team_id",
-                "user_id",
-                "installed_at",
-            ),
-        )
-
-    @classmethod
-    def build_bots_table(cls, metadata: MetaData, table_name: str) -> Table:
-        return Table(
-            table_name,
-            metadata,
-            Column("id", Integer, primary_key=True, autoincrement=True),
-            Column("client_id", String(32), nullable=False),
-            Column("app_id", String(32), nullable=False),
-            Column("enterprise_id", String(32)),
-            Column("enterprise_name", String(200)),
-            Column("team_id", String(32)),
-            Column("team_name", String(200)),
-            Column("bot_token", String(200)),
-            Column("bot_id", String(32)),
-            Column("bot_user_id", String(32)),
-            Column("bot_scopes", String(1000)),
-            Column("bot_refresh_token", String(200)),  # added in v3.8.0
-            Column("bot_token_expires_at", DateTime),  # added in v3.8.0
-            Column("is_enterprise_install", Boolean, default=False, nullable=False),
-            Column(
-                "installed_at",
-                DateTime,
-                nullable=False,
-                default=sqlalchemy.sql.func.now(),
-            ),
-            Index(
-                f"{table_name}_idx",
-                "client_id",
-                "enterprise_id",
-                "team_id",
-                "installed_at",
-            ),
-        )
-
-    def __init__(
-        self,
-        client_id: str,
-        engine: Engine,
-        bots_table_name: str = default_bots_table_name,
-        installations_table_name: str = default_installations_table_name,
-        logger: Logger = logging.getLogger(__name__),
-    ):
-        self.metadata = sqlalchemy.MetaData()
-        self.bots = self.build_bots_table(
-            metadata=self.metadata, table_name=bots_table_name
-        )
-        self.installations = self.build_installations_table(
-            metadata=self.metadata, table_name=installations_table_name
-        )
-        self.client_id = client_id
-        self._logger = logger
-        self.engine = engine
-
-    def create_tables(self):
-        self.metadata.create_all(self.engine)
-
-    @property
-    def logger(self) -> Logger:
-        return self._logger
-
-    def save(self, installation: Installation):
-        with self.engine.begin() as conn:
-            i = installation.to_dict()
-            i["client_id"] = self.client_id
-
-            i_column = self.installations.c
-            installations_rows = conn.execute(
-                sqlalchemy.select([i_column.id])
-                .where(
-                    and_(
-                        i_column.client_id == self.client_id,
-                        i_column.enterprise_id == installation.enterprise_id,
-                        i_column.team_id == installation.team_id,
-                        i_column.installed_at == i.get("installed_at"),
-                    )
-                )
-                .limit(1)
-            )
-            installations_row_id: Optional[str] = None
-            for row in installations_rows:
-                installations_row_id = row["id"]
-            if installations_row_id is None:
-                conn.execute(self.installations.insert(), i)
-            else:
-                update_statement = (
-                    self.installations.update()
-                    .where(i_column.id == installations_row_id)
-                    .values(**i)
-                )
-                conn.execute(update_statement, i)
-
-        # bots
-        self.save_bot(installation.to_bot())
-
-    def save_bot(self, bot: Bot):
-        with self.engine.begin() as conn:
-            # bots
-            b = bot.to_dict()
-            b["client_id"] = self.client_id
-
-            b_column = self.bots.c
-            bots_rows = conn.execute(
-                sqlalchemy.select([b_column.id])
-                .where(
-                    and_(
-                        b_column.client_id == self.client_id,
-                        b_column.enterprise_id == bot.enterprise_id,
-                        b_column.team_id == bot.team_id,
-                        b_column.installed_at == b.get("installed_at"),
-                    )
-                )
-                .limit(1)
-            )
-            bots_row_id: Optional[str] = None
-            for row in bots_rows:
-                bots_row_id = row["id"]
-            if bots_row_id is None:
-                conn.execute(self.bots.insert(), b)
-            else:
-                update_statement = (
-                    self.bots.update().where(b_column.id == bots_row_id).values(**b)
-                )
-                conn.execute(update_statement, b)
-
-    def find_bot(
-        self,
-        *,
-        enterprise_id: Optional[str],
-        team_id: Optional[str],
-        is_enterprise_install: Optional[bool] = False,
-    ) -> Optional[Bot]:
-        if is_enterprise_install or team_id is None:
-            team_id = None
-
-        c = self.bots.c
-        query = (
-            self.bots.select()
-            .where(
-                and_(
-                    c.client_id == self.client_id,
-                    c.enterprise_id == enterprise_id,
-                    c.team_id == team_id,
-                )
-            )
-            .order_by(desc(c.installed_at))
-            .limit(1)
-        )
-
-        with self.engine.connect() as conn:
-            result: object = conn.execute(query)
-            for row in result:
-                return Bot(
-                    app_id=row["app_id"],
-                    enterprise_id=row["enterprise_id"],
-                    enterprise_name=row["enterprise_name"],
-                    team_id=row["team_id"],
-                    team_name=row["team_name"],
-                    bot_token=row["bot_token"],
-                    bot_id=row["bot_id"],
-                    bot_user_id=row["bot_user_id"],
-                    bot_scopes=row["bot_scopes"],
-                    bot_refresh_token=row["bot_refresh_token"],
-                    bot_token_expires_at=row["bot_token_expires_at"],
-                    is_enterprise_install=row["is_enterprise_install"],
-                    installed_at=row["installed_at"],
-                )
-            return None
-
-    def find_installation(
-        self,
-        *,
-        enterprise_id: Optional[str],
-        team_id: Optional[str],
-        user_id: Optional[str] = None,
-        is_enterprise_install: Optional[bool] = False,
-    ) -> Optional[Installation]:
-        if is_enterprise_install or team_id is None:
-            team_id = None
-
-        c = self.installations.c
-        where_clause = and_(c.enterprise_id == enterprise_id, c.team_id == team_id)
-        if user_id is not None:
-            where_clause = and_(
-                c.client_id == self.client_id,
-                c.enterprise_id == enterprise_id,
-                c.team_id == team_id,
-                c.user_id == user_id,
-            )
-
-        query = (
-            self.installations.select()
-            .where(where_clause)
-            .order_by(desc(c.installed_at))
-            .limit(1)
-        )
-
-        with self.engine.connect() as conn:
-            result: object = conn.execute(query)
-            for row in result:
-                return Installation(
-                    app_id=row["app_id"],
-                    enterprise_id=row["enterprise_id"],
-                    enterprise_name=row["enterprise_name"],
-                    enterprise_url=row["enterprise_url"],
-                    team_id=row["team_id"],
-                    team_name=row["team_name"],
-                    bot_token=row["bot_token"],
-                    bot_id=row["bot_id"],
-                    bot_user_id=row["bot_user_id"],
-                    bot_scopes=row["bot_scopes"],
-                    bot_refresh_token=row["bot_refresh_token"],
-                    bot_token_expires_at=row["bot_token_expires_at"],
-                    user_id=row["user_id"],
-                    user_token=row["user_token"],
-                    user_scopes=row["user_scopes"],
-                    user_refresh_token=row["user_refresh_token"],
-                    user_token_expires_at=row["user_token_expires_at"],
-                    # Only the incoming webhook issued in the latest installation is set in this logic
-                    incoming_webhook_url=row["incoming_webhook_url"],
-                    incoming_webhook_channel=row["incoming_webhook_channel"],
-                    incoming_webhook_channel_id=row["incoming_webhook_channel_id"],
-                    incoming_webhook_configuration_url=row[
-                        "incoming_webhook_configuration_url"
-                    ],
-                    is_enterprise_install=row["is_enterprise_install"],
-                    token_type=row["token_type"],
-                    installed_at=row["installed_at"],
-                )
-            return None
-
-    def delete_bot(
-        self, *, enterprise_id: Optional[str], team_id: Optional[str]
-    ) -> None:
-        table = self.bots
-        c = table.c
-        with self.engine.begin() as conn:
-            deletion = table.delete().where(
-                and_(
-                    c.client_id == self.client_id,
-                    c.enterprise_id == enterprise_id,
-                    c.team_id == team_id,
-                )
-            )
-            conn.execute(deletion)
-
-    def delete_installation(
-        self,
-        *,
-        enterprise_id: Optional[str],
-        team_id: Optional[str],
-        user_id: Optional[str] = None,
-    ) -> None:
-        table = self.installations
-        c = table.c
-        with self.engine.begin() as conn:
-            if user_id is not None:
-                deletion = table.delete().where(
-                    and_(
-                        c.client_id == self.client_id,
-                        c.enterprise_id == enterprise_id,
-                        c.team_id == team_id,
-                        c.user_id == user_id,
-                    )
-                )
-                conn.execute(deletion)
-            else:
-                deletion = table.delete().where(
-                    and_(
-                        c.client_id == self.client_id,
-                        c.enterprise_id == enterprise_id,
-                        c.team_id == team_id,
-                    )
-                )
-                conn.execute(deletion)
-
-

Ancestors

- -

Class variables

-
-
var client_id :ย str
-
-
-
-
var default_bots_table_name :ย str
-
-
-
-
var default_installations_table_name :ย str
-
-
-
-
var engine :ย sqlalchemy.engine.base.Engine
-
-
-
-
var installations :ย sqlalchemy.sql.schema.Table
-
-
-
-
var metadata :ย sqlalchemy.sql.schema.MetaData
-
-
-
-
-

Static methods

-
-
-def build_bots_table(metadata:ย sqlalchemy.sql.schema.MetaData, table_name:ย str) โ€‘>ย sqlalchemy.sql.schema.Table -
-
-
-
- -Expand source code - -
@classmethod
-def build_bots_table(cls, metadata: MetaData, table_name: str) -> Table:
-    return Table(
-        table_name,
-        metadata,
-        Column("id", Integer, primary_key=True, autoincrement=True),
-        Column("client_id", String(32), nullable=False),
-        Column("app_id", String(32), nullable=False),
-        Column("enterprise_id", String(32)),
-        Column("enterprise_name", String(200)),
-        Column("team_id", String(32)),
-        Column("team_name", String(200)),
-        Column("bot_token", String(200)),
-        Column("bot_id", String(32)),
-        Column("bot_user_id", String(32)),
-        Column("bot_scopes", String(1000)),
-        Column("bot_refresh_token", String(200)),  # added in v3.8.0
-        Column("bot_token_expires_at", DateTime),  # added in v3.8.0
-        Column("is_enterprise_install", Boolean, default=False, nullable=False),
-        Column(
-            "installed_at",
-            DateTime,
-            nullable=False,
-            default=sqlalchemy.sql.func.now(),
-        ),
-        Index(
-            f"{table_name}_idx",
-            "client_id",
-            "enterprise_id",
-            "team_id",
-            "installed_at",
-        ),
-    )
-
-
-
-def build_installations_table(metadata:ย sqlalchemy.sql.schema.MetaData, table_name:ย str) โ€‘>ย sqlalchemy.sql.schema.Table -
-
-
-
- -Expand source code - -
@classmethod
-def build_installations_table(cls, metadata: MetaData, table_name: str) -> Table:
-    return sqlalchemy.Table(
-        table_name,
-        metadata,
-        Column("id", Integer, primary_key=True, autoincrement=True),
-        Column("client_id", String(32), nullable=False),
-        Column("app_id", String(32), nullable=False),
-        Column("enterprise_id", String(32)),
-        Column("enterprise_name", String(200)),
-        Column("enterprise_url", String(200)),
-        Column("team_id", String(32)),
-        Column("team_name", String(200)),
-        Column("bot_token", String(200)),
-        Column("bot_id", String(32)),
-        Column("bot_user_id", String(32)),
-        Column("bot_scopes", String(1000)),
-        Column("bot_refresh_token", String(200)),  # added in v3.8.0
-        Column("bot_token_expires_at", DateTime),  # added in v3.8.0
-        Column("user_id", String(32), nullable=False),
-        Column("user_token", String(200)),
-        Column("user_scopes", String(1000)),
-        Column("user_refresh_token", String(200)),  # added in v3.8.0
-        Column("user_token_expires_at", DateTime),  # added in v3.8.0
-        Column("incoming_webhook_url", String(200)),
-        Column("incoming_webhook_channel", String(200)),
-        Column("incoming_webhook_channel_id", String(200)),
-        Column("incoming_webhook_configuration_url", String(200)),
-        Column("is_enterprise_install", Boolean, default=False, nullable=False),
-        Column("token_type", String(32)),
-        Column(
-            "installed_at",
-            DateTime,
-            nullable=False,
-            default=sqlalchemy.sql.func.now(),
-        ),
-        Index(
-            f"{table_name}_idx",
-            "client_id",
-            "enterprise_id",
-            "team_id",
-            "user_id",
-            "installed_at",
-        ),
-    )
-
-
-
-

Instance variables

-
-
var logger :ย logging.Logger
-
-
-
- -Expand source code - -
@property
-def logger(self) -> Logger:
-    return self._logger
-
-
-
-

Methods

-
-
-def create_tables(self) -
-
-
-
- -Expand source code - -
def create_tables(self):
-    self.metadata.create_all(self.engine)
-
-
-
-

Inherited members

- -
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/oauth/redirect_uri_page_renderer/index.html b/docs/api-docs/slack_sdk/oauth/redirect_uri_page_renderer/index.html deleted file mode 100644 index 55b55b627..000000000 --- a/docs/api-docs/slack_sdk/oauth/redirect_uri_page_renderer/index.html +++ /dev/null @@ -1,313 +0,0 @@ - - - - - - -slack_sdk.oauth.redirect_uri_page_renderer API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.oauth.redirect_uri_page_renderer

-
-
-
- -Expand source code - -
from typing import Optional
-
-
-class RedirectUriPageRenderer:
-    def __init__(
-        self,
-        *,
-        install_path: str,
-        redirect_uri_path: str,
-        success_url: Optional[str] = None,
-        failure_url: Optional[str] = None,
-    ):
-        self.install_path = install_path
-        self.redirect_uri_path = redirect_uri_path
-        self.success_url = success_url
-        self.failure_url = failure_url
-
-    def render_success_page(
-        self,
-        app_id: str,
-        team_id: Optional[str],
-        is_enterprise_install: Optional[bool] = None,
-        enterprise_url: Optional[str] = None,
-    ) -> str:
-        url = self.success_url
-        if url is None:
-            if (
-                is_enterprise_install is True
-                and enterprise_url is not None
-                and app_id is not None
-            ):
-                url = f"{enterprise_url}manage/organization/apps/profile/{app_id}/workspaces/add"
-            elif team_id is None or app_id is None:
-                url = "slack://open"
-            else:
-                url = f"slack://app?team={team_id}&id={app_id}"
-        browser_url = f"https://app.slack.com/client/{team_id}"
-
-        return f"""
-<html>
-<head>
-<meta http-equiv="refresh" content="0; URL={url}">
-<style>
-body {{
-  padding: 10px 15px;
-  font-family: verdana;
-  text-align: center;
-}}
-</style>
-</head>
-<body>
-<h2>Thank you!</h2>
-<p>Redirecting to the Slack App... click <a href="{url}">here</a>. If you use the browser version of Slack, click <a href="{browser_url}" target="_blank">this link</a> instead.</p>
-</body>
-</html>
-"""  # noqa: E501
-
-    def render_failure_page(self, reason: str) -> str:
-        return f"""
-<html>
-<head>
-<style>
-body {{
-  padding: 10px 15px;
-  font-family: verdana;
-  text-align: center;
-}}
-</style>
-</head>
-<body>
-<h2>Oops, Something Went Wrong!</h2>
-<p>Please try again from <a href="{self.install_path}">here</a> or contact the app owner (reason: {reason})</p>
-</body>
-</html>
-"""
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class RedirectUriPageRenderer -(*, install_path:ย str, redirect_uri_path:ย str, success_url:ย Optional[str]ย =ย None, failure_url:ย Optional[str]ย =ย None) -
-
-
-
- -Expand source code - -
class RedirectUriPageRenderer:
-    def __init__(
-        self,
-        *,
-        install_path: str,
-        redirect_uri_path: str,
-        success_url: Optional[str] = None,
-        failure_url: Optional[str] = None,
-    ):
-        self.install_path = install_path
-        self.redirect_uri_path = redirect_uri_path
-        self.success_url = success_url
-        self.failure_url = failure_url
-
-    def render_success_page(
-        self,
-        app_id: str,
-        team_id: Optional[str],
-        is_enterprise_install: Optional[bool] = None,
-        enterprise_url: Optional[str] = None,
-    ) -> str:
-        url = self.success_url
-        if url is None:
-            if (
-                is_enterprise_install is True
-                and enterprise_url is not None
-                and app_id is not None
-            ):
-                url = f"{enterprise_url}manage/organization/apps/profile/{app_id}/workspaces/add"
-            elif team_id is None or app_id is None:
-                url = "slack://open"
-            else:
-                url = f"slack://app?team={team_id}&id={app_id}"
-        browser_url = f"https://app.slack.com/client/{team_id}"
-
-        return f"""
-<html>
-<head>
-<meta http-equiv="refresh" content="0; URL={url}">
-<style>
-body {{
-  padding: 10px 15px;
-  font-family: verdana;
-  text-align: center;
-}}
-</style>
-</head>
-<body>
-<h2>Thank you!</h2>
-<p>Redirecting to the Slack App... click <a href="{url}">here</a>. If you use the browser version of Slack, click <a href="{browser_url}" target="_blank">this link</a> instead.</p>
-</body>
-</html>
-"""  # noqa: E501
-
-    def render_failure_page(self, reason: str) -> str:
-        return f"""
-<html>
-<head>
-<style>
-body {{
-  padding: 10px 15px;
-  font-family: verdana;
-  text-align: center;
-}}
-</style>
-</head>
-<body>
-<h2>Oops, Something Went Wrong!</h2>
-<p>Please try again from <a href="{self.install_path}">here</a> or contact the app owner (reason: {reason})</p>
-</body>
-</html>
-"""
-
-

Methods

-
-
-def render_failure_page(self, reason:ย str) โ€‘>ย str -
-
-
-
- -Expand source code - -
    def render_failure_page(self, reason: str) -> str:
-        return f"""
-<html>
-<head>
-<style>
-body {{
-  padding: 10px 15px;
-  font-family: verdana;
-  text-align: center;
-}}
-</style>
-</head>
-<body>
-<h2>Oops, Something Went Wrong!</h2>
-<p>Please try again from <a href="{self.install_path}">here</a> or contact the app owner (reason: {reason})</p>
-</body>
-</html>
-"""
-
-
-
-def render_success_page(self, app_id:ย str, team_id:ย Optional[str], is_enterprise_install:ย Optional[bool]ย =ย None, enterprise_url:ย Optional[str]ย =ย None) โ€‘>ย str -
-
-
-
- -Expand source code - -
    def render_success_page(
-        self,
-        app_id: str,
-        team_id: Optional[str],
-        is_enterprise_install: Optional[bool] = None,
-        enterprise_url: Optional[str] = None,
-    ) -> str:
-        url = self.success_url
-        if url is None:
-            if (
-                is_enterprise_install is True
-                and enterprise_url is not None
-                and app_id is not None
-            ):
-                url = f"{enterprise_url}manage/organization/apps/profile/{app_id}/workspaces/add"
-            elif team_id is None or app_id is None:
-                url = "slack://open"
-            else:
-                url = f"slack://app?team={team_id}&id={app_id}"
-        browser_url = f"https://app.slack.com/client/{team_id}"
-
-        return f"""
-<html>
-<head>
-<meta http-equiv="refresh" content="0; URL={url}">
-<style>
-body {{
-  padding: 10px 15px;
-  font-family: verdana;
-  text-align: center;
-}}
-</style>
-</head>
-<body>
-<h2>Thank you!</h2>
-<p>Redirecting to the Slack App... click <a href="{url}">here</a>. If you use the browser version of Slack, click <a href="{browser_url}" target="_blank">this link</a> instead.</p>
-</body>
-</html>
-"""  # noqa: E501
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/oauth/state_store/index.html b/docs/api-docs/slack_sdk/oauth/state_store/index.html deleted file mode 100644 index 33f327d6a..000000000 --- a/docs/api-docs/slack_sdk/oauth/state_store/index.html +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - -slack_sdk.oauth.state_store API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.oauth.state_store

-
-
-

OAuth state parameter data store

-

Refer to https://slack.dev/python-slack-sdk/oauth/ for details.

-
- -Expand source code - -
"""OAuth state parameter data store
-
-Refer to https://slack.dev/python-slack-sdk/oauth/ for details.
-"""
-# from .amazon_s3_state_store import AmazonS3OAuthStateStore
-from .file import FileOAuthStateStore  # noqa
-from .state_store import OAuthStateStore  # noqa
-
-
-
-

Sub-modules

-
-
slack_sdk.oauth.state_store.amazon_s3
-
-
-
-
slack_sdk.oauth.state_store.async_state_store
-
-
-
-
slack_sdk.oauth.state_store.file
-
-
-
-
slack_sdk.oauth.state_store.sqlalchemy
-
-
-
-
slack_sdk.oauth.state_store.sqlite3
-
-
-
-
slack_sdk.oauth.state_store.state_store
-
-
-
-
-
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/oauth/state_store/sqlalchemy/index.html b/docs/api-docs/slack_sdk/oauth/state_store/sqlalchemy/index.html deleted file mode 100644 index 6711e3262..000000000 --- a/docs/api-docs/slack_sdk/oauth/state_store/sqlalchemy/index.html +++ /dev/null @@ -1,352 +0,0 @@ - - - - - - -slack_sdk.oauth.state_store.sqlalchemy API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.oauth.state_store.sqlalchemy

-
-
-
- -Expand source code - -
import logging
-import time
-from datetime import datetime
-from logging import Logger
-from uuid import uuid4
-
-from ..state_store import OAuthStateStore
-import sqlalchemy
-from sqlalchemy import Table, Column, Integer, String, DateTime, and_, MetaData
-from sqlalchemy.engine import Engine
-
-
-class SQLAlchemyOAuthStateStore(OAuthStateStore):
-    default_table_name: str = "slack_oauth_states"
-
-    expiration_seconds: int
-    engine: Engine
-    metadata: MetaData
-    oauth_states: Table
-
-    @classmethod
-    def build_oauth_states_table(cls, metadata: MetaData, table_name: str) -> Table:
-        return sqlalchemy.Table(
-            table_name,
-            metadata,
-            metadata,
-            Column("id", Integer, primary_key=True, autoincrement=True),
-            Column("state", String(200), nullable=False),
-            Column("expire_at", DateTime, nullable=False),
-        )
-
-    def __init__(
-        self,
-        expiration_seconds: int,
-        engine: Engine,
-        logger: Logger = logging.getLogger(__name__),
-        table_name: str = default_table_name,
-    ):
-        self.expiration_seconds = expiration_seconds
-        self._logger = logger
-        self.engine = engine
-        self.metadata = MetaData()
-        self.oauth_states = self.build_oauth_states_table(self.metadata, table_name)
-
-    @property
-    def logger(self) -> Logger:
-        if self._logger is None:
-            self._logger = logging.getLogger(__name__)
-        return self._logger
-
-    def issue(self, *args, **kwargs) -> str:
-        state: str = str(uuid4())
-        now = datetime.utcfromtimestamp(time.time() + self.expiration_seconds)
-        with self.engine.begin() as conn:
-            conn.execute(
-                self.oauth_states.insert(),
-                {"state": state, "expire_at": now},
-            )
-        return state
-
-    def consume(self, state: str) -> bool:
-        try:
-            with self.engine.begin() as conn:
-                c = self.oauth_states.c
-                query = self.oauth_states.select().where(
-                    and_(c.state == state, c.expire_at > datetime.utcnow())
-                )
-                result = conn.execute(query)
-                for row in result:
-                    self.logger.debug(f"consume's query result: {row}")
-                    conn.execute(self.oauth_states.delete().where(c.id == row["id"]))
-                    return True
-            return False
-        except Exception as e:  # skipcq: PYL-W0703
-            message = f"Failed to find any persistent data for state: {state} - {e}"
-            self.logger.warning(message)
-            return False
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class SQLAlchemyOAuthStateStore -(expiration_seconds:ย int, engine:ย sqlalchemy.engine.base.Engine, logger:ย logging.Loggerย =ย <Logger slack_sdk.oauth.state_store.sqlalchemy (WARNING)>, table_name:ย strย =ย 'slack_oauth_states') -
-
-
-
- -Expand source code - -
class SQLAlchemyOAuthStateStore(OAuthStateStore):
-    default_table_name: str = "slack_oauth_states"
-
-    expiration_seconds: int
-    engine: Engine
-    metadata: MetaData
-    oauth_states: Table
-
-    @classmethod
-    def build_oauth_states_table(cls, metadata: MetaData, table_name: str) -> Table:
-        return sqlalchemy.Table(
-            table_name,
-            metadata,
-            metadata,
-            Column("id", Integer, primary_key=True, autoincrement=True),
-            Column("state", String(200), nullable=False),
-            Column("expire_at", DateTime, nullable=False),
-        )
-
-    def __init__(
-        self,
-        expiration_seconds: int,
-        engine: Engine,
-        logger: Logger = logging.getLogger(__name__),
-        table_name: str = default_table_name,
-    ):
-        self.expiration_seconds = expiration_seconds
-        self._logger = logger
-        self.engine = engine
-        self.metadata = MetaData()
-        self.oauth_states = self.build_oauth_states_table(self.metadata, table_name)
-
-    @property
-    def logger(self) -> Logger:
-        if self._logger is None:
-            self._logger = logging.getLogger(__name__)
-        return self._logger
-
-    def issue(self, *args, **kwargs) -> str:
-        state: str = str(uuid4())
-        now = datetime.utcfromtimestamp(time.time() + self.expiration_seconds)
-        with self.engine.begin() as conn:
-            conn.execute(
-                self.oauth_states.insert(),
-                {"state": state, "expire_at": now},
-            )
-        return state
-
-    def consume(self, state: str) -> bool:
-        try:
-            with self.engine.begin() as conn:
-                c = self.oauth_states.c
-                query = self.oauth_states.select().where(
-                    and_(c.state == state, c.expire_at > datetime.utcnow())
-                )
-                result = conn.execute(query)
-                for row in result:
-                    self.logger.debug(f"consume's query result: {row}")
-                    conn.execute(self.oauth_states.delete().where(c.id == row["id"]))
-                    return True
-            return False
-        except Exception as e:  # skipcq: PYL-W0703
-            message = f"Failed to find any persistent data for state: {state} - {e}"
-            self.logger.warning(message)
-            return False
-
-

Ancestors

- -

Class variables

-
-
var default_table_name :ย str
-
-
-
-
var engine :ย sqlalchemy.engine.base.Engine
-
-
-
-
var expiration_seconds :ย int
-
-
-
-
var metadata :ย sqlalchemy.sql.schema.MetaData
-
-
-
-
var oauth_states :ย sqlalchemy.sql.schema.Table
-
-
-
-
-

Static methods

-
-
-def build_oauth_states_table(metadata:ย sqlalchemy.sql.schema.MetaData, table_name:ย str) โ€‘>ย sqlalchemy.sql.schema.Table -
-
-
-
- -Expand source code - -
@classmethod
-def build_oauth_states_table(cls, metadata: MetaData, table_name: str) -> Table:
-    return sqlalchemy.Table(
-        table_name,
-        metadata,
-        metadata,
-        Column("id", Integer, primary_key=True, autoincrement=True),
-        Column("state", String(200), nullable=False),
-        Column("expire_at", DateTime, nullable=False),
-    )
-
-
-
-

Instance variables

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

Methods

-
-
-def consume(self, state:ย str) โ€‘>ย bool -
-
-
-
- -Expand source code - -
def consume(self, state: str) -> bool:
-    try:
-        with self.engine.begin() as conn:
-            c = self.oauth_states.c
-            query = self.oauth_states.select().where(
-                and_(c.state == state, c.expire_at > datetime.utcnow())
-            )
-            result = conn.execute(query)
-            for row in result:
-                self.logger.debug(f"consume's query result: {row}")
-                conn.execute(self.oauth_states.delete().where(c.id == row["id"]))
-                return True
-        return False
-    except Exception as e:  # skipcq: PYL-W0703
-        message = f"Failed to find any persistent data for state: {state} - {e}"
-        self.logger.warning(message)
-        return False
-
-
-
-def issue(self, *args, **kwargs) โ€‘>ย str -
-
-
-
- -Expand source code - -
def issue(self, *args, **kwargs) -> str:
-    state: str = str(uuid4())
-    now = datetime.utcfromtimestamp(time.time() + self.expiration_seconds)
-    with self.engine.begin() as conn:
-        conn.execute(
-            self.oauth_states.insert(),
-            {"state": state, "expire_at": now},
-        )
-    return state
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/oauth/state_utils/index.html b/docs/api-docs/slack_sdk/oauth/state_utils/index.html deleted file mode 100644 index 4d8506058..000000000 --- a/docs/api-docs/slack_sdk/oauth/state_utils/index.html +++ /dev/null @@ -1,291 +0,0 @@ - - - - - - -slack_sdk.oauth.state_utils API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.oauth.state_utils

-
-
-
- -Expand source code - -
from typing import Optional, Dict, Sequence, Union
-
-
-class OAuthStateUtils:
-    cookie_name: str
-    expiration_seconds: int
-
-    default_cookie_name: str = "slack-app-oauth-state"
-    default_expiration_seconds: int = 60 * 10  # 10 minutes
-
-    def __init__(
-        self,
-        *,
-        cookie_name: str = default_cookie_name,
-        expiration_seconds: int = default_expiration_seconds,
-    ):
-        self.cookie_name = cookie_name
-        self.expiration_seconds = expiration_seconds
-
-    def build_set_cookie_for_new_state(self, state: str) -> str:
-        return (
-            f"{self.cookie_name}={state}; "
-            "Secure; "
-            "HttpOnly; "
-            "Path=/; "
-            f"Max-Age={self.expiration_seconds}"
-        )
-
-    def build_set_cookie_for_deletion(self) -> str:
-        return (
-            f"{self.cookie_name}=deleted; "
-            "Secure; "
-            "HttpOnly; "
-            "Path=/; "
-            "Expires=Thu, 01 Jan 1970 00:00:00 GMT"
-        )
-
-    def is_valid_browser(
-        self,
-        state: Optional[str],
-        request_headers: Dict[str, Union[str, Sequence[str]]],
-    ) -> bool:
-        if (
-            state is None
-            or request_headers is None
-            or request_headers.get("cookie", None) is None
-        ):
-            return False
-        cookies = request_headers["cookie"]
-        if isinstance(cookies, str):
-            cookies = [cookies]
-        for cookie in cookies:
-            values = cookie.split(";")
-            for value in values:
-                if value.strip() == f"{self.cookie_name}={state}":
-                    return True
-        return False
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class OAuthStateUtils -(*, cookie_name:ย strย =ย 'slack-app-oauth-state', expiration_seconds:ย intย =ย 600) -
-
-
-
- -Expand source code - -
class OAuthStateUtils:
-    cookie_name: str
-    expiration_seconds: int
-
-    default_cookie_name: str = "slack-app-oauth-state"
-    default_expiration_seconds: int = 60 * 10  # 10 minutes
-
-    def __init__(
-        self,
-        *,
-        cookie_name: str = default_cookie_name,
-        expiration_seconds: int = default_expiration_seconds,
-    ):
-        self.cookie_name = cookie_name
-        self.expiration_seconds = expiration_seconds
-
-    def build_set_cookie_for_new_state(self, state: str) -> str:
-        return (
-            f"{self.cookie_name}={state}; "
-            "Secure; "
-            "HttpOnly; "
-            "Path=/; "
-            f"Max-Age={self.expiration_seconds}"
-        )
-
-    def build_set_cookie_for_deletion(self) -> str:
-        return (
-            f"{self.cookie_name}=deleted; "
-            "Secure; "
-            "HttpOnly; "
-            "Path=/; "
-            "Expires=Thu, 01 Jan 1970 00:00:00 GMT"
-        )
-
-    def is_valid_browser(
-        self,
-        state: Optional[str],
-        request_headers: Dict[str, Union[str, Sequence[str]]],
-    ) -> bool:
-        if (
-            state is None
-            or request_headers is None
-            or request_headers.get("cookie", None) is None
-        ):
-            return False
-        cookies = request_headers["cookie"]
-        if isinstance(cookies, str):
-            cookies = [cookies]
-        for cookie in cookies:
-            values = cookie.split(";")
-            for value in values:
-                if value.strip() == f"{self.cookie_name}={state}":
-                    return True
-        return False
-
-

Class variables

-
-
var cookie_name :ย str
-
-
-
- -
-
-
-
var default_expiration_seconds :ย int
-
-
-
-
var expiration_seconds :ย int
-
-
-
-
-

Methods

-
- -
-
-
- -Expand source code - -
def build_set_cookie_for_deletion(self) -> str:
-    return (
-        f"{self.cookie_name}=deleted; "
-        "Secure; "
-        "HttpOnly; "
-        "Path=/; "
-        "Expires=Thu, 01 Jan 1970 00:00:00 GMT"
-    )
-
-
- -
-
-
- -Expand source code - -
def build_set_cookie_for_new_state(self, state: str) -> str:
-    return (
-        f"{self.cookie_name}={state}; "
-        "Secure; "
-        "HttpOnly; "
-        "Path=/; "
-        f"Max-Age={self.expiration_seconds}"
-    )
-
-
-
-def is_valid_browser(self, state:ย Optional[str], request_headers:ย Dict[str,ย Union[str,ย Sequence[str]]]) โ€‘>ย bool -
-
-
-
- -Expand source code - -
def is_valid_browser(
-    self,
-    state: Optional[str],
-    request_headers: Dict[str, Union[str, Sequence[str]]],
-) -> bool:
-    if (
-        state is None
-        or request_headers is None
-        or request_headers.get("cookie", None) is None
-    ):
-        return False
-    cookies = request_headers["cookie"]
-    if isinstance(cookies, str):
-        cookies = [cookies]
-    for cookie in cookies:
-        values = cookie.split(";")
-        for value in values:
-            if value.strip() == f"{self.cookie_name}={state}":
-                return True
-    return False
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/oauth/token_rotation/index.html b/docs/api-docs/slack_sdk/oauth/token_rotation/index.html deleted file mode 100644 index 8845233cb..000000000 --- a/docs/api-docs/slack_sdk/oauth/token_rotation/index.html +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - -slack_sdk.oauth.token_rotation API documentation - - - - - - - - - - - -
- - -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/proxy_env_variable_loader.html b/docs/api-docs/slack_sdk/proxy_env_variable_loader.html deleted file mode 100644 index a7864df3e..000000000 --- a/docs/api-docs/slack_sdk/proxy_env_variable_loader.html +++ /dev/null @@ -1,122 +0,0 @@ - - - - - - -slack_sdk.proxy_env_variable_loader API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.proxy_env_variable_loader

-
-
-

Internal module for loading proxy-related env variables

-
- -Expand source code - -
"""Internal module for loading proxy-related env variables"""
-import logging
-import os
-from typing import Optional
-
-_default_logger = logging.getLogger(__name__)
-
-
-def load_http_proxy_from_env(logger: logging.Logger = _default_logger) -> Optional[str]:
-    proxy_url = (
-        os.environ.get("HTTPS_PROXY")
-        or os.environ.get("https_proxy")
-        or os.environ.get("HTTP_PROXY")
-        or os.environ.get("http_proxy")
-    )
-    if proxy_url is None:
-        return None
-    if len(proxy_url.strip()) == 0:
-        # If the value is an empty string, the intention should be unsetting it
-        logger.debug(
-            "The Slack SDK ignored the proxy env variable as an empty value is set."
-        )
-        return None
-
-    logger.debug(f"HTTP proxy URL has been loaded from an env variable: {proxy_url}")
-    return proxy_url
-
-
-
-
-
-
-
-

Functions

-
-
-def load_http_proxy_from_env(logger:ย logging.Loggerย =ย <Logger slack_sdk.proxy_env_variable_loader (WARNING)>) โ€‘>ย Optional[str] -
-
-
-
- -Expand source code - -
def load_http_proxy_from_env(logger: logging.Logger = _default_logger) -> Optional[str]:
-    proxy_url = (
-        os.environ.get("HTTPS_PROXY")
-        or os.environ.get("https_proxy")
-        or os.environ.get("HTTP_PROXY")
-        or os.environ.get("http_proxy")
-    )
-    if proxy_url is None:
-        return None
-    if len(proxy_url.strip()) == 0:
-        # If the value is an empty string, the intention should be unsetting it
-        logger.debug(
-            "The Slack SDK ignored the proxy env variable as an empty value is set."
-        )
-        return None
-
-    logger.debug(f"HTTP proxy URL has been loaded from an env variable: {proxy_url}")
-    return proxy_url
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/rtm/v2/index.html b/docs/api-docs/slack_sdk/rtm/v2/index.html deleted file mode 100644 index e796a2883..000000000 --- a/docs/api-docs/slack_sdk/rtm/v2/index.html +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - -slack_sdk.rtm.v2 API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.rtm.v2

-
-
-
- -Expand source code - -
from slack_sdk.rtm_v2 import RTMClient  # noqa
-
-
-
-
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/scim/async_client.html b/docs/api-docs/slack_sdk/scim/async_client.html deleted file mode 100644 index 53c1cefc4..000000000 --- a/docs/api-docs/slack_sdk/scim/async_client.html +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - -slack_sdk.scim.async_client API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.scim.async_client

-
-
-
- -Expand source code - -
from .v1.async_client import AsyncSCIMClient  # noqa
-
-
-
-
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/scim/index.html b/docs/api-docs/slack_sdk/scim/index.html deleted file mode 100644 index c4e53b69a..000000000 --- a/docs/api-docs/slack_sdk/scim/index.html +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - -slack_sdk.scim API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.scim

-
-
-

SCIM API is a set of APIs for provisioning and managing user accounts and groups. -SCIM is used by Single Sign-On (SSO) services and identity providers to manage people across a variety of tools, -including Slack.

-

Refer to https://slack.dev/python-slack-sdk/scim/ for details.

-
- -Expand source code - -
"""SCIM API is a set of APIs for provisioning and managing user accounts and groups.
-SCIM is used by Single Sign-On (SSO) services and identity providers to manage people across a variety of tools,
-including Slack.
-
-Refer to https://slack.dev/python-slack-sdk/scim/ for details.
-"""
-from .v1.client import SCIMClient  # noqa
-from .v1.response import SCIMResponse  # noqa
-from .v1.response import SearchUsersResponse, ReadUserResponse  # noqa
-from .v1.response import SearchGroupsResponse, ReadGroupResponse  # noqa
-from .v1.user import User  # noqa
-from .v1.group import Group  # noqa
-
-
-
-

Sub-modules

-
-
slack_sdk.scim.async_client
-
-
-
-
slack_sdk.scim.v1
-
-

SCIM API is a set of APIs for provisioning and managing user accounts and groups. -SCIM is used by Single Sign-On (SSO) services and identity providers โ€ฆ

-
-
-
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/scim/v1/client.html b/docs/api-docs/slack_sdk/scim/v1/client.html deleted file mode 100644 index be97e4254..000000000 --- a/docs/api-docs/slack_sdk/scim/v1/client.html +++ /dev/null @@ -1,1308 +0,0 @@ - - - - - - -slack_sdk.scim.v1.client API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.scim.v1.client

-
-
-

SCIM API is a set of APIs for provisioning and managing user accounts and groups. -SCIM is used by Single Sign-On (SSO) services and identity providers to manage people across a variety of tools, -including Slack.

-

Refer to https://slack.dev/python-slack-sdk/scim/ for details.

-
- -Expand source code - -
"""SCIM API is a set of APIs for provisioning and managing user accounts and groups.
-SCIM is used by Single Sign-On (SSO) services and identity providers to manage people across a variety of tools,
-including Slack.
-
-Refer to https://slack.dev/python-slack-sdk/scim/ for details.
-"""
-import json
-import logging
-import urllib
-from http.client import HTTPResponse
-from ssl import SSLContext
-from typing import Dict, Optional, Union, Any, List
-from urllib.error import HTTPError
-from urllib.parse import quote
-from urllib.request import Request, urlopen, OpenerDirector, ProxyHandler, HTTPSHandler
-
-from slack_sdk.errors import SlackRequestError
-from .internal_utils import (
-    _build_query,
-    _build_request_headers,
-    _debug_log_response,
-    get_user_agent,
-    _to_dict_without_not_given,
-)
-from .response import (
-    SCIMResponse,
-    SearchUsersResponse,
-    ReadUserResponse,
-    SearchGroupsResponse,
-    ReadGroupResponse,
-    UserCreateResponse,
-    UserPatchResponse,
-    UserUpdateResponse,
-    UserDeleteResponse,
-    GroupCreateResponse,
-    GroupPatchResponse,
-    GroupUpdateResponse,
-    GroupDeleteResponse,
-)
-from .user import User
-from .group import Group
-
-from slack_sdk.http_retry import default_retry_handlers
-from slack_sdk.http_retry.handler import RetryHandler
-from slack_sdk.http_retry.request import HttpRequest as RetryHttpRequest
-from slack_sdk.http_retry.response import HttpResponse as RetryHttpResponse
-from slack_sdk.http_retry.state import RetryState
-
-from ...proxy_env_variable_loader import load_http_proxy_from_env
-
-
-class SCIMClient:
-    BASE_URL = "https://api.slack.com/scim/v1/"
-
-    token: str
-    timeout: int
-    ssl: Optional[SSLContext]
-    proxy: Optional[str]
-    base_url: str
-    default_headers: Dict[str, str]
-    logger: logging.Logger
-    retry_handlers: List[RetryHandler]
-
-    def __init__(
-        self,
-        token: str,
-        timeout: int = 30,
-        ssl: Optional[SSLContext] = None,
-        proxy: Optional[str] = None,
-        base_url: str = BASE_URL,
-        default_headers: Optional[Dict[str, str]] = None,
-        user_agent_prefix: Optional[str] = None,
-        user_agent_suffix: Optional[str] = None,
-        logger: Optional[logging.Logger] = None,
-        retry_handlers: Optional[List[RetryHandler]] = None,
-    ):
-        """API client for SCIM API
-        See https://api.slack.com/scim for more details
-
-        Args:
-            token: An admin user's token, which starts with `xoxp-`
-            timeout: Request timeout (in seconds)
-            ssl: `ssl.SSLContext` to use for requests
-            proxy: Proxy URL (e.g., `localhost:9000`, `http://localhost:9000`)
-            base_url: The base URL for API calls
-            default_headers: Request headers to add to all requests
-            user_agent_prefix: Prefix for User-Agent header value
-            user_agent_suffix: Suffix for User-Agent header value
-            logger: Custom logger
-            retry_handlers: Retry handlers
-        """
-        self.token = token
-        self.timeout = timeout
-        self.ssl = ssl
-        self.proxy = proxy
-        self.base_url = base_url
-        self.default_headers = default_headers if default_headers else {}
-        self.default_headers["User-Agent"] = get_user_agent(
-            user_agent_prefix, user_agent_suffix
-        )
-        self.logger = logger if logger is not None else logging.getLogger(__name__)
-        self.retry_handlers = (
-            retry_handlers if retry_handlers is not None else default_retry_handlers()
-        )
-
-        if self.proxy is None or len(self.proxy.strip()) == 0:
-            env_variable = load_http_proxy_from_env(self.logger)
-            if env_variable is not None:
-                self.proxy = env_variable
-
-    # -------------------------
-    # Users
-    # -------------------------
-
-    def search_users(
-        self,
-        *,
-        # Pagination required as of August 30, 2019.
-        count: int,
-        start_index: int,
-        filter: Optional[str] = None,
-    ) -> SearchUsersResponse:
-        return SearchUsersResponse(
-            self.api_call(
-                http_verb="GET",
-                path="Users",
-                query_params={
-                    "filter": filter,
-                    "count": count,
-                    "startIndex": start_index,
-                },
-            )
-        )
-
-    def read_user(self, id: str) -> ReadUserResponse:
-        return ReadUserResponse(
-            self.api_call(http_verb="GET", path=f"Users/{quote(id)}")
-        )
-
-    def create_user(self, user: Union[Dict[str, Any], User]) -> UserCreateResponse:
-        return UserCreateResponse(
-            self.api_call(
-                http_verb="POST",
-                path="Users",
-                body_params=user.to_dict()
-                if isinstance(user, User)
-                else _to_dict_without_not_given(user),
-            )
-        )
-
-    def patch_user(
-        self, id: str, partial_user: Union[Dict[str, Any], User]
-    ) -> UserPatchResponse:
-        return UserPatchResponse(
-            self.api_call(
-                http_verb="PATCH",
-                path=f"Users/{quote(id)}",
-                body_params=partial_user.to_dict()
-                if isinstance(partial_user, User)
-                else _to_dict_without_not_given(partial_user),
-            )
-        )
-
-    def update_user(self, user: Union[Dict[str, Any], User]) -> UserUpdateResponse:
-        return UserUpdateResponse(
-            self.api_call(
-                http_verb="PUT",
-                path=f"Users/{quote(user.id)}",
-                body_params=user.to_dict()
-                if isinstance(user, User)
-                else _to_dict_without_not_given(user),
-            )
-        )
-
-    def delete_user(self, id: str) -> UserDeleteResponse:
-        return UserDeleteResponse(
-            self.api_call(
-                http_verb="DELETE",
-                path=f"Users/{quote(id)}",
-            )
-        )
-
-    # -------------------------
-    # Groups
-    # -------------------------
-
-    def search_groups(
-        self,
-        *,
-        # Pagination required as of August 30, 2019.
-        count: int,
-        start_index: int,
-        filter: Optional[str] = None,
-    ) -> SearchGroupsResponse:
-        return SearchGroupsResponse(
-            self.api_call(
-                http_verb="GET",
-                path="Groups",
-                query_params={
-                    "filter": filter,
-                    "count": count,
-                    "startIndex": start_index,
-                },
-            )
-        )
-
-    def read_group(self, id: str) -> ReadGroupResponse:
-        return ReadGroupResponse(
-            self.api_call(http_verb="GET", path=f"Groups/{quote(id)}")
-        )
-
-    def create_group(self, group: Union[Dict[str, Any], Group]) -> GroupCreateResponse:
-        return GroupCreateResponse(
-            self.api_call(
-                http_verb="POST",
-                path="Groups",
-                body_params=group.to_dict()
-                if isinstance(group, Group)
-                else _to_dict_without_not_given(group),
-            )
-        )
-
-    def patch_group(
-        self, id: str, partial_group: Union[Dict[str, Any], Group]
-    ) -> GroupPatchResponse:
-        return GroupPatchResponse(
-            self.api_call(
-                http_verb="PATCH",
-                path=f"Groups/{quote(id)}",
-                body_params=partial_group.to_dict()
-                if isinstance(partial_group, Group)
-                else _to_dict_without_not_given(partial_group),
-            )
-        )
-
-    def update_group(self, group: Union[Dict[str, Any], Group]) -> GroupUpdateResponse:
-        return GroupUpdateResponse(
-            self.api_call(
-                http_verb="PUT",
-                path=f"Groups/{quote(group.id)}",
-                body_params=group.to_dict()
-                if isinstance(group, Group)
-                else _to_dict_without_not_given(group),
-            )
-        )
-
-    def delete_group(self, id: str) -> GroupDeleteResponse:
-        return GroupDeleteResponse(
-            self.api_call(
-                http_verb="DELETE",
-                path=f"Groups/{quote(id)}",
-            )
-        )
-
-    # -------------------------
-
-    def api_call(
-        self,
-        *,
-        http_verb: str,
-        path: str,
-        query_params: Optional[Dict[str, Any]] = None,
-        body_params: Optional[Dict[str, Any]] = None,
-        headers: Optional[Dict[str, str]] = None,
-    ) -> SCIMResponse:
-        """Performs a Slack API request and returns the result."""
-        url = f"{self.base_url}{path}"
-        query = _build_query(query_params)
-        if len(query) > 0:
-            url += f"?{query}"
-
-        return self._perform_http_request(
-            http_verb=http_verb,
-            url=url,
-            body=body_params,
-            headers=_build_request_headers(
-                token=self.token,
-                default_headers=self.default_headers,
-                additional_headers=headers,
-            ),
-        )
-
-    def _perform_http_request(
-        self,
-        *,
-        http_verb: str = "GET",
-        url: str,
-        body: Optional[Dict[str, Any]] = None,
-        headers: Dict[str, str],
-    ) -> SCIMResponse:
-        if body is not None:
-            if body.get("schemas") is None:
-                body["schemas"] = ["urn:scim:schemas:core:1.0"]
-            body = json.dumps(body)
-        headers["Content-Type"] = "application/json;charset=utf-8"
-
-        if self.logger.level <= logging.DEBUG:
-            headers_for_logging = {
-                k: "(redacted)" if k.lower() == "authorization" else v
-                for k, v in headers.items()
-            }
-            self.logger.debug(
-                f"Sending a request - {http_verb} url: {url}, body: {body}, headers: {headers_for_logging}"
-            )
-
-        # NOTE: Intentionally ignore the `http_verb` here
-        # Slack APIs accepts any API method requests with POST methods
-        req = Request(
-            method=http_verb,
-            url=url,
-            data=body.encode("utf-8") if body is not None else None,
-            headers=headers,
-        )
-        resp = None
-        last_error = None
-
-        retry_state = RetryState()
-        counter_for_safety = 0
-        while counter_for_safety < 100:
-            counter_for_safety += 1
-            # If this is a retry, the next try started here. We can reset the flag.
-            retry_state.next_attempt_requested = False
-
-            try:
-                resp = self._perform_http_request_internal(url, req)
-                # The resp is a 200 OK response
-                return resp
-
-            except HTTPError as e:
-                # read the response body here
-                charset = e.headers.get_content_charset() or "utf-8"
-                response_body: str = e.read().decode(charset)
-                resp = SCIMResponse(
-                    url=url,
-                    status_code=e.code,
-                    raw_body=response_body,
-                    headers=e.headers,
-                )
-                if e.code == 429:
-                    # for backward-compatibility with WebClient (v.2.5.0 or older)
-                    resp.headers["Retry-After"] = resp.headers["retry-after"]
-                _debug_log_response(self.logger, resp)
-
-                # Try to find a retry handler for this error
-                retry_request = RetryHttpRequest.from_urllib_http_request(req)
-                retry_response = RetryHttpResponse(
-                    status_code=e.code,
-                    headers={k: [v] for k, v in e.headers.items()},
-                    data=response_body.encode("utf-8")
-                    if response_body is not None
-                    else None,
-                )
-                for handler in self.retry_handlers:
-                    if handler.can_retry(
-                        state=retry_state,
-                        request=retry_request,
-                        response=retry_response,
-                        error=e,
-                    ):
-                        if self.logger.level <= logging.DEBUG:
-                            self.logger.info(
-                                f"A retry handler found: {type(handler).__name__} for {req.method} {req.full_url} - {e}"
-                            )
-                        handler.prepare_for_next_attempt(
-                            state=retry_state,
-                            request=retry_request,
-                            response=retry_response,
-                            error=e,
-                        )
-                        break
-
-                if retry_state.next_attempt_requested is False:
-                    return resp
-
-            except Exception as err:
-                last_error = err
-                self.logger.error(
-                    f"Failed to send a request to Slack API server: {err}"
-                )
-
-                # Try to find a retry handler for this error
-                retry_request = RetryHttpRequest.from_urllib_http_request(req)
-                for handler in self.retry_handlers:
-                    if handler.can_retry(
-                        state=retry_state,
-                        request=retry_request,
-                        response=None,
-                        error=err,
-                    ):
-                        if self.logger.level <= logging.DEBUG:
-                            self.logger.info(
-                                f"A retry handler found: {type(handler).__name__} for {req.method} {req.full_url} - {err}"
-                            )
-                        handler.prepare_for_next_attempt(
-                            state=retry_state,
-                            request=retry_request,
-                            response=None,
-                            error=err,
-                        )
-                        self.logger.info(
-                            f"Going to retry the same request: {req.method} {req.full_url}"
-                        )
-                        break
-
-                if retry_state.next_attempt_requested is False:
-                    raise err
-
-        if resp is not None:
-            return resp
-        raise last_error
-
-    def _perform_http_request_internal(self, url: str, req: Request) -> SCIMResponse:
-        opener: Optional[OpenerDirector] = None
-        # for security (BAN-B310)
-        if url.lower().startswith("http"):
-            if self.proxy is not None:
-                if isinstance(self.proxy, str):
-                    opener = urllib.request.build_opener(
-                        ProxyHandler({"http": self.proxy, "https": self.proxy}),
-                        HTTPSHandler(context=self.ssl),
-                    )
-                else:
-                    raise SlackRequestError(
-                        f"Invalid proxy detected: {self.proxy} must be a str value"
-                    )
-        else:
-            raise SlackRequestError(f"Invalid URL detected: {url}")
-
-        # NOTE: BAN-B310 is already checked above
-        resp: Optional[HTTPResponse] = None
-        if opener:
-            resp = opener.open(req, timeout=self.timeout)  # skipcq: BAN-B310
-        else:
-            resp = urlopen(  # skipcq: BAN-B310
-                req, context=self.ssl, timeout=self.timeout
-            )
-        charset: str = resp.headers.get_content_charset() or "utf-8"
-        response_body: str = resp.read().decode(charset)
-        resp = SCIMResponse(
-            url=url,
-            status_code=resp.status,
-            raw_body=response_body,
-            headers=resp.headers,
-        )
-        _debug_log_response(self.logger, resp)
-        return resp
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class SCIMClient -(token:ย str, timeout:ย intย =ย 30, ssl:ย Optional[ssl.SSLContext]ย =ย None, proxy:ย Optional[str]ย =ย None, base_url:ย strย =ย 'https://api.slack.com/scim/v1/', default_headers:ย Optional[Dict[str,ย str]]ย =ย None, user_agent_prefix:ย Optional[str]ย =ย None, user_agent_suffix:ย Optional[str]ย =ย None, logger:ย Optional[logging.Logger]ย =ย None, retry_handlers:ย Optional[List[RetryHandler]]ย =ย None) -
-
-

API client for SCIM API -See https://api.slack.com/scim for more details

-

Args

-
-
token
-
An admin user's token, which starts with xoxp-
-
timeout
-
Request timeout (in seconds)
-
ssl
-
ssl.SSLContext to use for requests
-
proxy
-
Proxy URL (e.g., localhost:9000, http://localhost:9000)
-
base_url
-
The base URL for API calls
-
default_headers
-
Request headers to add to all requests
-
user_agent_prefix
-
Prefix for User-Agent header value
-
user_agent_suffix
-
Suffix for User-Agent header value
-
logger
-
Custom logger
-
retry_handlers
-
Retry handlers
-
-
- -Expand source code - -
class SCIMClient:
-    BASE_URL = "https://api.slack.com/scim/v1/"
-
-    token: str
-    timeout: int
-    ssl: Optional[SSLContext]
-    proxy: Optional[str]
-    base_url: str
-    default_headers: Dict[str, str]
-    logger: logging.Logger
-    retry_handlers: List[RetryHandler]
-
-    def __init__(
-        self,
-        token: str,
-        timeout: int = 30,
-        ssl: Optional[SSLContext] = None,
-        proxy: Optional[str] = None,
-        base_url: str = BASE_URL,
-        default_headers: Optional[Dict[str, str]] = None,
-        user_agent_prefix: Optional[str] = None,
-        user_agent_suffix: Optional[str] = None,
-        logger: Optional[logging.Logger] = None,
-        retry_handlers: Optional[List[RetryHandler]] = None,
-    ):
-        """API client for SCIM API
-        See https://api.slack.com/scim for more details
-
-        Args:
-            token: An admin user's token, which starts with `xoxp-`
-            timeout: Request timeout (in seconds)
-            ssl: `ssl.SSLContext` to use for requests
-            proxy: Proxy URL (e.g., `localhost:9000`, `http://localhost:9000`)
-            base_url: The base URL for API calls
-            default_headers: Request headers to add to all requests
-            user_agent_prefix: Prefix for User-Agent header value
-            user_agent_suffix: Suffix for User-Agent header value
-            logger: Custom logger
-            retry_handlers: Retry handlers
-        """
-        self.token = token
-        self.timeout = timeout
-        self.ssl = ssl
-        self.proxy = proxy
-        self.base_url = base_url
-        self.default_headers = default_headers if default_headers else {}
-        self.default_headers["User-Agent"] = get_user_agent(
-            user_agent_prefix, user_agent_suffix
-        )
-        self.logger = logger if logger is not None else logging.getLogger(__name__)
-        self.retry_handlers = (
-            retry_handlers if retry_handlers is not None else default_retry_handlers()
-        )
-
-        if self.proxy is None or len(self.proxy.strip()) == 0:
-            env_variable = load_http_proxy_from_env(self.logger)
-            if env_variable is not None:
-                self.proxy = env_variable
-
-    # -------------------------
-    # Users
-    # -------------------------
-
-    def search_users(
-        self,
-        *,
-        # Pagination required as of August 30, 2019.
-        count: int,
-        start_index: int,
-        filter: Optional[str] = None,
-    ) -> SearchUsersResponse:
-        return SearchUsersResponse(
-            self.api_call(
-                http_verb="GET",
-                path="Users",
-                query_params={
-                    "filter": filter,
-                    "count": count,
-                    "startIndex": start_index,
-                },
-            )
-        )
-
-    def read_user(self, id: str) -> ReadUserResponse:
-        return ReadUserResponse(
-            self.api_call(http_verb="GET", path=f"Users/{quote(id)}")
-        )
-
-    def create_user(self, user: Union[Dict[str, Any], User]) -> UserCreateResponse:
-        return UserCreateResponse(
-            self.api_call(
-                http_verb="POST",
-                path="Users",
-                body_params=user.to_dict()
-                if isinstance(user, User)
-                else _to_dict_without_not_given(user),
-            )
-        )
-
-    def patch_user(
-        self, id: str, partial_user: Union[Dict[str, Any], User]
-    ) -> UserPatchResponse:
-        return UserPatchResponse(
-            self.api_call(
-                http_verb="PATCH",
-                path=f"Users/{quote(id)}",
-                body_params=partial_user.to_dict()
-                if isinstance(partial_user, User)
-                else _to_dict_without_not_given(partial_user),
-            )
-        )
-
-    def update_user(self, user: Union[Dict[str, Any], User]) -> UserUpdateResponse:
-        return UserUpdateResponse(
-            self.api_call(
-                http_verb="PUT",
-                path=f"Users/{quote(user.id)}",
-                body_params=user.to_dict()
-                if isinstance(user, User)
-                else _to_dict_without_not_given(user),
-            )
-        )
-
-    def delete_user(self, id: str) -> UserDeleteResponse:
-        return UserDeleteResponse(
-            self.api_call(
-                http_verb="DELETE",
-                path=f"Users/{quote(id)}",
-            )
-        )
-
-    # -------------------------
-    # Groups
-    # -------------------------
-
-    def search_groups(
-        self,
-        *,
-        # Pagination required as of August 30, 2019.
-        count: int,
-        start_index: int,
-        filter: Optional[str] = None,
-    ) -> SearchGroupsResponse:
-        return SearchGroupsResponse(
-            self.api_call(
-                http_verb="GET",
-                path="Groups",
-                query_params={
-                    "filter": filter,
-                    "count": count,
-                    "startIndex": start_index,
-                },
-            )
-        )
-
-    def read_group(self, id: str) -> ReadGroupResponse:
-        return ReadGroupResponse(
-            self.api_call(http_verb="GET", path=f"Groups/{quote(id)}")
-        )
-
-    def create_group(self, group: Union[Dict[str, Any], Group]) -> GroupCreateResponse:
-        return GroupCreateResponse(
-            self.api_call(
-                http_verb="POST",
-                path="Groups",
-                body_params=group.to_dict()
-                if isinstance(group, Group)
-                else _to_dict_without_not_given(group),
-            )
-        )
-
-    def patch_group(
-        self, id: str, partial_group: Union[Dict[str, Any], Group]
-    ) -> GroupPatchResponse:
-        return GroupPatchResponse(
-            self.api_call(
-                http_verb="PATCH",
-                path=f"Groups/{quote(id)}",
-                body_params=partial_group.to_dict()
-                if isinstance(partial_group, Group)
-                else _to_dict_without_not_given(partial_group),
-            )
-        )
-
-    def update_group(self, group: Union[Dict[str, Any], Group]) -> GroupUpdateResponse:
-        return GroupUpdateResponse(
-            self.api_call(
-                http_verb="PUT",
-                path=f"Groups/{quote(group.id)}",
-                body_params=group.to_dict()
-                if isinstance(group, Group)
-                else _to_dict_without_not_given(group),
-            )
-        )
-
-    def delete_group(self, id: str) -> GroupDeleteResponse:
-        return GroupDeleteResponse(
-            self.api_call(
-                http_verb="DELETE",
-                path=f"Groups/{quote(id)}",
-            )
-        )
-
-    # -------------------------
-
-    def api_call(
-        self,
-        *,
-        http_verb: str,
-        path: str,
-        query_params: Optional[Dict[str, Any]] = None,
-        body_params: Optional[Dict[str, Any]] = None,
-        headers: Optional[Dict[str, str]] = None,
-    ) -> SCIMResponse:
-        """Performs a Slack API request and returns the result."""
-        url = f"{self.base_url}{path}"
-        query = _build_query(query_params)
-        if len(query) > 0:
-            url += f"?{query}"
-
-        return self._perform_http_request(
-            http_verb=http_verb,
-            url=url,
-            body=body_params,
-            headers=_build_request_headers(
-                token=self.token,
-                default_headers=self.default_headers,
-                additional_headers=headers,
-            ),
-        )
-
-    def _perform_http_request(
-        self,
-        *,
-        http_verb: str = "GET",
-        url: str,
-        body: Optional[Dict[str, Any]] = None,
-        headers: Dict[str, str],
-    ) -> SCIMResponse:
-        if body is not None:
-            if body.get("schemas") is None:
-                body["schemas"] = ["urn:scim:schemas:core:1.0"]
-            body = json.dumps(body)
-        headers["Content-Type"] = "application/json;charset=utf-8"
-
-        if self.logger.level <= logging.DEBUG:
-            headers_for_logging = {
-                k: "(redacted)" if k.lower() == "authorization" else v
-                for k, v in headers.items()
-            }
-            self.logger.debug(
-                f"Sending a request - {http_verb} url: {url}, body: {body}, headers: {headers_for_logging}"
-            )
-
-        # NOTE: Intentionally ignore the `http_verb` here
-        # Slack APIs accepts any API method requests with POST methods
-        req = Request(
-            method=http_verb,
-            url=url,
-            data=body.encode("utf-8") if body is not None else None,
-            headers=headers,
-        )
-        resp = None
-        last_error = None
-
-        retry_state = RetryState()
-        counter_for_safety = 0
-        while counter_for_safety < 100:
-            counter_for_safety += 1
-            # If this is a retry, the next try started here. We can reset the flag.
-            retry_state.next_attempt_requested = False
-
-            try:
-                resp = self._perform_http_request_internal(url, req)
-                # The resp is a 200 OK response
-                return resp
-
-            except HTTPError as e:
-                # read the response body here
-                charset = e.headers.get_content_charset() or "utf-8"
-                response_body: str = e.read().decode(charset)
-                resp = SCIMResponse(
-                    url=url,
-                    status_code=e.code,
-                    raw_body=response_body,
-                    headers=e.headers,
-                )
-                if e.code == 429:
-                    # for backward-compatibility with WebClient (v.2.5.0 or older)
-                    resp.headers["Retry-After"] = resp.headers["retry-after"]
-                _debug_log_response(self.logger, resp)
-
-                # Try to find a retry handler for this error
-                retry_request = RetryHttpRequest.from_urllib_http_request(req)
-                retry_response = RetryHttpResponse(
-                    status_code=e.code,
-                    headers={k: [v] for k, v in e.headers.items()},
-                    data=response_body.encode("utf-8")
-                    if response_body is not None
-                    else None,
-                )
-                for handler in self.retry_handlers:
-                    if handler.can_retry(
-                        state=retry_state,
-                        request=retry_request,
-                        response=retry_response,
-                        error=e,
-                    ):
-                        if self.logger.level <= logging.DEBUG:
-                            self.logger.info(
-                                f"A retry handler found: {type(handler).__name__} for {req.method} {req.full_url} - {e}"
-                            )
-                        handler.prepare_for_next_attempt(
-                            state=retry_state,
-                            request=retry_request,
-                            response=retry_response,
-                            error=e,
-                        )
-                        break
-
-                if retry_state.next_attempt_requested is False:
-                    return resp
-
-            except Exception as err:
-                last_error = err
-                self.logger.error(
-                    f"Failed to send a request to Slack API server: {err}"
-                )
-
-                # Try to find a retry handler for this error
-                retry_request = RetryHttpRequest.from_urllib_http_request(req)
-                for handler in self.retry_handlers:
-                    if handler.can_retry(
-                        state=retry_state,
-                        request=retry_request,
-                        response=None,
-                        error=err,
-                    ):
-                        if self.logger.level <= logging.DEBUG:
-                            self.logger.info(
-                                f"A retry handler found: {type(handler).__name__} for {req.method} {req.full_url} - {err}"
-                            )
-                        handler.prepare_for_next_attempt(
-                            state=retry_state,
-                            request=retry_request,
-                            response=None,
-                            error=err,
-                        )
-                        self.logger.info(
-                            f"Going to retry the same request: {req.method} {req.full_url}"
-                        )
-                        break
-
-                if retry_state.next_attempt_requested is False:
-                    raise err
-
-        if resp is not None:
-            return resp
-        raise last_error
-
-    def _perform_http_request_internal(self, url: str, req: Request) -> SCIMResponse:
-        opener: Optional[OpenerDirector] = None
-        # for security (BAN-B310)
-        if url.lower().startswith("http"):
-            if self.proxy is not None:
-                if isinstance(self.proxy, str):
-                    opener = urllib.request.build_opener(
-                        ProxyHandler({"http": self.proxy, "https": self.proxy}),
-                        HTTPSHandler(context=self.ssl),
-                    )
-                else:
-                    raise SlackRequestError(
-                        f"Invalid proxy detected: {self.proxy} must be a str value"
-                    )
-        else:
-            raise SlackRequestError(f"Invalid URL detected: {url}")
-
-        # NOTE: BAN-B310 is already checked above
-        resp: Optional[HTTPResponse] = None
-        if opener:
-            resp = opener.open(req, timeout=self.timeout)  # skipcq: BAN-B310
-        else:
-            resp = urlopen(  # skipcq: BAN-B310
-                req, context=self.ssl, timeout=self.timeout
-            )
-        charset: str = resp.headers.get_content_charset() or "utf-8"
-        response_body: str = resp.read().decode(charset)
-        resp = SCIMResponse(
-            url=url,
-            status_code=resp.status,
-            raw_body=response_body,
-            headers=resp.headers,
-        )
-        _debug_log_response(self.logger, resp)
-        return resp
-
-

Class variables

-
-
var BASE_URL
-
-
-
-
var base_url :ย str
-
-
-
-
var default_headers :ย Dict[str,ย str]
-
-
-
-
var logger :ย logging.Logger
-
-
-
-
var proxy :ย Optional[str]
-
-
-
-
var retry_handlers :ย List[RetryHandler]
-
-
-
-
var ssl :ย Optional[ssl.SSLContext]
-
-
-
-
var timeout :ย int
-
-
-
-
var token :ย str
-
-
-
-
-

Methods

-
-
-def api_call(self, *, http_verb:ย str, path:ย str, query_params:ย Optional[Dict[str,ย Any]]ย =ย None, body_params:ย Optional[Dict[str,ย Any]]ย =ย None, headers:ย Optional[Dict[str,ย str]]ย =ย None) โ€‘>ย SCIMResponse -
-
-

Performs a Slack API request and returns the result.

-
- -Expand source code - -
def api_call(
-    self,
-    *,
-    http_verb: str,
-    path: str,
-    query_params: Optional[Dict[str, Any]] = None,
-    body_params: Optional[Dict[str, Any]] = None,
-    headers: Optional[Dict[str, str]] = None,
-) -> SCIMResponse:
-    """Performs a Slack API request and returns the result."""
-    url = f"{self.base_url}{path}"
-    query = _build_query(query_params)
-    if len(query) > 0:
-        url += f"?{query}"
-
-    return self._perform_http_request(
-        http_verb=http_verb,
-        url=url,
-        body=body_params,
-        headers=_build_request_headers(
-            token=self.token,
-            default_headers=self.default_headers,
-            additional_headers=headers,
-        ),
-    )
-
-
-
-def create_group(self, group:ย Union[Dict[str,ย Any],ย Group]) โ€‘>ย GroupCreateResponse -
-
-
-
- -Expand source code - -
def create_group(self, group: Union[Dict[str, Any], Group]) -> GroupCreateResponse:
-    return GroupCreateResponse(
-        self.api_call(
-            http_verb="POST",
-            path="Groups",
-            body_params=group.to_dict()
-            if isinstance(group, Group)
-            else _to_dict_without_not_given(group),
-        )
-    )
-
-
-
-def create_user(self, user:ย Union[Dict[str,ย Any],ย User]) โ€‘>ย UserCreateResponse -
-
-
-
- -Expand source code - -
def create_user(self, user: Union[Dict[str, Any], User]) -> UserCreateResponse:
-    return UserCreateResponse(
-        self.api_call(
-            http_verb="POST",
-            path="Users",
-            body_params=user.to_dict()
-            if isinstance(user, User)
-            else _to_dict_without_not_given(user),
-        )
-    )
-
-
-
-def delete_group(self, id:ย str) โ€‘>ย GroupDeleteResponse -
-
-
-
- -Expand source code - -
def delete_group(self, id: str) -> GroupDeleteResponse:
-    return GroupDeleteResponse(
-        self.api_call(
-            http_verb="DELETE",
-            path=f"Groups/{quote(id)}",
-        )
-    )
-
-
-
-def delete_user(self, id:ย str) โ€‘>ย UserDeleteResponse -
-
-
-
- -Expand source code - -
def delete_user(self, id: str) -> UserDeleteResponse:
-    return UserDeleteResponse(
-        self.api_call(
-            http_verb="DELETE",
-            path=f"Users/{quote(id)}",
-        )
-    )
-
-
-
-def patch_group(self, id:ย str, partial_group:ย Union[Dict[str,ย Any],ย Group]) โ€‘>ย GroupPatchResponse -
-
-
-
- -Expand source code - -
def patch_group(
-    self, id: str, partial_group: Union[Dict[str, Any], Group]
-) -> GroupPatchResponse:
-    return GroupPatchResponse(
-        self.api_call(
-            http_verb="PATCH",
-            path=f"Groups/{quote(id)}",
-            body_params=partial_group.to_dict()
-            if isinstance(partial_group, Group)
-            else _to_dict_without_not_given(partial_group),
-        )
-    )
-
-
-
-def patch_user(self, id:ย str, partial_user:ย Union[Dict[str,ย Any],ย User]) โ€‘>ย UserPatchResponse -
-
-
-
- -Expand source code - -
def patch_user(
-    self, id: str, partial_user: Union[Dict[str, Any], User]
-) -> UserPatchResponse:
-    return UserPatchResponse(
-        self.api_call(
-            http_verb="PATCH",
-            path=f"Users/{quote(id)}",
-            body_params=partial_user.to_dict()
-            if isinstance(partial_user, User)
-            else _to_dict_without_not_given(partial_user),
-        )
-    )
-
-
-
-def read_group(self, id:ย str) โ€‘>ย ReadGroupResponse -
-
-
-
- -Expand source code - -
def read_group(self, id: str) -> ReadGroupResponse:
-    return ReadGroupResponse(
-        self.api_call(http_verb="GET", path=f"Groups/{quote(id)}")
-    )
-
-
-
-def read_user(self, id:ย str) โ€‘>ย ReadUserResponse -
-
-
-
- -Expand source code - -
def read_user(self, id: str) -> ReadUserResponse:
-    return ReadUserResponse(
-        self.api_call(http_verb="GET", path=f"Users/{quote(id)}")
-    )
-
-
-
-def search_groups(self, *, count:ย int, start_index:ย int, filter:ย Optional[str]ย =ย None) โ€‘>ย SearchGroupsResponse -
-
-
-
- -Expand source code - -
def search_groups(
-    self,
-    *,
-    # Pagination required as of August 30, 2019.
-    count: int,
-    start_index: int,
-    filter: Optional[str] = None,
-) -> SearchGroupsResponse:
-    return SearchGroupsResponse(
-        self.api_call(
-            http_verb="GET",
-            path="Groups",
-            query_params={
-                "filter": filter,
-                "count": count,
-                "startIndex": start_index,
-            },
-        )
-    )
-
-
-
-def search_users(self, *, count:ย int, start_index:ย int, filter:ย Optional[str]ย =ย None) โ€‘>ย SearchUsersResponse -
-
-
-
- -Expand source code - -
def search_users(
-    self,
-    *,
-    # Pagination required as of August 30, 2019.
-    count: int,
-    start_index: int,
-    filter: Optional[str] = None,
-) -> SearchUsersResponse:
-    return SearchUsersResponse(
-        self.api_call(
-            http_verb="GET",
-            path="Users",
-            query_params={
-                "filter": filter,
-                "count": count,
-                "startIndex": start_index,
-            },
-        )
-    )
-
-
-
-def update_group(self, group:ย Union[Dict[str,ย Any],ย Group]) โ€‘>ย GroupUpdateResponse -
-
-
-
- -Expand source code - -
def update_group(self, group: Union[Dict[str, Any], Group]) -> GroupUpdateResponse:
-    return GroupUpdateResponse(
-        self.api_call(
-            http_verb="PUT",
-            path=f"Groups/{quote(group.id)}",
-            body_params=group.to_dict()
-            if isinstance(group, Group)
-            else _to_dict_without_not_given(group),
-        )
-    )
-
-
-
-def update_user(self, user:ย Union[Dict[str,ย Any],ย User]) โ€‘>ย UserUpdateResponse -
-
-
-
- -Expand source code - -
def update_user(self, user: Union[Dict[str, Any], User]) -> UserUpdateResponse:
-    return UserUpdateResponse(
-        self.api_call(
-            http_verb="PUT",
-            path=f"Users/{quote(user.id)}",
-            body_params=user.to_dict()
-            if isinstance(user, User)
-            else _to_dict_without_not_given(user),
-        )
-    )
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/scim/v1/default_arg.html b/docs/api-docs/slack_sdk/scim/v1/default_arg.html deleted file mode 100644 index c1d60a438..000000000 --- a/docs/api-docs/slack_sdk/scim/v1/default_arg.html +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - -slack_sdk.scim.v1.default_arg API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.scim.v1.default_arg

-
-
-
- -Expand source code - -
class DefaultArg:
-    pass
-
-
-NotGiven = DefaultArg()
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class DefaultArg -
-
-
-
- -Expand source code - -
class DefaultArg:
-    pass
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/scim/v1/group.html b/docs/api-docs/slack_sdk/scim/v1/group.html deleted file mode 100644 index cde91cd22..000000000 --- a/docs/api-docs/slack_sdk/scim/v1/group.html +++ /dev/null @@ -1,390 +0,0 @@ - - - - - - -slack_sdk.scim.v1.group API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.scim.v1.group

-
-
-
- -Expand source code - -
from typing import Optional, List, Union, Dict, Any
-
-from .default_arg import DefaultArg, NotGiven
-from .internal_utils import _to_dict_without_not_given, _is_iterable
-
-
-class GroupMember:
-    display: Union[Optional[str], DefaultArg]
-    value: Union[Optional[str], DefaultArg]
-    unknown_fields: Dict[str, Any]
-
-    def __init__(
-        self,
-        *,
-        display: Union[Optional[str], DefaultArg] = NotGiven,
-        value: Union[Optional[str], DefaultArg] = NotGiven,
-        **kwargs,
-    ) -> None:
-        self.display = display
-        self.value = value
-        self.unknown_fields = kwargs
-
-    def to_dict(self):
-        return _to_dict_without_not_given(self)
-
-
-class GroupMeta:
-    created: Union[Optional[str], DefaultArg]
-    location: Union[Optional[str], DefaultArg]
-    unknown_fields: Dict[str, Any]
-
-    def __init__(
-        self,
-        *,
-        created: Union[Optional[str], DefaultArg] = NotGiven,
-        location: Union[Optional[str], DefaultArg] = NotGiven,
-        **kwargs,
-    ) -> None:
-        self.created = created
-        self.location = location
-        self.unknown_fields = kwargs
-
-    def to_dict(self):
-        return _to_dict_without_not_given(self)
-
-
-class Group:
-    display_name: Union[Optional[str], DefaultArg]
-    id: Union[Optional[str], DefaultArg]
-    members: Union[Optional[List[GroupMember]], DefaultArg]
-    meta: Union[Optional[GroupMeta], DefaultArg]
-    schemas: Union[Optional[List[str]], DefaultArg]
-    unknown_fields: Dict[str, Any]
-
-    def __init__(
-        self,
-        *,
-        display_name: Union[Optional[str], DefaultArg] = NotGiven,
-        id: Union[Optional[str], DefaultArg] = NotGiven,
-        members: Union[Optional[List[GroupMember]], DefaultArg] = NotGiven,
-        meta: Union[Optional[GroupMeta], DefaultArg] = NotGiven,
-        schemas: Union[Optional[List[str]], DefaultArg] = NotGiven,
-        **kwargs,
-    ) -> None:
-        self.display_name = display_name
-        self.id = id
-        self.members = (
-            [a if isinstance(a, GroupMember) else GroupMember(**a) for a in members]
-            if _is_iterable(members)
-            else members
-        )
-        self.meta = (
-            GroupMeta(**meta) if meta is not None and isinstance(meta, dict) else meta
-        )
-        self.schemas = schemas
-        self.unknown_fields = kwargs
-
-    def to_dict(self):
-        return _to_dict_without_not_given(self)
-
-    def __repr__(self):
-        return f"<slack_sdk.scim.{self.__class__.__name__}: {self.to_dict()}>"
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class Group -(*, display_name:ย Union[str,ย ForwardRef(None),ย DefaultArg]ย =ย <slack_sdk.scim.v1.default_arg.DefaultArg object>, id:ย Union[str,ย ForwardRef(None),ย DefaultArg]ย =ย <slack_sdk.scim.v1.default_arg.DefaultArg object>, members:ย Union[List[GroupMember],ย ForwardRef(None),ย DefaultArg]ย =ย <slack_sdk.scim.v1.default_arg.DefaultArg object>, meta:ย Union[GroupMeta,ย ForwardRef(None),ย DefaultArg]ย =ย <slack_sdk.scim.v1.default_arg.DefaultArg object>, schemas:ย Union[List[str],ย ForwardRef(None),ย DefaultArg]ย =ย <slack_sdk.scim.v1.default_arg.DefaultArg object>, **kwargs) -
-
-
-
- -Expand source code - -
class Group:
-    display_name: Union[Optional[str], DefaultArg]
-    id: Union[Optional[str], DefaultArg]
-    members: Union[Optional[List[GroupMember]], DefaultArg]
-    meta: Union[Optional[GroupMeta], DefaultArg]
-    schemas: Union[Optional[List[str]], DefaultArg]
-    unknown_fields: Dict[str, Any]
-
-    def __init__(
-        self,
-        *,
-        display_name: Union[Optional[str], DefaultArg] = NotGiven,
-        id: Union[Optional[str], DefaultArg] = NotGiven,
-        members: Union[Optional[List[GroupMember]], DefaultArg] = NotGiven,
-        meta: Union[Optional[GroupMeta], DefaultArg] = NotGiven,
-        schemas: Union[Optional[List[str]], DefaultArg] = NotGiven,
-        **kwargs,
-    ) -> None:
-        self.display_name = display_name
-        self.id = id
-        self.members = (
-            [a if isinstance(a, GroupMember) else GroupMember(**a) for a in members]
-            if _is_iterable(members)
-            else members
-        )
-        self.meta = (
-            GroupMeta(**meta) if meta is not None and isinstance(meta, dict) else meta
-        )
-        self.schemas = schemas
-        self.unknown_fields = kwargs
-
-    def to_dict(self):
-        return _to_dict_without_not_given(self)
-
-    def __repr__(self):
-        return f"<slack_sdk.scim.{self.__class__.__name__}: {self.to_dict()}>"
-
-

Class variables

-
-
var display_name :ย Union[str,ย ForwardRef(None),ย DefaultArg]
-
-
-
-
var id :ย Union[str,ย ForwardRef(None),ย DefaultArg]
-
-
-
-
var members :ย Union[List[GroupMember],ย ForwardRef(None),ย DefaultArg]
-
-
-
-
var meta :ย Union[GroupMeta,ย ForwardRef(None),ย DefaultArg]
-
-
-
-
var schemas :ย Union[List[str],ย ForwardRef(None),ย DefaultArg]
-
-
-
-
var unknown_fields :ย Dict[str,ย Any]
-
-
-
-
-

Methods

-
-
-def to_dict(self) -
-
-
-
- -Expand source code - -
def to_dict(self):
-    return _to_dict_without_not_given(self)
-
-
-
-
-
-class GroupMember -(*, display:ย Union[str,ย ForwardRef(None),ย DefaultArg]ย =ย <slack_sdk.scim.v1.default_arg.DefaultArg object>, value:ย Union[str,ย ForwardRef(None),ย DefaultArg]ย =ย <slack_sdk.scim.v1.default_arg.DefaultArg object>, **kwargs) -
-
-
-
- -Expand source code - -
class GroupMember:
-    display: Union[Optional[str], DefaultArg]
-    value: Union[Optional[str], DefaultArg]
-    unknown_fields: Dict[str, Any]
-
-    def __init__(
-        self,
-        *,
-        display: Union[Optional[str], DefaultArg] = NotGiven,
-        value: Union[Optional[str], DefaultArg] = NotGiven,
-        **kwargs,
-    ) -> None:
-        self.display = display
-        self.value = value
-        self.unknown_fields = kwargs
-
-    def to_dict(self):
-        return _to_dict_without_not_given(self)
-
-

Class variables

-
-
var display :ย Union[str,ย ForwardRef(None),ย DefaultArg]
-
-
-
-
var unknown_fields :ย Dict[str,ย Any]
-
-
-
-
var value :ย Union[str,ย ForwardRef(None),ย DefaultArg]
-
-
-
-
-

Methods

-
-
-def to_dict(self) -
-
-
-
- -Expand source code - -
def to_dict(self):
-    return _to_dict_without_not_given(self)
-
-
-
-
-
-class GroupMeta -(*, created:ย Union[str,ย ForwardRef(None),ย DefaultArg]ย =ย <slack_sdk.scim.v1.default_arg.DefaultArg object>, location:ย Union[str,ย ForwardRef(None),ย DefaultArg]ย =ย <slack_sdk.scim.v1.default_arg.DefaultArg object>, **kwargs) -
-
-
-
- -Expand source code - -
class GroupMeta:
-    created: Union[Optional[str], DefaultArg]
-    location: Union[Optional[str], DefaultArg]
-    unknown_fields: Dict[str, Any]
-
-    def __init__(
-        self,
-        *,
-        created: Union[Optional[str], DefaultArg] = NotGiven,
-        location: Union[Optional[str], DefaultArg] = NotGiven,
-        **kwargs,
-    ) -> None:
-        self.created = created
-        self.location = location
-        self.unknown_fields = kwargs
-
-    def to_dict(self):
-        return _to_dict_without_not_given(self)
-
-

Class variables

-
-
var created :ย Union[str,ย ForwardRef(None),ย DefaultArg]
-
-
-
-
var location :ย Union[str,ย ForwardRef(None),ย DefaultArg]
-
-
-
-
var unknown_fields :ย Dict[str,ย Any]
-
-
-
-
-

Methods

-
-
-def to_dict(self) -
-
-
-
- -Expand source code - -
def to_dict(self):
-    return _to_dict_without_not_given(self)
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/scim/v1/index.html b/docs/api-docs/slack_sdk/scim/v1/index.html deleted file mode 100644 index 276bf7fa2..000000000 --- a/docs/api-docs/slack_sdk/scim/v1/index.html +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - -slack_sdk.scim.v1 API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.scim.v1

-
-
-

SCIM API is a set of APIs for provisioning and managing user accounts and groups. -SCIM is used by Single Sign-On (SSO) services and identity providers to manage people across a variety of tools, -including Slack.

-

Refer to https://slack.dev/python-slack-sdk/scim/ for details.

-
- -Expand source code - -
"""SCIM API is a set of APIs for provisioning and managing user accounts and groups.
-SCIM is used by Single Sign-On (SSO) services and identity providers to manage people across a variety of tools,
-including Slack.
-
-Refer to https://slack.dev/python-slack-sdk/scim/ for details.
-"""
-
-
-
-

Sub-modules

-
-
slack_sdk.scim.v1.async_client
-
-
-
-
slack_sdk.scim.v1.client
-
-

SCIM API is a set of APIs for provisioning and managing user accounts and groups. -SCIM is used by Single Sign-On (SSO) services and identity providers โ€ฆ

-
-
slack_sdk.scim.v1.default_arg
-
-
-
-
slack_sdk.scim.v1.group
-
-
-
-
slack_sdk.scim.v1.internal_utils
-
-
-
-
slack_sdk.scim.v1.response
-
-
-
-
slack_sdk.scim.v1.types
-
-
-
-
slack_sdk.scim.v1.user
-
-
-
-
-
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/scim/v1/internal_utils.html b/docs/api-docs/slack_sdk/scim/v1/internal_utils.html deleted file mode 100644 index 977a86f07..000000000 --- a/docs/api-docs/slack_sdk/scim/v1/internal_utils.html +++ /dev/null @@ -1,216 +0,0 @@ - - - - - - -slack_sdk.scim.v1.internal_utils API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.scim.v1.internal_utils

-
-
-
- -Expand source code - -
import copy
-import logging
-import re
-import sys
-from typing import Dict, Callable
-from typing import Union, Optional, Any
-from urllib.parse import quote
-
-from .default_arg import DefaultArg, NotGiven
-from slack_sdk.web.internal_utils import get_user_agent
-
-
-def _build_query(params: Optional[Dict[str, Any]]) -> str:
-    if params is not None and len(params) > 0:
-        return "&".join(
-            {
-                f"{quote(str(k))}={quote(str(v))}"
-                for k, v in params.items()
-                if v is not None
-            }
-        )
-    return ""
-
-
-def _is_iterable(obj: Union[Optional[Any], DefaultArg]) -> bool:
-    return obj is not None and obj is not NotGiven
-
-
-def _to_dict_without_not_given(obj: Any) -> dict:
-    dict_value = {}
-    given_dict = obj if isinstance(obj, dict) else vars(obj)
-    for key, value in given_dict.items():
-        if key == "unknown_fields":
-            if value is not None:
-                converted = _to_dict_without_not_given(value)
-                dict_value.update(converted)
-            continue
-
-        dict_key = _to_camel_case_key(key)
-        if value is NotGiven:
-            continue
-        if isinstance(value, list):
-            dict_value[dict_key] = [
-                elem.to_dict() if hasattr(elem, "to_dict") else elem for elem in value
-            ]
-        elif isinstance(value, dict):
-            dict_value[dict_key] = _to_dict_without_not_given(value)
-        else:
-            dict_value[dict_key] = (
-                value.to_dict() if hasattr(value, "to_dict") else value
-            )
-    return dict_value
-
-
-def _create_copy(original: Any) -> Any:
-    if sys.version_info.major == 3 and sys.version_info.minor <= 6:
-        return copy.copy(original)
-    else:
-        return copy.deepcopy(original)
-
-
-def _to_camel_case_key(key: str) -> str:
-    next_to_capital = False
-    result = ""
-    for c in key:
-        if c == "_":
-            next_to_capital = True
-        elif next_to_capital:
-            result += c.upper()
-            next_to_capital = False
-        else:
-            result += c
-    return result
-
-
-def _to_snake_cased(original: Optional[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
-    return _convert_dict_keys(
-        original,
-        {},
-        lambda s: re.sub(
-            "^_",
-            "",
-            "".join(["_" + c.lower() if c.isupper() else c for c in s]),
-        ),
-    )
-
-
-def _to_camel_cased(original: Optional[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
-    return _convert_dict_keys(
-        original,
-        {},
-        _to_camel_case_key,
-    )
-
-
-def _convert_dict_keys(
-    original_dict: Optional[Dict[str, Any]],
-    result_dict: Dict[str, Any],
-    convert: Callable[[str], str],
-) -> Optional[Dict[str, Any]]:
-    if original_dict is None:
-        return result_dict
-
-    for original_key, original_value in original_dict.items():
-        new_key = convert(original_key)
-        if isinstance(original_value, dict):
-            result_dict[new_key] = {}
-            new_value = _convert_dict_keys(
-                original_value, result_dict[new_key], convert
-            )
-            result_dict[new_key] = new_value
-        elif isinstance(original_value, list):
-            result_dict[new_key] = []
-            is_dict = len(original_value) > 0 and isinstance(original_value[0], dict)
-            for element in original_value:
-                if is_dict:
-                    if isinstance(element, dict):
-                        new_element = {}
-                        for elem_key, elem_value in element.items():
-                            new_element[convert(elem_key)] = (
-                                _convert_dict_keys(elem_value, {}, convert)
-                                if isinstance(elem_value, dict)
-                                else _create_copy(elem_value)
-                            )
-                        result_dict[new_key].append(new_element)
-                else:
-                    result_dict[new_key].append(_create_copy(original_value))
-        else:
-            result_dict[new_key] = _create_copy(original_value)
-    return result_dict
-
-
-def _build_request_headers(
-    token: str,
-    default_headers: Dict[str, str],
-    additional_headers: Optional[Dict[str, str]],
-) -> Dict[str, str]:
-    request_headers = {
-        "Content-Type": "application/json;charset=utf-8",
-        "Authorization": f"Bearer {token}",
-    }
-    if default_headers is None or "User-Agent" not in default_headers:
-        request_headers["User-Agent"] = get_user_agent()
-    if default_headers is not None:
-        request_headers.update(default_headers)
-    if additional_headers is not None:
-        request_headers.update(additional_headers)
-    return request_headers
-
-
-def _debug_log_response(logger, resp: "SCIMResponse") -> None:  # noqa: F821
-    if logger.level <= logging.DEBUG:
-        logger.debug(
-            "Received the following response - "
-            f"status: {resp.status_code}, "
-            f"headers: {(dict(resp.headers))}, "
-            f"body: {resp.raw_body}"
-        )
-
-
-
-
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/scim/v1/types.html b/docs/api-docs/slack_sdk/scim/v1/types.html deleted file mode 100644 index f7999b458..000000000 --- a/docs/api-docs/slack_sdk/scim/v1/types.html +++ /dev/null @@ -1,176 +0,0 @@ - - - - - - -slack_sdk.scim.v1.types API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.scim.v1.types

-
-
-
- -Expand source code - -
from typing import Optional, Union, Dict, Any
-
-from .default_arg import DefaultArg, NotGiven
-from .internal_utils import _to_dict_without_not_given
-
-
-class TypeAndValue:
-    primary: Union[Optional[bool], DefaultArg]
-    type: Union[Optional[str], DefaultArg]
-    value: Union[Optional[str], DefaultArg]
-    unknown_fields: Dict[str, Any]
-
-    def __init__(
-        self,
-        *,
-        primary: Union[Optional[bool], DefaultArg] = NotGiven,
-        type: Union[Optional[str], DefaultArg] = NotGiven,
-        value: Union[Optional[str], DefaultArg] = NotGiven,
-        **kwargs,
-    ) -> None:
-        self.primary = primary
-        self.type = type
-        self.value = value
-        self.unknown_fields = kwargs
-
-    def to_dict(self) -> dict:
-        return _to_dict_without_not_given(self)
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class TypeAndValue -(*, primary:ย Union[bool,ย ForwardRef(None),ย DefaultArg]ย =ย <slack_sdk.scim.v1.default_arg.DefaultArg object>, type:ย Union[str,ย ForwardRef(None),ย DefaultArg]ย =ย <slack_sdk.scim.v1.default_arg.DefaultArg object>, value:ย Union[str,ย ForwardRef(None),ย DefaultArg]ย =ย <slack_sdk.scim.v1.default_arg.DefaultArg object>, **kwargs) -
-
-
-
- -Expand source code - -
class TypeAndValue:
-    primary: Union[Optional[bool], DefaultArg]
-    type: Union[Optional[str], DefaultArg]
-    value: Union[Optional[str], DefaultArg]
-    unknown_fields: Dict[str, Any]
-
-    def __init__(
-        self,
-        *,
-        primary: Union[Optional[bool], DefaultArg] = NotGiven,
-        type: Union[Optional[str], DefaultArg] = NotGiven,
-        value: Union[Optional[str], DefaultArg] = NotGiven,
-        **kwargs,
-    ) -> None:
-        self.primary = primary
-        self.type = type
-        self.value = value
-        self.unknown_fields = kwargs
-
-    def to_dict(self) -> dict:
-        return _to_dict_without_not_given(self)
-
-

Subclasses

- -

Class variables

-
-
var primary :ย Union[bool,ย ForwardRef(None),ย DefaultArg]
-
-
-
-
var type :ย Union[str,ย ForwardRef(None),ย DefaultArg]
-
-
-
-
var unknown_fields :ย Dict[str,ย Any]
-
-
-
-
var value :ย Union[str,ย ForwardRef(None),ย DefaultArg]
-
-
-
-
-

Methods

-
-
-def to_dict(self) โ€‘>ย dict -
-
-
-
- -Expand source code - -
def to_dict(self) -> dict:
-    return _to_dict_without_not_given(self)
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/scim/v1/user.html b/docs/api-docs/slack_sdk/scim/v1/user.html deleted file mode 100644 index 743d1d7cd..000000000 --- a/docs/api-docs/slack_sdk/scim/v1/user.html +++ /dev/null @@ -1,1068 +0,0 @@ - - - - - - -slack_sdk.scim.v1.user API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.scim.v1.user

-
-
-
- -Expand source code - -
from typing import Optional, Any, List, Dict, Union
-
-from .default_arg import DefaultArg, NotGiven
-from .internal_utils import _to_dict_without_not_given, _is_iterable
-from .types import TypeAndValue
-
-
-class UserAddress:
-    country: Union[Optional[str], DefaultArg]
-    locality: Union[Optional[str], DefaultArg]
-    postal_code: Union[Optional[str], DefaultArg]
-    primary: Union[Optional[bool], DefaultArg]
-    region: Union[Optional[str], DefaultArg]
-    street_address: Union[Optional[str], DefaultArg]
-    unknown_fields: Dict[str, Any]
-
-    def __init__(
-        self,
-        *,
-        country: Union[Optional[str], DefaultArg] = NotGiven,
-        locality: Union[Optional[str], DefaultArg] = NotGiven,
-        postal_code: Union[Optional[str], DefaultArg] = NotGiven,
-        primary: Union[Optional[bool], DefaultArg] = NotGiven,
-        region: Union[Optional[str], DefaultArg] = NotGiven,
-        street_address: Union[Optional[str], DefaultArg] = NotGiven,
-        **kwargs,
-    ) -> None:
-        self.country = country
-        self.locality = locality
-        self.postal_code = postal_code
-        self.primary = primary
-        self.region = region
-        self.street_address = street_address
-        self.unknown_fields = kwargs
-
-    def to_dict(self) -> dict:
-        return _to_dict_without_not_given(self)
-
-
-class UserEmail(TypeAndValue):
-    pass
-
-
-class UserPhoneNumber(TypeAndValue):
-    pass
-
-
-class UserRole(TypeAndValue):
-    pass
-
-
-class UserGroup:
-    display: Union[Optional[str], DefaultArg]
-    value: Union[Optional[str], DefaultArg]
-    unknown_fields: Dict[str, Any]
-
-    def __init__(
-        self,
-        *,
-        display: Union[Optional[str], DefaultArg] = NotGiven,
-        value: Union[Optional[str], DefaultArg] = NotGiven,
-        **kwargs,
-    ) -> None:
-        self.display = display
-        self.value = value
-        self.unknown_fields = kwargs
-
-    def to_dict(self) -> dict:
-        return _to_dict_without_not_given(self)
-
-
-class UserMeta:
-    created: Union[Optional[str], DefaultArg]
-    location: Union[Optional[str], DefaultArg]
-    unknown_fields: Dict[str, Any]
-
-    def __init__(
-        self,
-        created: Union[Optional[str], DefaultArg] = NotGiven,
-        location: Union[Optional[str], DefaultArg] = NotGiven,
-        **kwargs,
-    ) -> None:
-        self.created = created
-        self.location = location
-        self.unknown_fields = kwargs
-
-    def to_dict(self) -> dict:
-        return _to_dict_without_not_given(self)
-
-
-class UserName:
-    family_name: Union[Optional[str], DefaultArg]
-    given_name: Union[Optional[str], DefaultArg]
-    unknown_fields: Dict[str, Any]
-
-    def __init__(
-        self,
-        family_name: Union[Optional[str], DefaultArg] = NotGiven,
-        given_name: Union[Optional[str], DefaultArg] = NotGiven,
-        **kwargs,
-    ) -> None:
-        self.family_name = family_name
-        self.given_name = given_name
-        self.unknown_fields = kwargs
-
-    def to_dict(self) -> dict:
-        return _to_dict_without_not_given(self)
-
-
-class UserPhoto:
-    type: Union[Optional[str], DefaultArg]
-    value: Union[Optional[str], DefaultArg]
-    unknown_fields: Dict[str, Any]
-
-    def __init__(
-        self,
-        type: Union[Optional[str], DefaultArg] = NotGiven,
-        value: Union[Optional[str], DefaultArg] = NotGiven,
-        **kwargs,
-    ) -> None:
-        self.type = type
-        self.value = value
-        self.unknown_fields = kwargs
-
-    def to_dict(self) -> dict:
-        return _to_dict_without_not_given(self)
-
-
-class User:
-    active: Union[Optional[bool], DefaultArg]
-    addresses: Union[Optional[List[UserAddress]], DefaultArg]
-    display_name: Union[Optional[str], DefaultArg]
-    emails: Union[Optional[List[TypeAndValue]], DefaultArg]
-    external_id: Union[Optional[str], DefaultArg]
-    groups: Union[Optional[List[UserGroup]], DefaultArg]
-    id: Union[Optional[str], DefaultArg]
-    meta: Union[Optional[UserMeta], DefaultArg]
-    name: Union[Optional[UserName], DefaultArg]
-    nick_name: Union[Optional[str], DefaultArg]
-    phone_numbers: Union[Optional[List[TypeAndValue]], DefaultArg]
-    photos: Union[Optional[List[UserPhoto]], DefaultArg]
-    profile_url: Union[Optional[str], DefaultArg]
-    roles: Union[Optional[List[TypeAndValue]], DefaultArg]
-    schemas: Union[Optional[List[str]], DefaultArg]
-    timezone: Union[Optional[str], DefaultArg]
-    title: Union[Optional[str], DefaultArg]
-    user_name: Union[Optional[str], DefaultArg]
-    unknown_fields: Dict[str, Any]
-
-    def __init__(
-        self,
-        *,
-        active: Union[Optional[bool], DefaultArg] = NotGiven,
-        addresses: Union[
-            Optional[List[Union[UserAddress, Dict[str, Any]]]], DefaultArg
-        ] = NotGiven,
-        display_name: Union[Optional[str], DefaultArg] = NotGiven,
-        emails: Union[
-            Optional[List[Union[TypeAndValue, Dict[str, Any]]]], DefaultArg
-        ] = NotGiven,
-        external_id: Union[Optional[str], DefaultArg] = NotGiven,
-        groups: Union[
-            Optional[List[Union[UserGroup, Dict[str, Any]]]], DefaultArg
-        ] = NotGiven,
-        id: Union[Optional[str], DefaultArg] = NotGiven,
-        meta: Union[Optional[Union[UserMeta, Dict[str, Any]]], DefaultArg] = NotGiven,
-        name: Union[Optional[Union[UserName, Dict[str, Any]]], DefaultArg] = NotGiven,
-        nick_name: Union[Optional[str], DefaultArg] = NotGiven,
-        phone_numbers: Union[
-            Optional[List[Union[TypeAndValue, Dict[str, Any]]]], DefaultArg
-        ] = NotGiven,
-        photos: Union[
-            Optional[List[Union[UserPhoto, Dict[str, Any]]]], DefaultArg
-        ] = NotGiven,
-        profile_url: Union[Optional[str], DefaultArg] = NotGiven,
-        roles: Union[
-            Optional[List[Union[TypeAndValue, Dict[str, Any]]]], DefaultArg
-        ] = NotGiven,
-        schemas: Union[Optional[List[str]], DefaultArg] = NotGiven,
-        timezone: Union[Optional[str], DefaultArg] = NotGiven,
-        title: Union[Optional[str], DefaultArg] = NotGiven,
-        user_name: Union[Optional[str], DefaultArg] = NotGiven,
-        **kwargs,
-    ) -> None:
-        self.active = active
-        self.addresses = (
-            [a if isinstance(a, UserAddress) else UserAddress(**a) for a in addresses]
-            if _is_iterable(addresses)
-            else addresses
-        )
-        self.display_name = display_name
-        self.emails = (
-            [a if isinstance(a, TypeAndValue) else TypeAndValue(**a) for a in emails]
-            if _is_iterable(emails)
-            else emails
-        )
-        self.external_id = external_id
-        self.groups = (
-            [a if isinstance(a, UserGroup) else UserGroup(**a) for a in groups]
-            if _is_iterable(groups)
-            else groups
-        )
-        self.id = id
-        self.meta = (
-            UserMeta(**meta) if meta is not None and isinstance(meta, dict) else meta
-        )
-        self.name = (
-            UserName(**name) if name is not None and isinstance(name, dict) else name
-        )
-        self.nick_name = nick_name
-        self.phone_numbers = (
-            [
-                a if isinstance(a, TypeAndValue) else TypeAndValue(**a)
-                for a in phone_numbers
-            ]
-            if _is_iterable(phone_numbers)
-            else phone_numbers
-        )
-        self.photos = (
-            [a if isinstance(a, UserPhoto) else UserPhoto(**a) for a in photos]
-            if _is_iterable(photos)
-            else photos
-        )
-        self.profile_url = profile_url
-        self.roles = (
-            [a if isinstance(a, TypeAndValue) else TypeAndValue(**a) for a in roles]
-            if _is_iterable(roles)
-            else roles
-        )
-        self.schemas = schemas
-        self.timezone = timezone
-        self.title = title
-        self.user_name = user_name
-
-        self.unknown_fields = kwargs
-
-    def to_dict(self):
-        return _to_dict_without_not_given(self)
-
-    def __repr__(self):
-        return f"<slack_sdk.scim.{self.__class__.__name__}: {self.to_dict()}>"
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class User -(*, active:ย Union[bool,ย ForwardRef(None),ย DefaultArg]ย =ย <slack_sdk.scim.v1.default_arg.DefaultArg object>, addresses:ย Union[List[Union[UserAddress,ย Dict[str,ย Any]]],ย ForwardRef(None),ย DefaultArg]ย =ย <slack_sdk.scim.v1.default_arg.DefaultArg object>, display_name:ย Union[str,ย ForwardRef(None),ย DefaultArg]ย =ย <slack_sdk.scim.v1.default_arg.DefaultArg object>, emails:ย Union[List[Union[TypeAndValue,ย Dict[str,ย Any]]],ย ForwardRef(None),ย DefaultArg]ย =ย <slack_sdk.scim.v1.default_arg.DefaultArg object>, external_id:ย Union[str,ย ForwardRef(None),ย DefaultArg]ย =ย <slack_sdk.scim.v1.default_arg.DefaultArg object>, groups:ย Union[List[Union[UserGroup,ย Dict[str,ย Any]]],ย ForwardRef(None),ย DefaultArg]ย =ย <slack_sdk.scim.v1.default_arg.DefaultArg object>, id:ย Union[str,ย ForwardRef(None),ย DefaultArg]ย =ย <slack_sdk.scim.v1.default_arg.DefaultArg object>, meta:ย Union[UserMeta,ย Dict[str,ย Any],ย ForwardRef(None),ย DefaultArg]ย =ย <slack_sdk.scim.v1.default_arg.DefaultArg object>, name:ย Union[UserName,ย Dict[str,ย Any],ย ForwardRef(None),ย DefaultArg]ย =ย <slack_sdk.scim.v1.default_arg.DefaultArg object>, nick_name:ย Union[str,ย ForwardRef(None),ย DefaultArg]ย =ย <slack_sdk.scim.v1.default_arg.DefaultArg object>, phone_numbers:ย Union[List[Union[TypeAndValue,ย Dict[str,ย Any]]],ย ForwardRef(None),ย DefaultArg]ย =ย <slack_sdk.scim.v1.default_arg.DefaultArg object>, photos:ย Union[List[Union[UserPhoto,ย Dict[str,ย Any]]],ย ForwardRef(None),ย DefaultArg]ย =ย <slack_sdk.scim.v1.default_arg.DefaultArg object>, profile_url:ย Union[str,ย ForwardRef(None),ย DefaultArg]ย =ย <slack_sdk.scim.v1.default_arg.DefaultArg object>, roles:ย Union[List[Union[TypeAndValue,ย Dict[str,ย Any]]],ย ForwardRef(None),ย DefaultArg]ย =ย <slack_sdk.scim.v1.default_arg.DefaultArg object>, schemas:ย Union[List[str],ย ForwardRef(None),ย DefaultArg]ย =ย <slack_sdk.scim.v1.default_arg.DefaultArg object>, timezone:ย Union[str,ย ForwardRef(None),ย DefaultArg]ย =ย <slack_sdk.scim.v1.default_arg.DefaultArg object>, title:ย Union[str,ย ForwardRef(None),ย DefaultArg]ย =ย <slack_sdk.scim.v1.default_arg.DefaultArg object>, user_name:ย Union[str,ย ForwardRef(None),ย DefaultArg]ย =ย <slack_sdk.scim.v1.default_arg.DefaultArg object>, **kwargs) -
-
-
-
- -Expand source code - -
class User:
-    active: Union[Optional[bool], DefaultArg]
-    addresses: Union[Optional[List[UserAddress]], DefaultArg]
-    display_name: Union[Optional[str], DefaultArg]
-    emails: Union[Optional[List[TypeAndValue]], DefaultArg]
-    external_id: Union[Optional[str], DefaultArg]
-    groups: Union[Optional[List[UserGroup]], DefaultArg]
-    id: Union[Optional[str], DefaultArg]
-    meta: Union[Optional[UserMeta], DefaultArg]
-    name: Union[Optional[UserName], DefaultArg]
-    nick_name: Union[Optional[str], DefaultArg]
-    phone_numbers: Union[Optional[List[TypeAndValue]], DefaultArg]
-    photos: Union[Optional[List[UserPhoto]], DefaultArg]
-    profile_url: Union[Optional[str], DefaultArg]
-    roles: Union[Optional[List[TypeAndValue]], DefaultArg]
-    schemas: Union[Optional[List[str]], DefaultArg]
-    timezone: Union[Optional[str], DefaultArg]
-    title: Union[Optional[str], DefaultArg]
-    user_name: Union[Optional[str], DefaultArg]
-    unknown_fields: Dict[str, Any]
-
-    def __init__(
-        self,
-        *,
-        active: Union[Optional[bool], DefaultArg] = NotGiven,
-        addresses: Union[
-            Optional[List[Union[UserAddress, Dict[str, Any]]]], DefaultArg
-        ] = NotGiven,
-        display_name: Union[Optional[str], DefaultArg] = NotGiven,
-        emails: Union[
-            Optional[List[Union[TypeAndValue, Dict[str, Any]]]], DefaultArg
-        ] = NotGiven,
-        external_id: Union[Optional[str], DefaultArg] = NotGiven,
-        groups: Union[
-            Optional[List[Union[UserGroup, Dict[str, Any]]]], DefaultArg
-        ] = NotGiven,
-        id: Union[Optional[str], DefaultArg] = NotGiven,
-        meta: Union[Optional[Union[UserMeta, Dict[str, Any]]], DefaultArg] = NotGiven,
-        name: Union[Optional[Union[UserName, Dict[str, Any]]], DefaultArg] = NotGiven,
-        nick_name: Union[Optional[str], DefaultArg] = NotGiven,
-        phone_numbers: Union[
-            Optional[List[Union[TypeAndValue, Dict[str, Any]]]], DefaultArg
-        ] = NotGiven,
-        photos: Union[
-            Optional[List[Union[UserPhoto, Dict[str, Any]]]], DefaultArg
-        ] = NotGiven,
-        profile_url: Union[Optional[str], DefaultArg] = NotGiven,
-        roles: Union[
-            Optional[List[Union[TypeAndValue, Dict[str, Any]]]], DefaultArg
-        ] = NotGiven,
-        schemas: Union[Optional[List[str]], DefaultArg] = NotGiven,
-        timezone: Union[Optional[str], DefaultArg] = NotGiven,
-        title: Union[Optional[str], DefaultArg] = NotGiven,
-        user_name: Union[Optional[str], DefaultArg] = NotGiven,
-        **kwargs,
-    ) -> None:
-        self.active = active
-        self.addresses = (
-            [a if isinstance(a, UserAddress) else UserAddress(**a) for a in addresses]
-            if _is_iterable(addresses)
-            else addresses
-        )
-        self.display_name = display_name
-        self.emails = (
-            [a if isinstance(a, TypeAndValue) else TypeAndValue(**a) for a in emails]
-            if _is_iterable(emails)
-            else emails
-        )
-        self.external_id = external_id
-        self.groups = (
-            [a if isinstance(a, UserGroup) else UserGroup(**a) for a in groups]
-            if _is_iterable(groups)
-            else groups
-        )
-        self.id = id
-        self.meta = (
-            UserMeta(**meta) if meta is not None and isinstance(meta, dict) else meta
-        )
-        self.name = (
-            UserName(**name) if name is not None and isinstance(name, dict) else name
-        )
-        self.nick_name = nick_name
-        self.phone_numbers = (
-            [
-                a if isinstance(a, TypeAndValue) else TypeAndValue(**a)
-                for a in phone_numbers
-            ]
-            if _is_iterable(phone_numbers)
-            else phone_numbers
-        )
-        self.photos = (
-            [a if isinstance(a, UserPhoto) else UserPhoto(**a) for a in photos]
-            if _is_iterable(photos)
-            else photos
-        )
-        self.profile_url = profile_url
-        self.roles = (
-            [a if isinstance(a, TypeAndValue) else TypeAndValue(**a) for a in roles]
-            if _is_iterable(roles)
-            else roles
-        )
-        self.schemas = schemas
-        self.timezone = timezone
-        self.title = title
-        self.user_name = user_name
-
-        self.unknown_fields = kwargs
-
-    def to_dict(self):
-        return _to_dict_without_not_given(self)
-
-    def __repr__(self):
-        return f"<slack_sdk.scim.{self.__class__.__name__}: {self.to_dict()}>"
-
-

Class variables

-
-
var active :ย Union[bool,ย ForwardRef(None),ย DefaultArg]
-
-
-
-
var addresses :ย Union[List[UserAddress],ย ForwardRef(None),ย DefaultArg]
-
-
-
-
var display_name :ย Union[str,ย ForwardRef(None),ย DefaultArg]
-
-
-
-
var emails :ย Union[List[TypeAndValue],ย ForwardRef(None),ย DefaultArg]
-
-
-
-
var external_id :ย Union[str,ย ForwardRef(None),ย DefaultArg]
-
-
-
-
var groups :ย Union[List[UserGroup],ย ForwardRef(None),ย DefaultArg]
-
-
-
-
var id :ย Union[str,ย ForwardRef(None),ย DefaultArg]
-
-
-
-
var meta :ย Union[UserMeta,ย ForwardRef(None),ย DefaultArg]
-
-
-
-
var name :ย Union[UserName,ย ForwardRef(None),ย DefaultArg]
-
-
-
-
var nick_name :ย Union[str,ย ForwardRef(None),ย DefaultArg]
-
-
-
-
var phone_numbers :ย Union[List[TypeAndValue],ย ForwardRef(None),ย DefaultArg]
-
-
-
-
var photos :ย Union[List[UserPhoto],ย ForwardRef(None),ย DefaultArg]
-
-
-
-
var profile_url :ย Union[str,ย ForwardRef(None),ย DefaultArg]
-
-
-
-
var roles :ย Union[List[TypeAndValue],ย ForwardRef(None),ย DefaultArg]
-
-
-
-
var schemas :ย Union[List[str],ย ForwardRef(None),ย DefaultArg]
-
-
-
-
var timezone :ย Union[str,ย ForwardRef(None),ย DefaultArg]
-
-
-
-
var title :ย Union[str,ย ForwardRef(None),ย DefaultArg]
-
-
-
-
var unknown_fields :ย Dict[str,ย Any]
-
-
-
-
var user_name :ย Union[str,ย ForwardRef(None),ย DefaultArg]
-
-
-
-
-

Methods

-
-
-def to_dict(self) -
-
-
-
- -Expand source code - -
def to_dict(self):
-    return _to_dict_without_not_given(self)
-
-
-
-
-
-class UserAddress -(*, country:ย Union[str,ย ForwardRef(None),ย DefaultArg]ย =ย <slack_sdk.scim.v1.default_arg.DefaultArg object>, locality:ย Union[str,ย ForwardRef(None),ย DefaultArg]ย =ย <slack_sdk.scim.v1.default_arg.DefaultArg object>, postal_code:ย Union[str,ย ForwardRef(None),ย DefaultArg]ย =ย <slack_sdk.scim.v1.default_arg.DefaultArg object>, primary:ย Union[bool,ย ForwardRef(None),ย DefaultArg]ย =ย <slack_sdk.scim.v1.default_arg.DefaultArg object>, region:ย Union[str,ย ForwardRef(None),ย DefaultArg]ย =ย <slack_sdk.scim.v1.default_arg.DefaultArg object>, street_address:ย Union[str,ย ForwardRef(None),ย DefaultArg]ย =ย <slack_sdk.scim.v1.default_arg.DefaultArg object>, **kwargs) -
-
-
-
- -Expand source code - -
class UserAddress:
-    country: Union[Optional[str], DefaultArg]
-    locality: Union[Optional[str], DefaultArg]
-    postal_code: Union[Optional[str], DefaultArg]
-    primary: Union[Optional[bool], DefaultArg]
-    region: Union[Optional[str], DefaultArg]
-    street_address: Union[Optional[str], DefaultArg]
-    unknown_fields: Dict[str, Any]
-
-    def __init__(
-        self,
-        *,
-        country: Union[Optional[str], DefaultArg] = NotGiven,
-        locality: Union[Optional[str], DefaultArg] = NotGiven,
-        postal_code: Union[Optional[str], DefaultArg] = NotGiven,
-        primary: Union[Optional[bool], DefaultArg] = NotGiven,
-        region: Union[Optional[str], DefaultArg] = NotGiven,
-        street_address: Union[Optional[str], DefaultArg] = NotGiven,
-        **kwargs,
-    ) -> None:
-        self.country = country
-        self.locality = locality
-        self.postal_code = postal_code
-        self.primary = primary
-        self.region = region
-        self.street_address = street_address
-        self.unknown_fields = kwargs
-
-    def to_dict(self) -> dict:
-        return _to_dict_without_not_given(self)
-
-

Class variables

-
-
var country :ย Union[str,ย ForwardRef(None),ย DefaultArg]
-
-
-
-
var locality :ย Union[str,ย ForwardRef(None),ย DefaultArg]
-
-
-
-
var postal_code :ย Union[str,ย ForwardRef(None),ย DefaultArg]
-
-
-
-
var primary :ย Union[bool,ย ForwardRef(None),ย DefaultArg]
-
-
-
-
var region :ย Union[str,ย ForwardRef(None),ย DefaultArg]
-
-
-
-
var street_address :ย Union[str,ย ForwardRef(None),ย DefaultArg]
-
-
-
-
var unknown_fields :ย Dict[str,ย Any]
-
-
-
-
-

Methods

-
-
-def to_dict(self) โ€‘>ย dict -
-
-
-
- -Expand source code - -
def to_dict(self) -> dict:
-    return _to_dict_without_not_given(self)
-
-
-
-
-
-class UserEmail -(*, primary:ย Union[bool,ย ForwardRef(None),ย DefaultArg]ย =ย <slack_sdk.scim.v1.default_arg.DefaultArg object>, type:ย Union[str,ย ForwardRef(None),ย DefaultArg]ย =ย <slack_sdk.scim.v1.default_arg.DefaultArg object>, value:ย Union[str,ย ForwardRef(None),ย DefaultArg]ย =ย <slack_sdk.scim.v1.default_arg.DefaultArg object>, **kwargs) -
-
-
-
- -Expand source code - -
class UserEmail(TypeAndValue):
-    pass
-
-

Ancestors

- -

Class variables

-
-
var primary :ย Union[bool,ย ForwardRef(None),ย DefaultArg]
-
-
-
-
var type :ย Union[str,ย ForwardRef(None),ย DefaultArg]
-
-
-
-
var unknown_fields :ย Dict[str,ย Any]
-
-
-
-
var value :ย Union[str,ย ForwardRef(None),ย DefaultArg]
-
-
-
-
-
-
-class UserGroup -(*, display:ย Union[str,ย ForwardRef(None),ย DefaultArg]ย =ย <slack_sdk.scim.v1.default_arg.DefaultArg object>, value:ย Union[str,ย ForwardRef(None),ย DefaultArg]ย =ย <slack_sdk.scim.v1.default_arg.DefaultArg object>, **kwargs) -
-
-
-
- -Expand source code - -
class UserGroup:
-    display: Union[Optional[str], DefaultArg]
-    value: Union[Optional[str], DefaultArg]
-    unknown_fields: Dict[str, Any]
-
-    def __init__(
-        self,
-        *,
-        display: Union[Optional[str], DefaultArg] = NotGiven,
-        value: Union[Optional[str], DefaultArg] = NotGiven,
-        **kwargs,
-    ) -> None:
-        self.display = display
-        self.value = value
-        self.unknown_fields = kwargs
-
-    def to_dict(self) -> dict:
-        return _to_dict_without_not_given(self)
-
-

Class variables

-
-
var display :ย Union[str,ย ForwardRef(None),ย DefaultArg]
-
-
-
-
var unknown_fields :ย Dict[str,ย Any]
-
-
-
-
var value :ย Union[str,ย ForwardRef(None),ย DefaultArg]
-
-
-
-
-

Methods

-
-
-def to_dict(self) โ€‘>ย dict -
-
-
-
- -Expand source code - -
def to_dict(self) -> dict:
-    return _to_dict_without_not_given(self)
-
-
-
-
-
-class UserMeta -(created:ย Union[str,ย ForwardRef(None),ย DefaultArg]ย =ย <slack_sdk.scim.v1.default_arg.DefaultArg object>, location:ย Union[str,ย ForwardRef(None),ย DefaultArg]ย =ย <slack_sdk.scim.v1.default_arg.DefaultArg object>, **kwargs) -
-
-
-
- -Expand source code - -
class UserMeta:
-    created: Union[Optional[str], DefaultArg]
-    location: Union[Optional[str], DefaultArg]
-    unknown_fields: Dict[str, Any]
-
-    def __init__(
-        self,
-        created: Union[Optional[str], DefaultArg] = NotGiven,
-        location: Union[Optional[str], DefaultArg] = NotGiven,
-        **kwargs,
-    ) -> None:
-        self.created = created
-        self.location = location
-        self.unknown_fields = kwargs
-
-    def to_dict(self) -> dict:
-        return _to_dict_without_not_given(self)
-
-

Class variables

-
-
var created :ย Union[str,ย ForwardRef(None),ย DefaultArg]
-
-
-
-
var location :ย Union[str,ย ForwardRef(None),ย DefaultArg]
-
-
-
-
var unknown_fields :ย Dict[str,ย Any]
-
-
-
-
-

Methods

-
-
-def to_dict(self) โ€‘>ย dict -
-
-
-
- -Expand source code - -
def to_dict(self) -> dict:
-    return _to_dict_without_not_given(self)
-
-
-
-
-
-class UserName -(family_name:ย Union[str,ย ForwardRef(None),ย DefaultArg]ย =ย <slack_sdk.scim.v1.default_arg.DefaultArg object>, given_name:ย Union[str,ย ForwardRef(None),ย DefaultArg]ย =ย <slack_sdk.scim.v1.default_arg.DefaultArg object>, **kwargs) -
-
-
-
- -Expand source code - -
class UserName:
-    family_name: Union[Optional[str], DefaultArg]
-    given_name: Union[Optional[str], DefaultArg]
-    unknown_fields: Dict[str, Any]
-
-    def __init__(
-        self,
-        family_name: Union[Optional[str], DefaultArg] = NotGiven,
-        given_name: Union[Optional[str], DefaultArg] = NotGiven,
-        **kwargs,
-    ) -> None:
-        self.family_name = family_name
-        self.given_name = given_name
-        self.unknown_fields = kwargs
-
-    def to_dict(self) -> dict:
-        return _to_dict_without_not_given(self)
-
-

Class variables

-
-
var family_name :ย Union[str,ย ForwardRef(None),ย DefaultArg]
-
-
-
-
var given_name :ย Union[str,ย ForwardRef(None),ย DefaultArg]
-
-
-
-
var unknown_fields :ย Dict[str,ย Any]
-
-
-
-
-

Methods

-
-
-def to_dict(self) โ€‘>ย dict -
-
-
-
- -Expand source code - -
def to_dict(self) -> dict:
-    return _to_dict_without_not_given(self)
-
-
-
-
-
-class UserPhoneNumber -(*, primary:ย Union[bool,ย ForwardRef(None),ย DefaultArg]ย =ย <slack_sdk.scim.v1.default_arg.DefaultArg object>, type:ย Union[str,ย ForwardRef(None),ย DefaultArg]ย =ย <slack_sdk.scim.v1.default_arg.DefaultArg object>, value:ย Union[str,ย ForwardRef(None),ย DefaultArg]ย =ย <slack_sdk.scim.v1.default_arg.DefaultArg object>, **kwargs) -
-
-
-
- -Expand source code - -
class UserPhoneNumber(TypeAndValue):
-    pass
-
-

Ancestors

- -

Class variables

-
-
var primary :ย Union[bool,ย ForwardRef(None),ย DefaultArg]
-
-
-
-
var type :ย Union[str,ย ForwardRef(None),ย DefaultArg]
-
-
-
-
var unknown_fields :ย Dict[str,ย Any]
-
-
-
-
var value :ย Union[str,ย ForwardRef(None),ย DefaultArg]
-
-
-
-
-
-
-class UserPhoto -(type:ย Union[str,ย ForwardRef(None),ย DefaultArg]ย =ย <slack_sdk.scim.v1.default_arg.DefaultArg object>, value:ย Union[str,ย ForwardRef(None),ย DefaultArg]ย =ย <slack_sdk.scim.v1.default_arg.DefaultArg object>, **kwargs) -
-
-
-
- -Expand source code - -
class UserPhoto:
-    type: Union[Optional[str], DefaultArg]
-    value: Union[Optional[str], DefaultArg]
-    unknown_fields: Dict[str, Any]
-
-    def __init__(
-        self,
-        type: Union[Optional[str], DefaultArg] = NotGiven,
-        value: Union[Optional[str], DefaultArg] = NotGiven,
-        **kwargs,
-    ) -> None:
-        self.type = type
-        self.value = value
-        self.unknown_fields = kwargs
-
-    def to_dict(self) -> dict:
-        return _to_dict_without_not_given(self)
-
-

Class variables

-
-
var type :ย Union[str,ย ForwardRef(None),ย DefaultArg]
-
-
-
-
var unknown_fields :ย Dict[str,ย Any]
-
-
-
-
var value :ย Union[str,ย ForwardRef(None),ย DefaultArg]
-
-
-
-
-

Methods

-
-
-def to_dict(self) โ€‘>ย dict -
-
-
-
- -Expand source code - -
def to_dict(self) -> dict:
-    return _to_dict_without_not_given(self)
-
-
-
-
-
-class UserRole -(*, primary:ย Union[bool,ย ForwardRef(None),ย DefaultArg]ย =ย <slack_sdk.scim.v1.default_arg.DefaultArg object>, type:ย Union[str,ย ForwardRef(None),ย DefaultArg]ย =ย <slack_sdk.scim.v1.default_arg.DefaultArg object>, value:ย Union[str,ย ForwardRef(None),ย DefaultArg]ย =ย <slack_sdk.scim.v1.default_arg.DefaultArg object>, **kwargs) -
-
-
-
- -Expand source code - -
class UserRole(TypeAndValue):
-    pass
-
-

Ancestors

- -

Class variables

-
-
var primary :ย Union[bool,ย ForwardRef(None),ย DefaultArg]
-
-
-
-
var type :ย Union[str,ย ForwardRef(None),ย DefaultArg]
-
-
-
-
var unknown_fields :ย Dict[str,ย Any]
-
-
-
-
var value :ย Union[str,ย ForwardRef(None),ย DefaultArg]
-
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/socket_mode/aiohttp/index.html b/docs/api-docs/slack_sdk/socket_mode/aiohttp/index.html deleted file mode 100644 index dc9a33ad5..000000000 --- a/docs/api-docs/slack_sdk/socket_mode/aiohttp/index.html +++ /dev/null @@ -1,1595 +0,0 @@ - - - - - - -slack_sdk.socket_mode.aiohttp API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.socket_mode.aiohttp

-
-
-

aiohttp based Socket Mode client

- -
- -Expand source code - -
"""aiohttp based Socket Mode client
-
-* https://api.slack.com/apis/connections/socket
-* https://slack.dev/python-slack-sdk/socket-mode/
-* https://pypi.org/project/aiohttp/
-
-"""
-import asyncio
-import logging
-import time
-from asyncio import Future, Lock
-from asyncio import Queue
-from logging import Logger
-from typing import Union, Optional, List, Callable, Awaitable
-
-import aiohttp
-from aiohttp import ClientWebSocketResponse, WSMessage, WSMsgType, ClientConnectionError
-
-from slack_sdk.proxy_env_variable_loader import load_http_proxy_from_env
-from slack_sdk.socket_mode.async_client import AsyncBaseSocketModeClient
-from slack_sdk.socket_mode.async_listeners import (
-    AsyncWebSocketMessageListener,
-    AsyncSocketModeRequestListener,
-)
-from slack_sdk.socket_mode.request import SocketModeRequest
-from slack_sdk.web.async_client import AsyncWebClient
-
-
-class SocketModeClient(AsyncBaseSocketModeClient):
-    logger: Logger
-    web_client: AsyncWebClient
-    app_token: str
-    wss_uri: Optional[str]
-    auto_reconnect_enabled: bool
-    message_queue: Queue
-    message_listeners: List[
-        Union[
-            AsyncWebSocketMessageListener,
-            Callable[
-                ["AsyncBaseSocketModeClient", dict, Optional[str]], Awaitable[None]
-            ],
-        ]
-    ]
-    socket_mode_request_listeners: List[
-        Union[
-            AsyncSocketModeRequestListener,
-            Callable[["AsyncBaseSocketModeClient", SocketModeRequest], Awaitable[None]],
-        ]
-    ]
-
-    message_receiver: Optional[Future]
-    message_processor: Future
-
-    proxy: Optional[str]
-    ping_interval: float
-    trace_enabled: bool
-
-    last_ping_pong_time: Optional[float]
-    current_session: Optional[ClientWebSocketResponse]
-    current_session_monitor: Optional[Future]
-
-    auto_reconnect_enabled: bool
-    default_auto_reconnect_enabled: bool
-    closed: bool
-    stale: bool
-    connect_operation_lock: Lock
-
-    on_message_listeners: List[Callable[[WSMessage], Awaitable[None]]]
-    on_error_listeners: List[Callable[[WSMessage], Awaitable[None]]]
-    on_close_listeners: List[Callable[[WSMessage], Awaitable[None]]]
-
-    def __init__(
-        self,
-        app_token: str,
-        logger: Optional[Logger] = None,
-        web_client: Optional[AsyncWebClient] = None,
-        proxy: Optional[str] = None,
-        auto_reconnect_enabled: bool = True,
-        ping_interval: float = 5,
-        trace_enabled: bool = False,
-        on_message_listeners: Optional[List[Callable[[WSMessage], None]]] = None,
-        on_error_listeners: Optional[List[Callable[[WSMessage], None]]] = None,
-        on_close_listeners: Optional[List[Callable[[WSMessage], None]]] = None,
-    ):
-        """Socket Mode client
-
-        Args:
-            app_token: App-level token
-            logger: Custom logger
-            web_client: Web API client
-            auto_reconnect_enabled: True if automatic reconnection is enabled (default: True)
-            ping_interval: interval for ping-pong with Slack servers (seconds)
-            trace_enabled: True if more verbose logs to see what's happening under the hood
-            proxy: the HTTP proxy URL
-            on_message_listeners: listener functions for on_message
-            on_error_listeners: listener functions for on_error
-            on_close_listeners: listener functions for on_close
-        """
-        self.app_token = app_token
-        self.logger = logger or logging.getLogger(__name__)
-        self.web_client = web_client or AsyncWebClient()
-        self.closed = False
-        self.stale = False
-        self.connect_operation_lock = Lock()
-        self.proxy = proxy
-        if self.proxy is None or len(self.proxy.strip()) == 0:
-            env_variable = load_http_proxy_from_env(self.logger)
-            if env_variable is not None:
-                self.proxy = env_variable
-
-        self.default_auto_reconnect_enabled = auto_reconnect_enabled
-        self.auto_reconnect_enabled = self.default_auto_reconnect_enabled
-        self.ping_interval = ping_interval
-        self.trace_enabled = trace_enabled
-        self.last_ping_pong_time = None
-
-        self.wss_uri = None
-        self.message_queue = Queue()
-        self.message_listeners = []
-        self.socket_mode_request_listeners = []
-        self.current_session = None
-        self.current_session_monitor = None
-
-        # https://docs.aiohttp.org/en/stable/client_reference.html
-        # Unless you are connecting to a large, unknown number of different servers
-        # over the lifetime of your application,
-        # it is suggested you use a single session for the lifetime of your application
-        # to benefit from connection pooling.
-        self.aiohttp_client_session = aiohttp.ClientSession()
-
-        self.on_message_listeners = on_message_listeners or []
-        self.on_error_listeners = on_error_listeners or []
-        self.on_close_listeners = on_close_listeners or []
-
-        self.message_receiver = None
-        self.message_processor = asyncio.ensure_future(self.process_messages())
-
-    async def monitor_current_session(self) -> None:
-        # In the asyncio runtime, accessing a shared object (self.current_session here) from
-        # multiple tasks can cause race conditions and errors.
-        # To avoid such, we access only the session that is active when this loop starts.
-        session: ClientWebSocketResponse = self.current_session
-        session_id: str = self.build_session_id(session)
-
-        if self.logger.level <= logging.DEBUG:
-            self.logger.debug(
-                f"A new monitor_current_session() execution loop for {session_id} started"
-            )
-        try:
-            logging_interval = 100
-            counter_for_logging = 0
-
-            while not self.closed:
-                if session != self.current_session:
-                    if self.logger.level <= logging.DEBUG:
-                        self.logger.debug(
-                            f"The monitor_current_session task for {session_id} is now cancelled"
-                        )
-                    break
-                try:
-                    # The logging here is for detailed trouble shooting of potential issues in this client.
-                    # If you don't see this log for a while, it means that
-                    # this receive_messages execution is no longer working for some reason.
-                    counter_for_logging = (counter_for_logging + 1) % logging_interval
-                    if counter_for_logging == 0:
-                        log_message = (
-                            f"{logging_interval} session verification executed after the previous same log"
-                            f" ({session_id})"
-                        )
-                        self.logger.debug(log_message)
-
-                    await asyncio.sleep(self.ping_interval)
-
-                    if session is not None and session.closed is False:
-                        t = time.time()
-                        if self.last_ping_pong_time is None:
-                            self.last_ping_pong_time = float(t)
-                        try:
-                            await session.ping(f"sdk-ping-pong:{t}")
-                        except Exception as e:
-                            # The ping() method can fail for some reason.
-                            # To establish a new connection even in this scenario,
-                            # we ignore the exception here.
-                            self.logger.warning(
-                                f"Failed to send a ping message ({session_id}): {e}"
-                            )
-
-                    if self.auto_reconnect_enabled:
-                        should_reconnect = False
-                        if session is None or session.closed:
-                            self.logger.info(
-                                f"The session ({session_id}) seems to be already closed. Reconnecting..."
-                            )
-                            should_reconnect = True
-
-                        if await self.is_ping_pong_failing():
-                            disconnected_seconds = int(
-                                time.time() - self.last_ping_pong_time
-                            )
-                            self.logger.info(
-                                f"The session ({session_id}) seems to be stale. Reconnecting..."
-                                f" reason: disconnected for {disconnected_seconds}+ seconds)"
-                            )
-                            self.stale = True
-                            self.last_ping_pong_time = None
-                            should_reconnect = True
-
-                        if should_reconnect is True or not await self.is_connected():
-                            await self.connect_to_new_endpoint()
-
-                except Exception as e:
-                    self.logger.error(
-                        f"Failed to check the current session ({session_id}) or reconnect to the server "
-                        f"(error: {type(e).__name__}, message: {e})"
-                    )
-        except asyncio.CancelledError:
-            if self.logger.level <= logging.DEBUG:
-                self.logger.debug(
-                    f"The monitor_current_session task for {session_id} is now cancelled"
-                )
-            raise
-
-    async def receive_messages(self) -> None:
-        # In the asyncio runtime, accessing a shared object (self.current_session here) from
-        # multiple tasks can cause race conditions and errors.
-        # To avoid such, we access only the session that is active when this loop starts.
-        session = self.current_session
-        session_id = self.build_session_id(session)
-        if self.logger.level <= logging.DEBUG:
-            self.logger.debug(
-                f"A new receive_messages() execution loop with {session_id} started"
-            )
-        try:
-            consecutive_error_count = 0
-            logging_interval = 100
-            counter_for_logging = 0
-
-            while not self.closed:
-                if session != self.current_session:
-                    if self.logger.level <= logging.DEBUG:
-                        self.logger.debug(
-                            f"The running receive_messages task for {session_id} is now cancelled"
-                        )
-                    break
-                try:
-                    message: WSMessage = await session.receive()
-                    if self.trace_enabled and self.logger.level <= logging.DEBUG:
-                        # The following logging prints every single received message except empty message data ones.
-                        type = WSMsgType(message.type)
-                        message_type = type.name if type is not None else message.type
-                        message_data = message.data
-                        if isinstance(message_data, bytes):
-                            message_data = message_data.decode("utf-8")
-                        if len(message_data) > 0:
-                            # To skip the empty message that Slack server-side often sends
-                            self.logger.debug(
-                                f"Received message "
-                                f"(type: {message_type}, "
-                                f"data: {message_data}, "
-                                f"extra: {message.extra}, "
-                                f"session: {session_id})"
-                            )
-
-                    # The logging here is for detailed trouble shooting of potential issues in this client.
-                    # If you don't see this log for a while, it means that
-                    # this receive_messages execution is no longer working for some reason.
-                    if self.logger.level <= logging.DEBUG:
-                        counter_for_logging = (
-                            counter_for_logging + 1
-                        ) % logging_interval
-                        if counter_for_logging == 0:
-                            log_message = (
-                                f"{logging_interval} WebSocket messages received "
-                                f"after the previous same log ({session_id})"
-                            )
-                            self.logger.debug(log_message)
-
-                    if message is not None:
-                        if message.type == WSMsgType.TEXT:
-                            message_data = message.data
-                            await self.enqueue_message(message_data)
-                            for listener in self.on_message_listeners:
-                                await listener(message)
-                        elif message.type == WSMsgType.CLOSE:
-                            if self.auto_reconnect_enabled:
-                                self.logger.info(
-                                    f"Received CLOSE event from {session_id}. Reconnecting..."
-                                )
-                                await self.connect_to_new_endpoint()
-                            for listener in self.on_close_listeners:
-                                await listener(message)
-                        elif message.type == WSMsgType.ERROR:
-                            for listener in self.on_error_listeners:
-                                await listener(message)
-                        elif message.type == WSMsgType.CLOSED:
-                            await asyncio.sleep(self.ping_interval)
-                            continue
-                        elif message.type == WSMsgType.PING:
-                            await session.pong(message.data)
-                            continue
-                        elif message.type == WSMsgType.PONG:
-                            if message.data is not None:
-                                str_message_data = message.data.decode("utf-8")
-                                elements = str_message_data.split(":")
-                                if (
-                                    len(elements) == 2
-                                    and elements[0] == "sdk-ping-pong"
-                                ):
-                                    try:
-                                        self.last_ping_pong_time = float(elements[1])
-                                    except Exception as e:
-                                        self.logger.warning(
-                                            f"Failed to parse the last_ping_pong_time value from {str_message_data}"
-                                            f" - error : {e}, session: {session_id}"
-                                        )
-                            continue
-                    consecutive_error_count = 0
-                except Exception as e:
-                    consecutive_error_count += 1
-                    self.logger.error(
-                        f"Failed to receive or enqueue a message: {type(e).__name__}, {e} ({session_id})"
-                    )
-                    if isinstance(e, ClientConnectionError):
-                        await asyncio.sleep(self.ping_interval)
-                    else:
-                        await asyncio.sleep(consecutive_error_count)
-        except asyncio.CancelledError:
-            if self.logger.level <= logging.DEBUG:
-                self.logger.debug(
-                    f"The running receive_messages task for {session_id} is now cancelled"
-                )
-            raise
-
-    async def is_ping_pong_failing(self) -> bool:
-        if self.last_ping_pong_time is None:
-            return False
-        disconnected_seconds = int(time.time() - self.last_ping_pong_time)
-        return disconnected_seconds >= (self.ping_interval * 4)
-
-    async def is_connected(self) -> bool:
-        connected: bool = (
-            not self.closed
-            and not self.stale
-            and self.current_session is not None
-            and not self.current_session.closed
-            and not await self.is_ping_pong_failing()
-        )
-        if connected is False and self.logger.level <= logging.DEBUG:
-            is_ping_pong_failing = await self.is_ping_pong_failing()
-            session_id = await self.session_id()
-            self.logger.debug(
-                "Inactive connection detected ("
-                f"session_id: {session_id}, "
-                f"closed: {self.closed}, "
-                f"stale: {self.stale}, "
-                f"current_session.closed: {self.current_session.closed}, "
-                f"is_ping_pong_failing: {is_ping_pong_failing}"
-                ")"
-            )
-        return connected
-
-    async def session_id(self) -> str:
-        return self.build_session_id(self.current_session)
-
-    async def connect(self):
-        old_session = None if self.current_session is None else self.current_session
-        if self.wss_uri is None:
-            self.wss_uri = await self.issue_new_wss_url()
-        self.current_session = await self.aiohttp_client_session.ws_connect(
-            self.wss_uri,
-            autoping=False,
-            heartbeat=self.ping_interval,
-            proxy=self.proxy,
-        )
-        session_id: str = await self.session_id()
-        self.auto_reconnect_enabled = self.default_auto_reconnect_enabled
-        self.stale = False
-        self.logger.info(f"A new session ({session_id}) has been established")
-
-        # The first ping from the new connection
-        if self.logger.level <= logging.DEBUG:
-            self.logger.debug(
-                f"Sending a ping message with the newly established connection ({session_id})..."
-            )
-        t = time.time()
-        await self.current_session.ping(f"sdk-ping-pong:{t}")
-
-        if self.current_session_monitor is not None:
-            self.current_session_monitor.cancel()
-
-        self.current_session_monitor = asyncio.ensure_future(
-            self.monitor_current_session()
-        )
-        if self.logger.level <= logging.DEBUG:
-            self.logger.debug(
-                f"A new monitor_current_session() executor has been recreated for {session_id}"
-            )
-
-        if self.message_receiver is not None:
-            self.message_receiver.cancel()
-
-        self.message_receiver = asyncio.ensure_future(self.receive_messages())
-        if self.logger.level <= logging.DEBUG:
-            self.logger.debug(
-                f"A new receive_messages() executor has been recreated for {session_id}"
-            )
-
-        if old_session is not None:
-            await old_session.close()
-            old_session_id = self.build_session_id(old_session)
-            self.logger.info(f"The old session ({old_session_id}) has been abandoned")
-
-    async def disconnect(self):
-        if self.current_session is not None:
-            await self.current_session.close()
-        session_id = await self.session_id()
-        self.logger.info(
-            f"The current session ({session_id}) has been abandoned by disconnect() method call"
-        )
-
-    async def send_message(self, message: str):
-        session_id = await self.session_id()
-        if self.logger.level <= logging.DEBUG:
-            self.logger.debug(
-                f"Sending a message: {message} from session: {session_id}"
-            )
-        try:
-            await self.current_session.send_str(message)
-        except ConnectionError as e:
-            # We rarely get this exception while replacing the underlying WebSocket connections.
-            # We can do one more try here as the self.current_session should be ready now.
-            if self.logger.level <= logging.DEBUG:
-                self.logger.debug(
-                    f"Failed to send a message (error: {e}, message: {message}, session: {session_id})"
-                    " as the underlying connection was replaced. Retrying the same request only one time..."
-                )
-            # Although acquiring self.connect_operation_lock also for the first method call is the safest way,
-            # we avoid synchronizing a lot for better performance. That's why we are doing a retry here.
-            try:
-                await self.connect_operation_lock.acquire()
-                if await self.is_connected():
-                    await self.current_session.send_str(message)
-                else:
-                    self.logger.warning(
-                        f"The current session ({session_id}) is no longer active. "
-                        "Failed to send a message"
-                    )
-                    raise e
-            finally:
-                if self.connect_operation_lock.locked() is True:
-                    self.connect_operation_lock.release()
-
-    async def close(self):
-        self.closed = True
-        self.auto_reconnect_enabled = False
-        await self.disconnect()
-        if self.message_processor is not None:
-            self.message_processor.cancel()
-        if self.current_session_monitor is not None:
-            self.current_session_monitor.cancel()
-        if self.message_receiver is not None:
-            self.message_receiver.cancel()
-        if self.aiohttp_client_session is not None:
-            await self.aiohttp_client_session.close()
-
-    @classmethod
-    def build_session_id(cls, session: ClientWebSocketResponse) -> str:
-        if session is None:
-            return None
-        return "s_" + str(hash(session))
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class SocketModeClient -(app_token:ย str, logger:ย Optional[logging.Logger]ย =ย None, web_client:ย Optional[AsyncWebClient]ย =ย None, proxy:ย Optional[str]ย =ย None, auto_reconnect_enabled:ย boolย =ย True, ping_interval:ย floatย =ย 5, trace_enabled:ย boolย =ย False, on_message_listeners:ย Optional[List[Callable[[aiohttp.http_websocket.WSMessage],ย None]]]ย =ย None, on_error_listeners:ย Optional[List[Callable[[aiohttp.http_websocket.WSMessage],ย None]]]ย =ย None, on_close_listeners:ย Optional[List[Callable[[aiohttp.http_websocket.WSMessage],ย None]]]ย =ย None) -
-
-

Socket Mode client

-

Args

-
-
app_token
-
App-level token
-
logger
-
Custom logger
-
web_client
-
Web API client
-
auto_reconnect_enabled
-
True if automatic reconnection is enabled (default: True)
-
ping_interval
-
interval for ping-pong with Slack servers (seconds)
-
trace_enabled
-
True if more verbose logs to see what's happening under the hood
-
proxy
-
the HTTP proxy URL
-
on_message_listeners
-
listener functions for on_message
-
on_error_listeners
-
listener functions for on_error
-
on_close_listeners
-
listener functions for on_close
-
-
- -Expand source code - -
class SocketModeClient(AsyncBaseSocketModeClient):
-    logger: Logger
-    web_client: AsyncWebClient
-    app_token: str
-    wss_uri: Optional[str]
-    auto_reconnect_enabled: bool
-    message_queue: Queue
-    message_listeners: List[
-        Union[
-            AsyncWebSocketMessageListener,
-            Callable[
-                ["AsyncBaseSocketModeClient", dict, Optional[str]], Awaitable[None]
-            ],
-        ]
-    ]
-    socket_mode_request_listeners: List[
-        Union[
-            AsyncSocketModeRequestListener,
-            Callable[["AsyncBaseSocketModeClient", SocketModeRequest], Awaitable[None]],
-        ]
-    ]
-
-    message_receiver: Optional[Future]
-    message_processor: Future
-
-    proxy: Optional[str]
-    ping_interval: float
-    trace_enabled: bool
-
-    last_ping_pong_time: Optional[float]
-    current_session: Optional[ClientWebSocketResponse]
-    current_session_monitor: Optional[Future]
-
-    auto_reconnect_enabled: bool
-    default_auto_reconnect_enabled: bool
-    closed: bool
-    stale: bool
-    connect_operation_lock: Lock
-
-    on_message_listeners: List[Callable[[WSMessage], Awaitable[None]]]
-    on_error_listeners: List[Callable[[WSMessage], Awaitable[None]]]
-    on_close_listeners: List[Callable[[WSMessage], Awaitable[None]]]
-
-    def __init__(
-        self,
-        app_token: str,
-        logger: Optional[Logger] = None,
-        web_client: Optional[AsyncWebClient] = None,
-        proxy: Optional[str] = None,
-        auto_reconnect_enabled: bool = True,
-        ping_interval: float = 5,
-        trace_enabled: bool = False,
-        on_message_listeners: Optional[List[Callable[[WSMessage], None]]] = None,
-        on_error_listeners: Optional[List[Callable[[WSMessage], None]]] = None,
-        on_close_listeners: Optional[List[Callable[[WSMessage], None]]] = None,
-    ):
-        """Socket Mode client
-
-        Args:
-            app_token: App-level token
-            logger: Custom logger
-            web_client: Web API client
-            auto_reconnect_enabled: True if automatic reconnection is enabled (default: True)
-            ping_interval: interval for ping-pong with Slack servers (seconds)
-            trace_enabled: True if more verbose logs to see what's happening under the hood
-            proxy: the HTTP proxy URL
-            on_message_listeners: listener functions for on_message
-            on_error_listeners: listener functions for on_error
-            on_close_listeners: listener functions for on_close
-        """
-        self.app_token = app_token
-        self.logger = logger or logging.getLogger(__name__)
-        self.web_client = web_client or AsyncWebClient()
-        self.closed = False
-        self.stale = False
-        self.connect_operation_lock = Lock()
-        self.proxy = proxy
-        if self.proxy is None or len(self.proxy.strip()) == 0:
-            env_variable = load_http_proxy_from_env(self.logger)
-            if env_variable is not None:
-                self.proxy = env_variable
-
-        self.default_auto_reconnect_enabled = auto_reconnect_enabled
-        self.auto_reconnect_enabled = self.default_auto_reconnect_enabled
-        self.ping_interval = ping_interval
-        self.trace_enabled = trace_enabled
-        self.last_ping_pong_time = None
-
-        self.wss_uri = None
-        self.message_queue = Queue()
-        self.message_listeners = []
-        self.socket_mode_request_listeners = []
-        self.current_session = None
-        self.current_session_monitor = None
-
-        # https://docs.aiohttp.org/en/stable/client_reference.html
-        # Unless you are connecting to a large, unknown number of different servers
-        # over the lifetime of your application,
-        # it is suggested you use a single session for the lifetime of your application
-        # to benefit from connection pooling.
-        self.aiohttp_client_session = aiohttp.ClientSession()
-
-        self.on_message_listeners = on_message_listeners or []
-        self.on_error_listeners = on_error_listeners or []
-        self.on_close_listeners = on_close_listeners or []
-
-        self.message_receiver = None
-        self.message_processor = asyncio.ensure_future(self.process_messages())
-
-    async def monitor_current_session(self) -> None:
-        # In the asyncio runtime, accessing a shared object (self.current_session here) from
-        # multiple tasks can cause race conditions and errors.
-        # To avoid such, we access only the session that is active when this loop starts.
-        session: ClientWebSocketResponse = self.current_session
-        session_id: str = self.build_session_id(session)
-
-        if self.logger.level <= logging.DEBUG:
-            self.logger.debug(
-                f"A new monitor_current_session() execution loop for {session_id} started"
-            )
-        try:
-            logging_interval = 100
-            counter_for_logging = 0
-
-            while not self.closed:
-                if session != self.current_session:
-                    if self.logger.level <= logging.DEBUG:
-                        self.logger.debug(
-                            f"The monitor_current_session task for {session_id} is now cancelled"
-                        )
-                    break
-                try:
-                    # The logging here is for detailed trouble shooting of potential issues in this client.
-                    # If you don't see this log for a while, it means that
-                    # this receive_messages execution is no longer working for some reason.
-                    counter_for_logging = (counter_for_logging + 1) % logging_interval
-                    if counter_for_logging == 0:
-                        log_message = (
-                            f"{logging_interval} session verification executed after the previous same log"
-                            f" ({session_id})"
-                        )
-                        self.logger.debug(log_message)
-
-                    await asyncio.sleep(self.ping_interval)
-
-                    if session is not None and session.closed is False:
-                        t = time.time()
-                        if self.last_ping_pong_time is None:
-                            self.last_ping_pong_time = float(t)
-                        try:
-                            await session.ping(f"sdk-ping-pong:{t}")
-                        except Exception as e:
-                            # The ping() method can fail for some reason.
-                            # To establish a new connection even in this scenario,
-                            # we ignore the exception here.
-                            self.logger.warning(
-                                f"Failed to send a ping message ({session_id}): {e}"
-                            )
-
-                    if self.auto_reconnect_enabled:
-                        should_reconnect = False
-                        if session is None or session.closed:
-                            self.logger.info(
-                                f"The session ({session_id}) seems to be already closed. Reconnecting..."
-                            )
-                            should_reconnect = True
-
-                        if await self.is_ping_pong_failing():
-                            disconnected_seconds = int(
-                                time.time() - self.last_ping_pong_time
-                            )
-                            self.logger.info(
-                                f"The session ({session_id}) seems to be stale. Reconnecting..."
-                                f" reason: disconnected for {disconnected_seconds}+ seconds)"
-                            )
-                            self.stale = True
-                            self.last_ping_pong_time = None
-                            should_reconnect = True
-
-                        if should_reconnect is True or not await self.is_connected():
-                            await self.connect_to_new_endpoint()
-
-                except Exception as e:
-                    self.logger.error(
-                        f"Failed to check the current session ({session_id}) or reconnect to the server "
-                        f"(error: {type(e).__name__}, message: {e})"
-                    )
-        except asyncio.CancelledError:
-            if self.logger.level <= logging.DEBUG:
-                self.logger.debug(
-                    f"The monitor_current_session task for {session_id} is now cancelled"
-                )
-            raise
-
-    async def receive_messages(self) -> None:
-        # In the asyncio runtime, accessing a shared object (self.current_session here) from
-        # multiple tasks can cause race conditions and errors.
-        # To avoid such, we access only the session that is active when this loop starts.
-        session = self.current_session
-        session_id = self.build_session_id(session)
-        if self.logger.level <= logging.DEBUG:
-            self.logger.debug(
-                f"A new receive_messages() execution loop with {session_id} started"
-            )
-        try:
-            consecutive_error_count = 0
-            logging_interval = 100
-            counter_for_logging = 0
-
-            while not self.closed:
-                if session != self.current_session:
-                    if self.logger.level <= logging.DEBUG:
-                        self.logger.debug(
-                            f"The running receive_messages task for {session_id} is now cancelled"
-                        )
-                    break
-                try:
-                    message: WSMessage = await session.receive()
-                    if self.trace_enabled and self.logger.level <= logging.DEBUG:
-                        # The following logging prints every single received message except empty message data ones.
-                        type = WSMsgType(message.type)
-                        message_type = type.name if type is not None else message.type
-                        message_data = message.data
-                        if isinstance(message_data, bytes):
-                            message_data = message_data.decode("utf-8")
-                        if len(message_data) > 0:
-                            # To skip the empty message that Slack server-side often sends
-                            self.logger.debug(
-                                f"Received message "
-                                f"(type: {message_type}, "
-                                f"data: {message_data}, "
-                                f"extra: {message.extra}, "
-                                f"session: {session_id})"
-                            )
-
-                    # The logging here is for detailed trouble shooting of potential issues in this client.
-                    # If you don't see this log for a while, it means that
-                    # this receive_messages execution is no longer working for some reason.
-                    if self.logger.level <= logging.DEBUG:
-                        counter_for_logging = (
-                            counter_for_logging + 1
-                        ) % logging_interval
-                        if counter_for_logging == 0:
-                            log_message = (
-                                f"{logging_interval} WebSocket messages received "
-                                f"after the previous same log ({session_id})"
-                            )
-                            self.logger.debug(log_message)
-
-                    if message is not None:
-                        if message.type == WSMsgType.TEXT:
-                            message_data = message.data
-                            await self.enqueue_message(message_data)
-                            for listener in self.on_message_listeners:
-                                await listener(message)
-                        elif message.type == WSMsgType.CLOSE:
-                            if self.auto_reconnect_enabled:
-                                self.logger.info(
-                                    f"Received CLOSE event from {session_id}. Reconnecting..."
-                                )
-                                await self.connect_to_new_endpoint()
-                            for listener in self.on_close_listeners:
-                                await listener(message)
-                        elif message.type == WSMsgType.ERROR:
-                            for listener in self.on_error_listeners:
-                                await listener(message)
-                        elif message.type == WSMsgType.CLOSED:
-                            await asyncio.sleep(self.ping_interval)
-                            continue
-                        elif message.type == WSMsgType.PING:
-                            await session.pong(message.data)
-                            continue
-                        elif message.type == WSMsgType.PONG:
-                            if message.data is not None:
-                                str_message_data = message.data.decode("utf-8")
-                                elements = str_message_data.split(":")
-                                if (
-                                    len(elements) == 2
-                                    and elements[0] == "sdk-ping-pong"
-                                ):
-                                    try:
-                                        self.last_ping_pong_time = float(elements[1])
-                                    except Exception as e:
-                                        self.logger.warning(
-                                            f"Failed to parse the last_ping_pong_time value from {str_message_data}"
-                                            f" - error : {e}, session: {session_id}"
-                                        )
-                            continue
-                    consecutive_error_count = 0
-                except Exception as e:
-                    consecutive_error_count += 1
-                    self.logger.error(
-                        f"Failed to receive or enqueue a message: {type(e).__name__}, {e} ({session_id})"
-                    )
-                    if isinstance(e, ClientConnectionError):
-                        await asyncio.sleep(self.ping_interval)
-                    else:
-                        await asyncio.sleep(consecutive_error_count)
-        except asyncio.CancelledError:
-            if self.logger.level <= logging.DEBUG:
-                self.logger.debug(
-                    f"The running receive_messages task for {session_id} is now cancelled"
-                )
-            raise
-
-    async def is_ping_pong_failing(self) -> bool:
-        if self.last_ping_pong_time is None:
-            return False
-        disconnected_seconds = int(time.time() - self.last_ping_pong_time)
-        return disconnected_seconds >= (self.ping_interval * 4)
-
-    async def is_connected(self) -> bool:
-        connected: bool = (
-            not self.closed
-            and not self.stale
-            and self.current_session is not None
-            and not self.current_session.closed
-            and not await self.is_ping_pong_failing()
-        )
-        if connected is False and self.logger.level <= logging.DEBUG:
-            is_ping_pong_failing = await self.is_ping_pong_failing()
-            session_id = await self.session_id()
-            self.logger.debug(
-                "Inactive connection detected ("
-                f"session_id: {session_id}, "
-                f"closed: {self.closed}, "
-                f"stale: {self.stale}, "
-                f"current_session.closed: {self.current_session.closed}, "
-                f"is_ping_pong_failing: {is_ping_pong_failing}"
-                ")"
-            )
-        return connected
-
-    async def session_id(self) -> str:
-        return self.build_session_id(self.current_session)
-
-    async def connect(self):
-        old_session = None if self.current_session is None else self.current_session
-        if self.wss_uri is None:
-            self.wss_uri = await self.issue_new_wss_url()
-        self.current_session = await self.aiohttp_client_session.ws_connect(
-            self.wss_uri,
-            autoping=False,
-            heartbeat=self.ping_interval,
-            proxy=self.proxy,
-        )
-        session_id: str = await self.session_id()
-        self.auto_reconnect_enabled = self.default_auto_reconnect_enabled
-        self.stale = False
-        self.logger.info(f"A new session ({session_id}) has been established")
-
-        # The first ping from the new connection
-        if self.logger.level <= logging.DEBUG:
-            self.logger.debug(
-                f"Sending a ping message with the newly established connection ({session_id})..."
-            )
-        t = time.time()
-        await self.current_session.ping(f"sdk-ping-pong:{t}")
-
-        if self.current_session_monitor is not None:
-            self.current_session_monitor.cancel()
-
-        self.current_session_monitor = asyncio.ensure_future(
-            self.monitor_current_session()
-        )
-        if self.logger.level <= logging.DEBUG:
-            self.logger.debug(
-                f"A new monitor_current_session() executor has been recreated for {session_id}"
-            )
-
-        if self.message_receiver is not None:
-            self.message_receiver.cancel()
-
-        self.message_receiver = asyncio.ensure_future(self.receive_messages())
-        if self.logger.level <= logging.DEBUG:
-            self.logger.debug(
-                f"A new receive_messages() executor has been recreated for {session_id}"
-            )
-
-        if old_session is not None:
-            await old_session.close()
-            old_session_id = self.build_session_id(old_session)
-            self.logger.info(f"The old session ({old_session_id}) has been abandoned")
-
-    async def disconnect(self):
-        if self.current_session is not None:
-            await self.current_session.close()
-        session_id = await self.session_id()
-        self.logger.info(
-            f"The current session ({session_id}) has been abandoned by disconnect() method call"
-        )
-
-    async def send_message(self, message: str):
-        session_id = await self.session_id()
-        if self.logger.level <= logging.DEBUG:
-            self.logger.debug(
-                f"Sending a message: {message} from session: {session_id}"
-            )
-        try:
-            await self.current_session.send_str(message)
-        except ConnectionError as e:
-            # We rarely get this exception while replacing the underlying WebSocket connections.
-            # We can do one more try here as the self.current_session should be ready now.
-            if self.logger.level <= logging.DEBUG:
-                self.logger.debug(
-                    f"Failed to send a message (error: {e}, message: {message}, session: {session_id})"
-                    " as the underlying connection was replaced. Retrying the same request only one time..."
-                )
-            # Although acquiring self.connect_operation_lock also for the first method call is the safest way,
-            # we avoid synchronizing a lot for better performance. That's why we are doing a retry here.
-            try:
-                await self.connect_operation_lock.acquire()
-                if await self.is_connected():
-                    await self.current_session.send_str(message)
-                else:
-                    self.logger.warning(
-                        f"The current session ({session_id}) is no longer active. "
-                        "Failed to send a message"
-                    )
-                    raise e
-            finally:
-                if self.connect_operation_lock.locked() is True:
-                    self.connect_operation_lock.release()
-
-    async def close(self):
-        self.closed = True
-        self.auto_reconnect_enabled = False
-        await self.disconnect()
-        if self.message_processor is not None:
-            self.message_processor.cancel()
-        if self.current_session_monitor is not None:
-            self.current_session_monitor.cancel()
-        if self.message_receiver is not None:
-            self.message_receiver.cancel()
-        if self.aiohttp_client_session is not None:
-            await self.aiohttp_client_session.close()
-
-    @classmethod
-    def build_session_id(cls, session: ClientWebSocketResponse) -> str:
-        if session is None:
-            return None
-        return "s_" + str(hash(session))
-
-

Ancestors

- -

Class variables

-
-
var app_token :ย str
-
-
-
-
var auto_reconnect_enabled :ย bool
-
-
-
-
var closed :ย bool
-
-
-
-
var connect_operation_lock :ย asyncio.locks.Lock
-
-
-
-
var current_session :ย Optional[aiohttp.client_ws.ClientWebSocketResponse]
-
-
-
-
var current_session_monitor :ย Optional[_asyncio.Future]
-
-
-
-
var default_auto_reconnect_enabled :ย bool
-
-
-
-
var last_ping_pong_time :ย Optional[float]
-
-
-
-
var logger :ย logging.Logger
-
-
-
-
var message_listeners :ย List[Union[AsyncWebSocketMessageListener,ย Callable[[AsyncBaseSocketModeClient,ย dict,ย Optional[str]],ย Awaitable[None]]]]
-
-
-
-
var message_processor :ย _asyncio.Future
-
-
-
-
var message_queue :ย asyncio.queues.Queue
-
-
-
-
var message_receiver :ย Optional[_asyncio.Future]
-
-
-
-
var on_close_listeners :ย List[Callable[[aiohttp.http_websocket.WSMessage],ย Awaitable[None]]]
-
-
-
-
var on_error_listeners :ย List[Callable[[aiohttp.http_websocket.WSMessage],ย Awaitable[None]]]
-
-
-
-
var on_message_listeners :ย List[Callable[[aiohttp.http_websocket.WSMessage],ย Awaitable[None]]]
-
-
-
-
var ping_interval :ย float
-
-
-
-
var proxy :ย Optional[str]
-
-
-
-
var socket_mode_request_listeners :ย List[Union[AsyncSocketModeRequestListener,ย Callable[[AsyncBaseSocketModeClient,ย SocketModeRequest],ย Awaitable[None]]]]
-
-
-
-
var stale :ย bool
-
-
-
-
var trace_enabled :ย bool
-
-
-
-
var web_client :ย AsyncWebClient
-
-
-
-
var wss_uri :ย Optional[str]
-
-
-
-
-

Static methods

-
-
-def build_session_id(session:ย aiohttp.client_ws.ClientWebSocketResponse) โ€‘>ย str -
-
-
-
- -Expand source code - -
@classmethod
-def build_session_id(cls, session: ClientWebSocketResponse) -> str:
-    if session is None:
-        return None
-    return "s_" + str(hash(session))
-
-
-
-

Methods

-
-
-async def close(self) -
-
-
-
- -Expand source code - -
async def close(self):
-    self.closed = True
-    self.auto_reconnect_enabled = False
-    await self.disconnect()
-    if self.message_processor is not None:
-        self.message_processor.cancel()
-    if self.current_session_monitor is not None:
-        self.current_session_monitor.cancel()
-    if self.message_receiver is not None:
-        self.message_receiver.cancel()
-    if self.aiohttp_client_session is not None:
-        await self.aiohttp_client_session.close()
-
-
-
-async def connect(self) -
-
-
-
- -Expand source code - -
async def connect(self):
-    old_session = None if self.current_session is None else self.current_session
-    if self.wss_uri is None:
-        self.wss_uri = await self.issue_new_wss_url()
-    self.current_session = await self.aiohttp_client_session.ws_connect(
-        self.wss_uri,
-        autoping=False,
-        heartbeat=self.ping_interval,
-        proxy=self.proxy,
-    )
-    session_id: str = await self.session_id()
-    self.auto_reconnect_enabled = self.default_auto_reconnect_enabled
-    self.stale = False
-    self.logger.info(f"A new session ({session_id}) has been established")
-
-    # The first ping from the new connection
-    if self.logger.level <= logging.DEBUG:
-        self.logger.debug(
-            f"Sending a ping message with the newly established connection ({session_id})..."
-        )
-    t = time.time()
-    await self.current_session.ping(f"sdk-ping-pong:{t}")
-
-    if self.current_session_monitor is not None:
-        self.current_session_monitor.cancel()
-
-    self.current_session_monitor = asyncio.ensure_future(
-        self.monitor_current_session()
-    )
-    if self.logger.level <= logging.DEBUG:
-        self.logger.debug(
-            f"A new monitor_current_session() executor has been recreated for {session_id}"
-        )
-
-    if self.message_receiver is not None:
-        self.message_receiver.cancel()
-
-    self.message_receiver = asyncio.ensure_future(self.receive_messages())
-    if self.logger.level <= logging.DEBUG:
-        self.logger.debug(
-            f"A new receive_messages() executor has been recreated for {session_id}"
-        )
-
-    if old_session is not None:
-        await old_session.close()
-        old_session_id = self.build_session_id(old_session)
-        self.logger.info(f"The old session ({old_session_id}) has been abandoned")
-
-
-
-async def disconnect(self) -
-
-
-
- -Expand source code - -
async def disconnect(self):
-    if self.current_session is not None:
-        await self.current_session.close()
-    session_id = await self.session_id()
-    self.logger.info(
-        f"The current session ({session_id}) has been abandoned by disconnect() method call"
-    )
-
-
-
-async def is_connected(self) โ€‘>ย bool -
-
-
-
- -Expand source code - -
async def is_connected(self) -> bool:
-    connected: bool = (
-        not self.closed
-        and not self.stale
-        and self.current_session is not None
-        and not self.current_session.closed
-        and not await self.is_ping_pong_failing()
-    )
-    if connected is False and self.logger.level <= logging.DEBUG:
-        is_ping_pong_failing = await self.is_ping_pong_failing()
-        session_id = await self.session_id()
-        self.logger.debug(
-            "Inactive connection detected ("
-            f"session_id: {session_id}, "
-            f"closed: {self.closed}, "
-            f"stale: {self.stale}, "
-            f"current_session.closed: {self.current_session.closed}, "
-            f"is_ping_pong_failing: {is_ping_pong_failing}"
-            ")"
-        )
-    return connected
-
-
-
-async def is_ping_pong_failing(self) โ€‘>ย bool -
-
-
-
- -Expand source code - -
async def is_ping_pong_failing(self) -> bool:
-    if self.last_ping_pong_time is None:
-        return False
-    disconnected_seconds = int(time.time() - self.last_ping_pong_time)
-    return disconnected_seconds >= (self.ping_interval * 4)
-
-
-
-async def monitor_current_session(self) โ€‘>ย None -
-
-
-
- -Expand source code - -
async def monitor_current_session(self) -> None:
-    # In the asyncio runtime, accessing a shared object (self.current_session here) from
-    # multiple tasks can cause race conditions and errors.
-    # To avoid such, we access only the session that is active when this loop starts.
-    session: ClientWebSocketResponse = self.current_session
-    session_id: str = self.build_session_id(session)
-
-    if self.logger.level <= logging.DEBUG:
-        self.logger.debug(
-            f"A new monitor_current_session() execution loop for {session_id} started"
-        )
-    try:
-        logging_interval = 100
-        counter_for_logging = 0
-
-        while not self.closed:
-            if session != self.current_session:
-                if self.logger.level <= logging.DEBUG:
-                    self.logger.debug(
-                        f"The monitor_current_session task for {session_id} is now cancelled"
-                    )
-                break
-            try:
-                # The logging here is for detailed trouble shooting of potential issues in this client.
-                # If you don't see this log for a while, it means that
-                # this receive_messages execution is no longer working for some reason.
-                counter_for_logging = (counter_for_logging + 1) % logging_interval
-                if counter_for_logging == 0:
-                    log_message = (
-                        f"{logging_interval} session verification executed after the previous same log"
-                        f" ({session_id})"
-                    )
-                    self.logger.debug(log_message)
-
-                await asyncio.sleep(self.ping_interval)
-
-                if session is not None and session.closed is False:
-                    t = time.time()
-                    if self.last_ping_pong_time is None:
-                        self.last_ping_pong_time = float(t)
-                    try:
-                        await session.ping(f"sdk-ping-pong:{t}")
-                    except Exception as e:
-                        # The ping() method can fail for some reason.
-                        # To establish a new connection even in this scenario,
-                        # we ignore the exception here.
-                        self.logger.warning(
-                            f"Failed to send a ping message ({session_id}): {e}"
-                        )
-
-                if self.auto_reconnect_enabled:
-                    should_reconnect = False
-                    if session is None or session.closed:
-                        self.logger.info(
-                            f"The session ({session_id}) seems to be already closed. Reconnecting..."
-                        )
-                        should_reconnect = True
-
-                    if await self.is_ping_pong_failing():
-                        disconnected_seconds = int(
-                            time.time() - self.last_ping_pong_time
-                        )
-                        self.logger.info(
-                            f"The session ({session_id}) seems to be stale. Reconnecting..."
-                            f" reason: disconnected for {disconnected_seconds}+ seconds)"
-                        )
-                        self.stale = True
-                        self.last_ping_pong_time = None
-                        should_reconnect = True
-
-                    if should_reconnect is True or not await self.is_connected():
-                        await self.connect_to_new_endpoint()
-
-            except Exception as e:
-                self.logger.error(
-                    f"Failed to check the current session ({session_id}) or reconnect to the server "
-                    f"(error: {type(e).__name__}, message: {e})"
-                )
-    except asyncio.CancelledError:
-        if self.logger.level <= logging.DEBUG:
-            self.logger.debug(
-                f"The monitor_current_session task for {session_id} is now cancelled"
-            )
-        raise
-
-
-
-async def receive_messages(self) โ€‘>ย None -
-
-
-
- -Expand source code - -
async def receive_messages(self) -> None:
-    # In the asyncio runtime, accessing a shared object (self.current_session here) from
-    # multiple tasks can cause race conditions and errors.
-    # To avoid such, we access only the session that is active when this loop starts.
-    session = self.current_session
-    session_id = self.build_session_id(session)
-    if self.logger.level <= logging.DEBUG:
-        self.logger.debug(
-            f"A new receive_messages() execution loop with {session_id} started"
-        )
-    try:
-        consecutive_error_count = 0
-        logging_interval = 100
-        counter_for_logging = 0
-
-        while not self.closed:
-            if session != self.current_session:
-                if self.logger.level <= logging.DEBUG:
-                    self.logger.debug(
-                        f"The running receive_messages task for {session_id} is now cancelled"
-                    )
-                break
-            try:
-                message: WSMessage = await session.receive()
-                if self.trace_enabled and self.logger.level <= logging.DEBUG:
-                    # The following logging prints every single received message except empty message data ones.
-                    type = WSMsgType(message.type)
-                    message_type = type.name if type is not None else message.type
-                    message_data = message.data
-                    if isinstance(message_data, bytes):
-                        message_data = message_data.decode("utf-8")
-                    if len(message_data) > 0:
-                        # To skip the empty message that Slack server-side often sends
-                        self.logger.debug(
-                            f"Received message "
-                            f"(type: {message_type}, "
-                            f"data: {message_data}, "
-                            f"extra: {message.extra}, "
-                            f"session: {session_id})"
-                        )
-
-                # The logging here is for detailed trouble shooting of potential issues in this client.
-                # If you don't see this log for a while, it means that
-                # this receive_messages execution is no longer working for some reason.
-                if self.logger.level <= logging.DEBUG:
-                    counter_for_logging = (
-                        counter_for_logging + 1
-                    ) % logging_interval
-                    if counter_for_logging == 0:
-                        log_message = (
-                            f"{logging_interval} WebSocket messages received "
-                            f"after the previous same log ({session_id})"
-                        )
-                        self.logger.debug(log_message)
-
-                if message is not None:
-                    if message.type == WSMsgType.TEXT:
-                        message_data = message.data
-                        await self.enqueue_message(message_data)
-                        for listener in self.on_message_listeners:
-                            await listener(message)
-                    elif message.type == WSMsgType.CLOSE:
-                        if self.auto_reconnect_enabled:
-                            self.logger.info(
-                                f"Received CLOSE event from {session_id}. Reconnecting..."
-                            )
-                            await self.connect_to_new_endpoint()
-                        for listener in self.on_close_listeners:
-                            await listener(message)
-                    elif message.type == WSMsgType.ERROR:
-                        for listener in self.on_error_listeners:
-                            await listener(message)
-                    elif message.type == WSMsgType.CLOSED:
-                        await asyncio.sleep(self.ping_interval)
-                        continue
-                    elif message.type == WSMsgType.PING:
-                        await session.pong(message.data)
-                        continue
-                    elif message.type == WSMsgType.PONG:
-                        if message.data is not None:
-                            str_message_data = message.data.decode("utf-8")
-                            elements = str_message_data.split(":")
-                            if (
-                                len(elements) == 2
-                                and elements[0] == "sdk-ping-pong"
-                            ):
-                                try:
-                                    self.last_ping_pong_time = float(elements[1])
-                                except Exception as e:
-                                    self.logger.warning(
-                                        f"Failed to parse the last_ping_pong_time value from {str_message_data}"
-                                        f" - error : {e}, session: {session_id}"
-                                    )
-                        continue
-                consecutive_error_count = 0
-            except Exception as e:
-                consecutive_error_count += 1
-                self.logger.error(
-                    f"Failed to receive or enqueue a message: {type(e).__name__}, {e} ({session_id})"
-                )
-                if isinstance(e, ClientConnectionError):
-                    await asyncio.sleep(self.ping_interval)
-                else:
-                    await asyncio.sleep(consecutive_error_count)
-    except asyncio.CancelledError:
-        if self.logger.level <= logging.DEBUG:
-            self.logger.debug(
-                f"The running receive_messages task for {session_id} is now cancelled"
-            )
-        raise
-
-
-
-async def send_message(self, message:ย str) -
-
-
-
- -Expand source code - -
async def send_message(self, message: str):
-    session_id = await self.session_id()
-    if self.logger.level <= logging.DEBUG:
-        self.logger.debug(
-            f"Sending a message: {message} from session: {session_id}"
-        )
-    try:
-        await self.current_session.send_str(message)
-    except ConnectionError as e:
-        # We rarely get this exception while replacing the underlying WebSocket connections.
-        # We can do one more try here as the self.current_session should be ready now.
-        if self.logger.level <= logging.DEBUG:
-            self.logger.debug(
-                f"Failed to send a message (error: {e}, message: {message}, session: {session_id})"
-                " as the underlying connection was replaced. Retrying the same request only one time..."
-            )
-        # Although acquiring self.connect_operation_lock also for the first method call is the safest way,
-        # we avoid synchronizing a lot for better performance. That's why we are doing a retry here.
-        try:
-            await self.connect_operation_lock.acquire()
-            if await self.is_connected():
-                await self.current_session.send_str(message)
-            else:
-                self.logger.warning(
-                    f"The current session ({session_id}) is no longer active. "
-                    "Failed to send a message"
-                )
-                raise e
-        finally:
-            if self.connect_operation_lock.locked() is True:
-                self.connect_operation_lock.release()
-
-
-
-async def session_id(self) โ€‘>ย str -
-
-
-
- -Expand source code - -
async def session_id(self) -> str:
-    return self.build_session_id(self.current_session)
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/socket_mode/async_listeners.html b/docs/api-docs/slack_sdk/socket_mode/async_listeners.html deleted file mode 100644 index 06799d296..000000000 --- a/docs/api-docs/slack_sdk/socket_mode/async_listeners.html +++ /dev/null @@ -1,162 +0,0 @@ - - - - - - -slack_sdk.socket_mode.async_listeners API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.socket_mode.async_listeners

-
-
-
- -Expand source code - -
from typing import Optional, Callable
-
-from slack_sdk.socket_mode.request import SocketModeRequest
-
-
-class AsyncWebSocketMessageListener(Callable):
-    async def __call__(
-        client: "AsyncBaseSocketModeClient",  # noqa: F821
-        message: dict,
-        raw_message: Optional[str] = None,
-    ):  # noqa: F821
-        raise NotImplementedError()
-
-
-class AsyncSocketModeRequestListener(Callable):
-    async def __call__(
-        client: "AsyncBaseSocketModeClient",  # noqa: F821
-        request: SocketModeRequest,
-    ):  # noqa: F821
-        raise NotImplementedError()
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class AsyncSocketModeRequestListener -
-
-

Abstract base class for generic types.

-

A generic type is typically declared by inheriting from -this class parameterized with one or more type variables. -For example, a generic mapping type might be defined as::

-

class Mapping(Generic[KT, VT]): -def getitem(self, key: KT) -> VT: -… -# Etc.

-

This class can then be used as follows::

-

def lookup_name(mapping: Mapping[KT, VT], key: KT, default: VT) -> VT: -try: -return mapping[key] -except KeyError: -return default

-
- -Expand source code - -
class AsyncSocketModeRequestListener(Callable):
-    async def __call__(
-        client: "AsyncBaseSocketModeClient",  # noqa: F821
-        request: SocketModeRequest,
-    ):  # noqa: F821
-        raise NotImplementedError()
-
-

Ancestors

-
    -
  • collections.abc.Callable
  • -
  • typing.Generic
  • -
-
-
-class AsyncWebSocketMessageListener -
-
-

Abstract base class for generic types.

-

A generic type is typically declared by inheriting from -this class parameterized with one or more type variables. -For example, a generic mapping type might be defined as::

-

class Mapping(Generic[KT, VT]): -def getitem(self, key: KT) -> VT: -… -# Etc.

-

This class can then be used as follows::

-

def lookup_name(mapping: Mapping[KT, VT], key: KT, default: VT) -> VT: -try: -return mapping[key] -except KeyError: -return default

-
- -Expand source code - -
class AsyncWebSocketMessageListener(Callable):
-    async def __call__(
-        client: "AsyncBaseSocketModeClient",  # noqa: F821
-        message: dict,
-        raw_message: Optional[str] = None,
-    ):  # noqa: F821
-        raise NotImplementedError()
-
-

Ancestors

-
    -
  • collections.abc.Callable
  • -
  • typing.Generic
  • -
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/socket_mode/builtin/index.html b/docs/api-docs/slack_sdk/socket_mode/builtin/index.html deleted file mode 100644 index 5215bce84..000000000 --- a/docs/api-docs/slack_sdk/socket_mode/builtin/index.html +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - -slack_sdk.socket_mode.builtin API documentation - - - - - - - - - - - -
- - -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/socket_mode/builtin/internals.html b/docs/api-docs/slack_sdk/socket_mode/builtin/internals.html deleted file mode 100644 index 1954d3228..000000000 --- a/docs/api-docs/slack_sdk/socket_mode/builtin/internals.html +++ /dev/null @@ -1,464 +0,0 @@ - - - - - - -slack_sdk.socket_mode.builtin.internals API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.socket_mode.builtin.internals

-
-
-
- -Expand source code - -
import errno
-import hashlib
-import itertools
-import os
-import random
-import socket
-from socket import socket as Socket
-import ssl
-import struct
-from base64 import encodebytes, b64encode
-from hmac import compare_digest
-from logging import Logger
-from threading import Lock
-from typing import Tuple, Optional, Union, List, Callable, Dict
-from urllib.parse import urlparse
-
-from .frame_header import FrameHeader
-
-
-def _parse_connect_response(sock) -> (Optional[int], str):
-    status = None
-    lines = []
-    while True:
-        line = []
-        while True:
-            c = sock.recv(1)
-            line.append(c)
-            if c == b"\n":
-                break
-        line = b"".join(line).decode("utf-8").strip()
-        if line is None or len(line) == 0:
-            break
-        lines.append(line)
-        if not status:
-            status_line = line.split(" ", 2)
-            status = int(status_line[1])
-    return status, "\n".join(lines)
-
-
-def _establish_new_socket_connection(
-    session_id: str,
-    server_hostname: str,
-    server_port: int,
-    logger: Logger,
-    sock_send_lock: Lock,
-    receive_timeout: float,
-    proxy: Optional[str],
-    proxy_headers: Optional[Dict[str, str]],
-    trace_enabled: bool,
-) -> Union[ssl.SSLSocket, Socket]:
-    if proxy is not None:
-        parsed_proxy = urlparse(proxy)
-        proxy_host, proxy_port = parsed_proxy.hostname, parsed_proxy.port or 80
-        sock = socket.create_connection((proxy_host, proxy_port), receive_timeout)
-        if hasattr(socket, "TCP_NODELAY"):
-            sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
-        if hasattr(socket, "SO_KEEPALIVE"):
-            sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
-        message = [f"CONNECT {server_hostname}:{server_port} HTTP/1.0"]
-        if parsed_proxy.username is not None and parsed_proxy.password is not None:
-            # In the case where the proxy is "http://{username}:{password}@{hostname}:{port}"
-            raw_value = f"{parsed_proxy.username}:{parsed_proxy.password}"
-            auth = b64encode(raw_value.encode("utf-8")).decode("ascii")
-            message.append(f"Proxy-Authorization: Basic {auth}")
-        if proxy_headers is not None:
-            for k, v in proxy_headers.items():
-                message.append(f"{k}: {v}")
-        message.append("")
-        message.append("")
-        req: str = "\r\n".join([line.lstrip() for line in message])
-        if trace_enabled:
-            logger.debug(f"Proxy connect request (session id: {session_id}):\n{req}")
-        with sock_send_lock:
-            sock.send(req.encode("utf-8"))
-        status, text = _parse_connect_response(sock)
-        if trace_enabled:
-            log_message = f"Proxy connect response (session id: {session_id}):\n{text}"
-            logger.debug(log_message)
-        if status != 200:
-            raise Exception(
-                f"Failed to connect to the proxy (proxy: {proxy}, connect status code: {status})"
-            )
-
-        sock = ssl.create_default_context().wrap_socket(
-            sock,
-            do_handshake_on_connect=True,
-            suppress_ragged_eofs=True,
-            server_hostname=server_hostname,
-        )
-        return sock
-
-    if server_port != 443:
-        # only for library testing
-        logger.info(
-            f"Using non-ssl socket to connect ({server_hostname}:{server_port})"
-        )
-        sock = socket.create_connection((server_hostname, server_port), timeout=3)
-        return sock
-
-    sock = socket.create_connection((server_hostname, server_port), receive_timeout)
-    sock = ssl.create_default_context().wrap_socket(
-        sock,
-        do_handshake_on_connect=True,
-        suppress_ragged_eofs=True,
-        server_hostname=server_hostname,
-    )
-    return sock
-
-
-def _read_http_response_line(sock: ssl.SSLSocket) -> str:
-    cs = []
-    while True:
-        c: str = sock.recv(1).decode("utf-8")
-        if c == "\r":
-            break
-        if c != "\n":
-            cs.append(c)
-    return "".join(cs)
-
-
-def _parse_handshake_response(sock: ssl.SSLSocket) -> (int, dict, str):
-    """Parses the handshake response.
-
-    Args:
-        sock: The current active socket
-
-    Returns:
-        (http status, headers, whole response as a str)
-    """
-    lines = []
-    status = None
-    headers = {}
-    while True:
-        line = _read_http_response_line(sock)
-        if status is None:
-            elements = line.split(" ")
-            if len(elements) > 2:
-                status = int(elements[1])
-        else:
-            elements = line.split(":")
-            if len(elements) == 2:
-                headers[elements[0].strip().lower()] = elements[1].strip()
-        if line is None or len(line.strip()) == 0:
-            break
-        lines.append(line)
-    text = "\n".join(lines)
-    return (status, headers, text)
-
-
-def _generate_sec_websocket_key() -> str:
-    return encodebytes(os.urandom(16)).decode("utf-8").strip()
-
-
-def _validate_sec_websocket_accept(sec_websocket_key: str, headers: dict) -> bool:
-    v = (sec_websocket_key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").encode("utf-8")
-    expected = encodebytes(hashlib.sha1(v).digest()).decode("utf-8").strip()
-    actual = headers.get("sec-websocket-accept").strip()
-    return compare_digest(expected, actual)
-
-
-def _to_readable_opcode(opcode: int) -> str:
-    if opcode == FrameHeader.OPCODE_CONTINUATION:
-        return "continuation"
-    if opcode == FrameHeader.OPCODE_TEXT:
-        return "text"
-    if opcode == FrameHeader.OPCODE_BINARY:
-        return "binary"
-    if opcode == FrameHeader.OPCODE_CLOSE:
-        return "close"
-    if opcode == FrameHeader.OPCODE_PING:
-        return "ping"
-    if opcode == FrameHeader.OPCODE_PONG:
-        return "pong"
-
-
-def _parse_text_payload(data: Optional[bytes], logger: Logger) -> str:
-    try:
-        if data is not None and isinstance(data, bytes):
-            return data.decode("utf-8")
-        else:
-            return ""
-    except UnicodeDecodeError as e:
-        logger.debug(f"Failed to parse a payload (data: {data}, error: {e})")
-
-
-def _receive_messages(
-    sock: ssl.SSLSocket,
-    sock_receive_lock: Lock,
-    logger: Logger,
-    receive_buffer_size: int = 1024,
-    all_message_trace_enabled: bool = False,
-) -> List[Tuple[Optional[FrameHeader], bytes]]:
-    def receive(specific_buffer_size: Optional[int] = None):
-        size = (
-            specific_buffer_size
-            if specific_buffer_size is not None
-            else receive_buffer_size
-        )
-        with sock_receive_lock:
-            try:
-                received_bytes = sock.recv(size)
-                if all_message_trace_enabled:
-                    if len(received_bytes) > 0:
-                        logger.debug(f"Received bytes: {received_bytes}")
-                return received_bytes
-            except OSError as e:
-                # For Linux/macOS, errno.EBADF is the expected error for bad connections.
-                # The errno.ENOTSOCK can be sent when running on Windows OS.
-                if e.errno in (errno.EBADF, errno.ENOTSOCK):
-                    logger.debug("The connection seems to be already closed.")
-                    return bytes()
-                raise e
-
-    return _fetch_messages(
-        messages=[],
-        receive=receive,
-        remaining_bytes=None,
-        current_mask_key=None,
-        current_header=None,
-        current_data=bytes(),
-        logger=logger,
-    )
-
-
-def _fetch_messages(
-    messages: List[Tuple[Optional[FrameHeader], bytes]],
-    receive: Callable[[Optional[int]], bytes],  # buffer size
-    logger: Logger,
-    remaining_bytes: Optional[bytes] = None,
-    current_mask_key: Optional[str] = None,
-    current_header: Optional[FrameHeader] = None,
-    current_data: Optional[bytes] = None,
-) -> List[Tuple[Optional[FrameHeader], bytes]]:
-
-    if remaining_bytes is None:
-        # Fetch more to complete the current message
-        remaining_bytes = receive()
-
-    if remaining_bytes is None or len(remaining_bytes) == 0:
-        # no more bytes
-        if current_header is not None:
-            _append_message(messages, current_header, current_data)
-        return messages
-
-    if current_header is None:
-        # new message
-        if len(remaining_bytes) <= 2:
-            remaining_bytes += receive()
-
-        if remaining_bytes[0] == 10:  # \n
-            if current_data is not None and len(current_data) >= 0:
-                _append_message(messages, current_header, current_data)
-            _append_message(messages, None, remaining_bytes[:1])
-            remaining_bytes = remaining_bytes[1:]
-            if len(remaining_bytes) == 0:
-                return messages
-            else:
-                return _fetch_messages(
-                    messages=messages,
-                    receive=receive,
-                    remaining_bytes=remaining_bytes,
-                    logger=logger,
-                )
-
-        # https://tools.ietf.org/html/rfc6455#section-5.2
-        b1, b2 = remaining_bytes[0], remaining_bytes[1]
-
-        # determine data length and the first index of the data part
-        current_data_length: int = b2 & 0b01111111
-        idx_after_length_part: int = 2
-        if current_data_length == 126:
-            if len(remaining_bytes) < 4:
-                remaining_bytes += receive(1024)
-            current_data_length = struct.unpack("!H", bytes(remaining_bytes[2:4]))[0]
-            idx_after_length_part = 4
-        elif current_data_length == 127:
-            if len(remaining_bytes) < 10:
-                remaining_bytes += receive(1024)
-            current_data_length = struct.unpack("!H", bytes(remaining_bytes[2:10]))[0]
-            idx_after_length_part = 10
-
-        current_header = FrameHeader(
-            fin=b1 & 0b10000000,
-            rsv1=b1 & 0b01000000,
-            rsv2=b1 & 0b00100000,
-            rsv3=b1 & 0b00010000,
-            opcode=b1 & 0b00001111,
-            masked=b2 & 0b10000000,
-            length=current_data_length,
-        )
-        if current_header.masked > 0:
-            if current_mask_key is None:
-                idx1, idx2 = idx_after_length_part, idx_after_length_part + 4
-                current_mask_key = remaining_bytes[idx1:idx2]
-                idx_after_length_part += 4
-
-        start, end = idx_after_length_part, idx_after_length_part + current_data_length
-        data_to_append = remaining_bytes[start:end]
-
-        current_data = bytes()
-        if current_header.masked > 0:
-            for i in range(data_to_append):
-                mask = current_mask_key[i % 4]
-                data_to_append[i] ^= mask
-            current_data += data_to_append
-        else:
-            current_data += data_to_append
-        if len(current_data) == current_data_length:
-            _append_message(messages, current_header, current_data)
-            remaining_bytes = remaining_bytes[end:]
-            if len(remaining_bytes) > 0:
-                # continue with the remaining data
-                return _fetch_messages(
-                    messages=messages,
-                    receive=receive,
-                    remaining_bytes=remaining_bytes,
-                    logger=logger,
-                )
-            else:
-                return messages
-        elif len(current_data) < current_data_length:
-            # need more bytes to complete this message
-            return _fetch_messages(
-                messages=messages,
-                receive=receive,
-                current_mask_key=current_mask_key,
-                current_header=current_header,
-                current_data=current_data,
-                logger=logger,
-            )
-        else:
-            # This pattern is unexpected but set data with the expected length anyway
-            _append_message(current_header, current_data[:current_data_length])
-            return messages
-
-    # work in progress with the current_header/current_data
-    if current_header is not None:
-        length_needed = current_header.length - len(current_data)
-        if length_needed > len(remaining_bytes):
-            current_data += remaining_bytes
-            # need more bytes to complete this message
-            return _fetch_messages(
-                messages=messages,
-                receive=receive,
-                current_mask_key=current_mask_key,
-                current_header=current_header,
-                current_data=current_data,
-                logger=logger,
-            )
-        else:
-            current_data += remaining_bytes[:length_needed]
-            _append_message(messages, current_header, current_data)
-            remaining_bytes = remaining_bytes[length_needed:]
-            if len(remaining_bytes) == 0:
-                return messages
-            else:
-                # continue with the remaining data
-                return _fetch_messages(
-                    messages=messages,
-                    receive=receive,
-                    remaining_bytes=remaining_bytes,
-                    logger=logger,
-                )
-
-    return messages
-
-
-def _append_message(
-    messages: List[Tuple[Optional[FrameHeader], bytes]],
-    header: Optional[FrameHeader],
-    data: bytes,
-) -> None:
-    messages.append((header, data))
-
-
-def _build_data_frame_for_sending(
-    payload: Union[str, bytes],
-    opcode: int,
-    fin: int = 1,
-    rsv1: int = 0,
-    rsv2: int = 0,
-    rsv3: int = 0,
-    masked: int = 1,
-):
-    b1 = fin << 7 | rsv1 << 6 | rsv2 << 5 | rsv3 << 4 | opcode
-    header: bytes = bytes([b1])
-
-    original_payload_data: bytes = (
-        payload.encode("utf-8") if isinstance(payload, str) else payload
-    )
-    payload_length = len(original_payload_data)
-    if payload_length <= 125:
-        b2 = masked << 7 | payload_length
-        header += bytes([b2])
-    else:
-        b2 = masked << 7 | 126
-        header += struct.pack("!BH", b2, payload_length)
-
-    mask_key: List[int] = random.choices(range(256), k=4)
-    header += bytes(mask_key)
-
-    payload_data: bytes = bytes(
-        byte ^ mask
-        for byte, mask in zip(original_payload_data, itertools.cycle(mask_key))
-    )
-    return header + payload_data
-
-
-
-
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/socket_mode/index.html b/docs/api-docs/slack_sdk/socket_mode/index.html deleted file mode 100644 index 3e2986f7f..000000000 --- a/docs/api-docs/slack_sdk/socket_mode/index.html +++ /dev/null @@ -1,132 +0,0 @@ - - - - - - -slack_sdk.socket_mode API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.socket_mode

-
-
-

Socket Mode is a method of connecting your app to Slackโ€™s APIs using WebSockets instead of HTTP. -You can use slack_sdk.socket_mode.SocketModeClient for managing Socket Mode connections -and performing interactions with Slack.

-

https://api.slack.com/apis/connections/socket

-
- -Expand source code - -
"""Socket Mode is a method of connecting your app to Slackโ€™s APIs using WebSockets instead of HTTP.
-You can use slack_sdk.socket_mode.SocketModeClient for managing Socket Mode connections
-and performing interactions with Slack.
-
-https://api.slack.com/apis/connections/socket
-"""
-from .builtin import SocketModeClient  # noqa
-
-
-
-

Sub-modules

-
-
slack_sdk.socket_mode.aiohttp
-
-

aiohttp based Socket Mode client โ€ฆ

-
-
slack_sdk.socket_mode.async_client
-
-
-
-
slack_sdk.socket_mode.async_listeners
-
-
-
-
slack_sdk.socket_mode.builtin
-
-
-
-
slack_sdk.socket_mode.client
-
-
-
-
slack_sdk.socket_mode.interval_runner
-
-
-
-
slack_sdk.socket_mode.listeners
-
-
-
-
slack_sdk.socket_mode.request
-
-
-
-
slack_sdk.socket_mode.response
-
-
-
-
slack_sdk.socket_mode.websocket_client
-
-

websocket-client bassd Socket Mode client โ€ฆ

-
-
slack_sdk.socket_mode.websockets
-
-

websockets bassd Socket Mode client โ€ฆ

-
-
-
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/socket_mode/listeners.html b/docs/api-docs/slack_sdk/socket_mode/listeners.html deleted file mode 100644 index a60e5753b..000000000 --- a/docs/api-docs/slack_sdk/socket_mode/listeners.html +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - -slack_sdk.socket_mode.listeners API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.socket_mode.listeners

-
-
-
- -Expand source code - -
from typing import Optional
-
-from slack_sdk.socket_mode.request import SocketModeRequest
-
-
-class WebSocketMessageListener:
-    def __call__(
-        client: "BaseSocketModeClient",  # noqa: F821
-        message: dict,
-        raw_message: Optional[str] = None,
-    ):  # noqa: F821
-        raise NotImplementedError()
-
-
-class SocketModeRequestListener:
-    def __call__(
-        client: "BaseSocketModeClient", request: SocketModeRequest  # noqa: F821
-    ):  # noqa: F821
-        raise NotImplementedError()
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class SocketModeRequestListener -
-
-
-
- -Expand source code - -
class SocketModeRequestListener:
-    def __call__(
-        client: "BaseSocketModeClient", request: SocketModeRequest  # noqa: F821
-    ):  # noqa: F821
-        raise NotImplementedError()
-
-
-
-class WebSocketMessageListener -
-
-
-
- -Expand source code - -
class WebSocketMessageListener:
-    def __call__(
-        client: "BaseSocketModeClient",  # noqa: F821
-        message: dict,
-        raw_message: Optional[str] = None,
-    ):  # noqa: F821
-        raise NotImplementedError()
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/socket_mode/request.html b/docs/api-docs/slack_sdk/socket_mode/request.html deleted file mode 100644 index e8ebb0298..000000000 --- a/docs/api-docs/slack_sdk/socket_mode/request.html +++ /dev/null @@ -1,272 +0,0 @@ - - - - - - -slack_sdk.socket_mode.request API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.socket_mode.request

-
-
-
- -Expand source code - -
from typing import Union, Optional
-
-from slack_sdk.models import JsonObject
-
-
-class SocketModeRequest:
-    type: str
-    envelope_id: str
-    payload: dict
-    accepts_response_payload: bool
-    retry_attempt: Optional[int]  # events_api
-    retry_reason: Optional[str]  # events_api
-
-    def __init__(
-        self,
-        type: str,
-        envelope_id: str,
-        payload: Union[dict, JsonObject, str],
-        accepts_response_payload: Optional[bool] = None,
-        retry_attempt: Optional[int] = None,
-        retry_reason: Optional[str] = None,
-    ):
-        self.type = type
-        self.envelope_id = envelope_id
-
-        if isinstance(payload, JsonObject):
-            self.payload = payload.to_dict()
-        elif isinstance(payload, dict):
-            self.payload = payload
-        elif isinstance(payload, str):
-            self.payload = {"text": payload}
-        else:
-            raise ValueError(f"Unsupported payload data type ({type(payload)})")
-
-        self.accepts_response_payload = accepts_response_payload or False
-        self.retry_attempt = retry_attempt
-        self.retry_reason = retry_reason
-
-    @classmethod
-    def from_dict(cls, message: dict) -> Optional["SocketModeRequest"]:
-        if all(k in message for k in ("type", "envelope_id", "payload")):
-            return SocketModeRequest(
-                type=message.get("type"),
-                envelope_id=message.get("envelope_id"),
-                payload=message.get("payload"),
-                accepts_response_payload=message.get("accepts_response_payload")
-                or False,
-                retry_attempt=message.get("retry_attempt"),
-                retry_reason=message.get("retry_reason"),
-            )
-        return None
-
-    def to_dict(self) -> dict:  # skipcq: PYL-W0221
-        d = {"envelope_id": self.envelope_id}
-        if self.payload is not None:
-            d["payload"] = self.payload
-        return d
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class SocketModeRequest -(type:ย str, envelope_id:ย str, payload:ย Union[dict,ย JsonObject,ย str], accepts_response_payload:ย Optional[bool]ย =ย None, retry_attempt:ย Optional[int]ย =ย None, retry_reason:ย Optional[str]ย =ย None) -
-
-
-
- -Expand source code - -
class SocketModeRequest:
-    type: str
-    envelope_id: str
-    payload: dict
-    accepts_response_payload: bool
-    retry_attempt: Optional[int]  # events_api
-    retry_reason: Optional[str]  # events_api
-
-    def __init__(
-        self,
-        type: str,
-        envelope_id: str,
-        payload: Union[dict, JsonObject, str],
-        accepts_response_payload: Optional[bool] = None,
-        retry_attempt: Optional[int] = None,
-        retry_reason: Optional[str] = None,
-    ):
-        self.type = type
-        self.envelope_id = envelope_id
-
-        if isinstance(payload, JsonObject):
-            self.payload = payload.to_dict()
-        elif isinstance(payload, dict):
-            self.payload = payload
-        elif isinstance(payload, str):
-            self.payload = {"text": payload}
-        else:
-            raise ValueError(f"Unsupported payload data type ({type(payload)})")
-
-        self.accepts_response_payload = accepts_response_payload or False
-        self.retry_attempt = retry_attempt
-        self.retry_reason = retry_reason
-
-    @classmethod
-    def from_dict(cls, message: dict) -> Optional["SocketModeRequest"]:
-        if all(k in message for k in ("type", "envelope_id", "payload")):
-            return SocketModeRequest(
-                type=message.get("type"),
-                envelope_id=message.get("envelope_id"),
-                payload=message.get("payload"),
-                accepts_response_payload=message.get("accepts_response_payload")
-                or False,
-                retry_attempt=message.get("retry_attempt"),
-                retry_reason=message.get("retry_reason"),
-            )
-        return None
-
-    def to_dict(self) -> dict:  # skipcq: PYL-W0221
-        d = {"envelope_id": self.envelope_id}
-        if self.payload is not None:
-            d["payload"] = self.payload
-        return d
-
-

Class variables

-
-
var accepts_response_payload :ย bool
-
-
-
-
var envelope_id :ย str
-
-
-
-
var payload :ย dict
-
-
-
-
var retry_attempt :ย Optional[int]
-
-
-
-
var retry_reason :ย Optional[str]
-
-
-
-
var type :ย str
-
-
-
-
-

Static methods

-
-
-def from_dict(message:ย dict) โ€‘>ย Optional[SocketModeRequest] -
-
-
-
- -Expand source code - -
@classmethod
-def from_dict(cls, message: dict) -> Optional["SocketModeRequest"]:
-    if all(k in message for k in ("type", "envelope_id", "payload")):
-        return SocketModeRequest(
-            type=message.get("type"),
-            envelope_id=message.get("envelope_id"),
-            payload=message.get("payload"),
-            accepts_response_payload=message.get("accepts_response_payload")
-            or False,
-            retry_attempt=message.get("retry_attempt"),
-            retry_reason=message.get("retry_reason"),
-        )
-    return None
-
-
-
-

Methods

-
-
-def to_dict(self) โ€‘>ย dict -
-
-
-
- -Expand source code - -
def to_dict(self) -> dict:  # skipcq: PYL-W0221
-    d = {"envelope_id": self.envelope_id}
-    if self.payload is not None:
-        d["payload"] = self.payload
-    return d
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/socket_mode/response.html b/docs/api-docs/slack_sdk/socket_mode/response.html deleted file mode 100644 index 0dc7d1951..000000000 --- a/docs/api-docs/slack_sdk/socket_mode/response.html +++ /dev/null @@ -1,170 +0,0 @@ - - - - - - -slack_sdk.socket_mode.response API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.socket_mode.response

-
-
-
- -Expand source code - -
from typing import Union, Optional
-
-from slack_sdk.models import JsonObject
-
-
-class SocketModeResponse:
-    envelope_id: str
-    payload: dict
-
-    def __init__(
-        self, envelope_id: str, payload: Optional[Union[dict, JsonObject, str]] = None
-    ):
-        self.envelope_id = envelope_id
-
-        if payload is None:
-            self.payload = None
-        elif isinstance(payload, JsonObject):
-            self.payload = payload.to_dict()
-        elif isinstance(payload, dict):
-            self.payload = payload
-        elif isinstance(payload, str):
-            self.payload = {"text": payload}
-        else:
-            raise ValueError(f"Unsupported payload data type ({type(payload)})")
-
-    def to_dict(self) -> dict:  # skipcq: PYL-W0221
-        d = {"envelope_id": self.envelope_id}
-        if self.payload is not None:
-            d["payload"] = self.payload
-        return d
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class SocketModeResponse -(envelope_id:ย str, payload:ย Union[dict,ย JsonObject,ย str,ย ForwardRef(None)]ย =ย None) -
-
-
-
- -Expand source code - -
class SocketModeResponse:
-    envelope_id: str
-    payload: dict
-
-    def __init__(
-        self, envelope_id: str, payload: Optional[Union[dict, JsonObject, str]] = None
-    ):
-        self.envelope_id = envelope_id
-
-        if payload is None:
-            self.payload = None
-        elif isinstance(payload, JsonObject):
-            self.payload = payload.to_dict()
-        elif isinstance(payload, dict):
-            self.payload = payload
-        elif isinstance(payload, str):
-            self.payload = {"text": payload}
-        else:
-            raise ValueError(f"Unsupported payload data type ({type(payload)})")
-
-    def to_dict(self) -> dict:  # skipcq: PYL-W0221
-        d = {"envelope_id": self.envelope_id}
-        if self.payload is not None:
-            d["payload"] = self.payload
-        return d
-
-

Class variables

-
-
var envelope_id :ย str
-
-
-
-
var payload :ย dict
-
-
-
-
-

Methods

-
-
-def to_dict(self) โ€‘>ย dict -
-
-
-
- -Expand source code - -
def to_dict(self) -> dict:  # skipcq: PYL-W0221
-    d = {"envelope_id": self.envelope_id}
-    if self.payload is not None:
-        d["payload"] = self.payload
-    return d
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/socket_mode/websocket_client/index.html b/docs/api-docs/slack_sdk/socket_mode/websocket_client/index.html deleted file mode 100644 index f1081cae9..000000000 --- a/docs/api-docs/slack_sdk/socket_mode/websocket_client/index.html +++ /dev/null @@ -1,907 +0,0 @@ - - - - - - -slack_sdk.socket_mode.websocket_client API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.socket_mode.websocket_client

-
-
-

websocket-client bassd Socket Mode client

- -
- -Expand source code - -
"""websocket-client bassd Socket Mode client
-
-* https://api.slack.com/apis/connections/socket
-* https://slack.dev/python-slack-sdk/socket-mode/
-* https://pypi.org/project/websocket-client/
-
-"""
-import logging
-from concurrent.futures.thread import ThreadPoolExecutor
-from logging import Logger
-from queue import Queue
-from threading import Lock
-from typing import Union, Optional, List, Callable, Tuple
-
-import websocket
-from websocket import WebSocketApp, WebSocketException
-
-from slack_sdk.socket_mode.client import BaseSocketModeClient
-from slack_sdk.socket_mode.interval_runner import IntervalRunner
-from slack_sdk.socket_mode.listeners import (
-    WebSocketMessageListener,
-    SocketModeRequestListener,
-)
-from slack_sdk.socket_mode.request import SocketModeRequest
-from slack_sdk.web import WebClient
-
-
-class SocketModeClient(BaseSocketModeClient):
-    logger: Logger
-    web_client: WebClient
-    app_token: str
-    wss_uri: Optional[str]
-    message_queue: Queue
-    message_listeners: List[
-        Union[
-            WebSocketMessageListener,
-            Callable[["BaseSocketModeClient", dict, Optional[str]], None],
-        ]
-    ]
-    socket_mode_request_listeners: List[
-        Union[
-            SocketModeRequestListener,
-            Callable[["BaseSocketModeClient", SocketModeRequest], None],
-        ]
-    ]
-
-    current_app_monitor: IntervalRunner
-    current_app_monitor_started: bool
-    message_processor: IntervalRunner
-    message_workers: ThreadPoolExecutor
-
-    current_session: Optional[WebSocketApp]
-    current_session_runner: IntervalRunner
-
-    auto_reconnect_enabled: bool
-    default_auto_reconnect_enabled: bool
-
-    close: bool
-    connect_operation_lock: Lock
-
-    on_open_listeners: List[Callable[[WebSocketApp], None]]
-    on_message_listeners: List[Callable[[WebSocketApp, str], None]]
-    on_error_listeners: List[Callable[[WebSocketApp, Exception], None]]
-    on_close_listeners: List[Callable[[WebSocketApp], None]]
-
-    def __init__(
-        self,
-        app_token: str,
-        logger: Optional[Logger] = None,
-        web_client: Optional[WebClient] = None,
-        auto_reconnect_enabled: bool = True,
-        ping_interval: float = 10,
-        concurrency: int = 10,
-        trace_enabled: bool = False,
-        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,
-        on_open_listeners: Optional[List[Callable[[WebSocketApp], None]]] = None,
-        on_message_listeners: Optional[
-            List[Callable[[WebSocketApp, str], None]]
-        ] = None,
-        on_error_listeners: Optional[
-            List[Callable[[WebSocketApp, Exception], None]]
-        ] = None,
-        on_close_listeners: Optional[List[Callable[[WebSocketApp], None]]] = None,
-    ):
-        """
-
-        Args:
-            app_token: App-level token
-            logger: Custom logger
-            web_client: Web API client
-            auto_reconnect_enabled: True if automatic reconnection is enabled (default: True)
-            ping_interval: interval for ping-pong with Slack servers (seconds)
-            concurrency: the size of thread pool (default: 10)
-            http_proxy_host: the HTTP proxy host
-            http_proxy_port: the HTTP proxy port
-            http_proxy_auth: the HTTP proxy username & password
-            proxy_type: the HTTP proxy type
-            on_open_listeners: listener functions for on_open
-            on_message_listeners: listener functions for on_message
-            on_error_listeners: listener functions for on_error
-            on_close_listeners: listener functions for on_close
-        """
-        self.app_token = app_token
-        self.logger = logger or logging.getLogger(__name__)
-        self.web_client = web_client or WebClient()
-        self.default_auto_reconnect_enabled = auto_reconnect_enabled
-        self.auto_reconnect_enabled = self.default_auto_reconnect_enabled
-        self.ping_interval = ping_interval
-        self.wss_uri = None
-        self.message_queue = Queue()
-        self.message_listeners = []
-        self.socket_mode_request_listeners = []
-
-        self.current_session = None
-        self.current_session_runner = IntervalRunner(
-            self._run_current_session, 0.5
-        ).start()
-
-        self.current_app_monitor_started = False
-        self.current_app_monitor = IntervalRunner(
-            self._monitor_current_session, self.ping_interval
-        )
-
-        self.closed = False
-        self.connect_operation_lock = Lock()
-
-        self.message_processor = IntervalRunner(self.process_messages, 0.001).start()
-        self.message_workers = ThreadPoolExecutor(max_workers=concurrency)
-
-        # NOTE: only global settings is provided by the library
-        websocket.enableTrace(trace_enabled)
-
-        self.http_proxy_host = http_proxy_host
-        self.http_proxy_port = http_proxy_port
-        self.http_proxy_auth = http_proxy_auth
-        self.proxy_type = proxy_type
-
-        self.on_open_listeners = on_open_listeners or []
-        self.on_message_listeners = on_message_listeners or []
-        self.on_error_listeners = on_error_listeners or []
-        self.on_close_listeners = on_close_listeners or []
-
-    def is_connected(self) -> bool:
-        return self.current_session is not None
-
-    def connect(self) -> None:
-        def on_open(ws: WebSocketApp):
-            if self.logger.level <= logging.DEBUG:
-                self.logger.debug("on_open invoked")
-            for listener in self.on_open_listeners:
-                listener(ws)
-
-        def on_message(ws: WebSocketApp, message: str):
-            if self.logger.level <= logging.DEBUG:
-                self.logger.debug(f"on_message invoked: (message: {message})")
-            self.enqueue_message(message)
-            for listener in self.on_message_listeners:
-                listener(ws, message)
-
-        def on_error(ws: WebSocketApp, error: Exception):
-            self.logger.error(
-                f"on_error invoked (error: {type(error).__name__}, message: {error})"
-            )
-            for listener in self.on_error_listeners:
-                listener(ws, error)
-
-        def on_close(
-            ws: WebSocketApp,
-            close_status_code: Optional[int] = None,
-            close_msg: Optional[str] = None,
-        ):
-            if self.logger.level <= logging.DEBUG:
-                self.logger.debug(
-                    f"on_close invoked: (code: {close_status_code}, message: {close_msg})"
-                )
-            if self.auto_reconnect_enabled:
-                self.logger.info("Received CLOSE event. Reconnecting...")
-                self.connect_to_new_endpoint()
-            for listener in self.on_close_listeners:
-                listener(ws)
-
-        old_session: Optional[WebSocketApp] = self.current_session
-
-        if self.wss_uri is None:
-            self.wss_uri = self.issue_new_wss_url()
-
-        self.current_session = websocket.WebSocketApp(
-            self.wss_uri,
-            on_open=on_open,
-            on_message=on_message,
-            on_error=on_error,
-            on_close=on_close,
-        )
-        self.auto_reconnect_enabled = self.default_auto_reconnect_enabled
-
-        if not self.current_app_monitor_started:
-            self.current_app_monitor_started = True
-            self.current_app_monitor.start()
-
-        if old_session is not None:
-            old_session.close()
-
-        self.logger.info("A new session has been established")
-
-    def disconnect(self) -> None:
-        if self.current_session is not None:
-            self.current_session.close()
-
-    def send_message(self, message: str) -> None:
-        if self.logger.level <= logging.DEBUG:
-            self.logger.debug(f"Sending a message: {message}")
-        try:
-            self.current_session.send(message)
-        except WebSocketException as e:
-            # We rarely get this exception while replacing the underlying WebSocket connections.
-            # We can do one more try here as the self.current_session should be ready now.
-            if self.logger.level <= logging.DEBUG:
-                self.logger.debug(
-                    f"Failed to send a message (error: {e}, message: {message})"
-                    " as the underlying connection was replaced. Retrying the same request only one time..."
-                )
-            # Although acquiring self.connect_operation_lock also for the first method call is the safest way,
-            # we avoid synchronizing a lot for better performance. That's why we are doing a retry here.
-            with self.connect_operation_lock:
-                if self.is_connected():
-                    self.current_session.send(message)
-                else:
-                    self.logger.warning(
-                        f"The current session (session id: {self.session_id()}) is no longer active. "
-                        "Failed to send a message"
-                    )
-                    raise e
-
-    def close(self):
-        self.closed = True
-        self.auto_reconnect_enabled = False
-        self.disconnect()
-        self.current_app_monitor.shutdown()
-        self.message_processor.shutdown()
-        self.message_workers.shutdown()
-
-    def _run_current_session(self):
-        if self.current_session is not None:
-            try:
-                self.logger.info("Starting to receive messages from a new connection")
-                self.current_session.run_forever(
-                    ping_interval=self.ping_interval,
-                    http_proxy_host=self.http_proxy_host,
-                    http_proxy_port=self.http_proxy_port,
-                    http_proxy_auth=self.http_proxy_auth,
-                    proxy_type=self.proxy_type,
-                )
-                self.logger.info("Stopped receiving messages from a connection")
-            except Exception as e:
-                self.logger.exception(
-                    f"Failed to start or stop the current session: {e}"
-                )
-
-    def _monitor_current_session(self):
-        if self.current_app_monitor_started:
-            try:
-                if self.auto_reconnect_enabled and (
-                    self.current_session is None or self.current_session.sock is None
-                ):
-                    self.logger.info(
-                        "The session seems to be already closed. Reconnecting..."
-                    )
-                    self.connect_to_new_endpoint()
-            except Exception as e:
-                self.logger.error(
-                    "Failed to check the current session or reconnect to the server "
-                    f"(error: {type(e).__name__}, message: {e})"
-                )
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class SocketModeClient -(app_token:ย str, logger:ย Optional[logging.Logger]ย =ย None, web_client:ย Optional[WebClient]ย =ย None, auto_reconnect_enabled:ย boolย =ย True, ping_interval:ย floatย =ย 10, concurrency:ย intย =ย 10, trace_enabled:ย boolย =ย False, 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, on_open_listeners:ย Optional[List[Callable[[websocket._app.WebSocketApp],ย None]]]ย =ย None, on_message_listeners:ย Optional[List[Callable[[websocket._app.WebSocketApp,ย str],ย None]]]ย =ย None, on_error_listeners:ย Optional[List[Callable[[websocket._app.WebSocketApp,ย Exception],ย None]]]ย =ย None, on_close_listeners:ย Optional[List[Callable[[websocket._app.WebSocketApp],ย None]]]ย =ย None) -
-
-

Args

-
-
app_token
-
App-level token
-
logger
-
Custom logger
-
web_client
-
Web API client
-
auto_reconnect_enabled
-
True if automatic reconnection is enabled (default: True)
-
ping_interval
-
interval for ping-pong with Slack servers (seconds)
-
concurrency
-
the size of thread pool (default: 10)
-
http_proxy_host
-
the HTTP proxy host
-
http_proxy_port
-
the HTTP proxy port
-
http_proxy_auth
-
the HTTP proxy username & password
-
proxy_type
-
the HTTP proxy type
-
on_open_listeners
-
listener functions for on_open
-
on_message_listeners
-
listener functions for on_message
-
on_error_listeners
-
listener functions for on_error
-
on_close_listeners
-
listener functions for on_close
-
-
- -Expand source code - -
class SocketModeClient(BaseSocketModeClient):
-    logger: Logger
-    web_client: WebClient
-    app_token: str
-    wss_uri: Optional[str]
-    message_queue: Queue
-    message_listeners: List[
-        Union[
-            WebSocketMessageListener,
-            Callable[["BaseSocketModeClient", dict, Optional[str]], None],
-        ]
-    ]
-    socket_mode_request_listeners: List[
-        Union[
-            SocketModeRequestListener,
-            Callable[["BaseSocketModeClient", SocketModeRequest], None],
-        ]
-    ]
-
-    current_app_monitor: IntervalRunner
-    current_app_monitor_started: bool
-    message_processor: IntervalRunner
-    message_workers: ThreadPoolExecutor
-
-    current_session: Optional[WebSocketApp]
-    current_session_runner: IntervalRunner
-
-    auto_reconnect_enabled: bool
-    default_auto_reconnect_enabled: bool
-
-    close: bool
-    connect_operation_lock: Lock
-
-    on_open_listeners: List[Callable[[WebSocketApp], None]]
-    on_message_listeners: List[Callable[[WebSocketApp, str], None]]
-    on_error_listeners: List[Callable[[WebSocketApp, Exception], None]]
-    on_close_listeners: List[Callable[[WebSocketApp], None]]
-
-    def __init__(
-        self,
-        app_token: str,
-        logger: Optional[Logger] = None,
-        web_client: Optional[WebClient] = None,
-        auto_reconnect_enabled: bool = True,
-        ping_interval: float = 10,
-        concurrency: int = 10,
-        trace_enabled: bool = False,
-        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,
-        on_open_listeners: Optional[List[Callable[[WebSocketApp], None]]] = None,
-        on_message_listeners: Optional[
-            List[Callable[[WebSocketApp, str], None]]
-        ] = None,
-        on_error_listeners: Optional[
-            List[Callable[[WebSocketApp, Exception], None]]
-        ] = None,
-        on_close_listeners: Optional[List[Callable[[WebSocketApp], None]]] = None,
-    ):
-        """
-
-        Args:
-            app_token: App-level token
-            logger: Custom logger
-            web_client: Web API client
-            auto_reconnect_enabled: True if automatic reconnection is enabled (default: True)
-            ping_interval: interval for ping-pong with Slack servers (seconds)
-            concurrency: the size of thread pool (default: 10)
-            http_proxy_host: the HTTP proxy host
-            http_proxy_port: the HTTP proxy port
-            http_proxy_auth: the HTTP proxy username & password
-            proxy_type: the HTTP proxy type
-            on_open_listeners: listener functions for on_open
-            on_message_listeners: listener functions for on_message
-            on_error_listeners: listener functions for on_error
-            on_close_listeners: listener functions for on_close
-        """
-        self.app_token = app_token
-        self.logger = logger or logging.getLogger(__name__)
-        self.web_client = web_client or WebClient()
-        self.default_auto_reconnect_enabled = auto_reconnect_enabled
-        self.auto_reconnect_enabled = self.default_auto_reconnect_enabled
-        self.ping_interval = ping_interval
-        self.wss_uri = None
-        self.message_queue = Queue()
-        self.message_listeners = []
-        self.socket_mode_request_listeners = []
-
-        self.current_session = None
-        self.current_session_runner = IntervalRunner(
-            self._run_current_session, 0.5
-        ).start()
-
-        self.current_app_monitor_started = False
-        self.current_app_monitor = IntervalRunner(
-            self._monitor_current_session, self.ping_interval
-        )
-
-        self.closed = False
-        self.connect_operation_lock = Lock()
-
-        self.message_processor = IntervalRunner(self.process_messages, 0.001).start()
-        self.message_workers = ThreadPoolExecutor(max_workers=concurrency)
-
-        # NOTE: only global settings is provided by the library
-        websocket.enableTrace(trace_enabled)
-
-        self.http_proxy_host = http_proxy_host
-        self.http_proxy_port = http_proxy_port
-        self.http_proxy_auth = http_proxy_auth
-        self.proxy_type = proxy_type
-
-        self.on_open_listeners = on_open_listeners or []
-        self.on_message_listeners = on_message_listeners or []
-        self.on_error_listeners = on_error_listeners or []
-        self.on_close_listeners = on_close_listeners or []
-
-    def is_connected(self) -> bool:
-        return self.current_session is not None
-
-    def connect(self) -> None:
-        def on_open(ws: WebSocketApp):
-            if self.logger.level <= logging.DEBUG:
-                self.logger.debug("on_open invoked")
-            for listener in self.on_open_listeners:
-                listener(ws)
-
-        def on_message(ws: WebSocketApp, message: str):
-            if self.logger.level <= logging.DEBUG:
-                self.logger.debug(f"on_message invoked: (message: {message})")
-            self.enqueue_message(message)
-            for listener in self.on_message_listeners:
-                listener(ws, message)
-
-        def on_error(ws: WebSocketApp, error: Exception):
-            self.logger.error(
-                f"on_error invoked (error: {type(error).__name__}, message: {error})"
-            )
-            for listener in self.on_error_listeners:
-                listener(ws, error)
-
-        def on_close(
-            ws: WebSocketApp,
-            close_status_code: Optional[int] = None,
-            close_msg: Optional[str] = None,
-        ):
-            if self.logger.level <= logging.DEBUG:
-                self.logger.debug(
-                    f"on_close invoked: (code: {close_status_code}, message: {close_msg})"
-                )
-            if self.auto_reconnect_enabled:
-                self.logger.info("Received CLOSE event. Reconnecting...")
-                self.connect_to_new_endpoint()
-            for listener in self.on_close_listeners:
-                listener(ws)
-
-        old_session: Optional[WebSocketApp] = self.current_session
-
-        if self.wss_uri is None:
-            self.wss_uri = self.issue_new_wss_url()
-
-        self.current_session = websocket.WebSocketApp(
-            self.wss_uri,
-            on_open=on_open,
-            on_message=on_message,
-            on_error=on_error,
-            on_close=on_close,
-        )
-        self.auto_reconnect_enabled = self.default_auto_reconnect_enabled
-
-        if not self.current_app_monitor_started:
-            self.current_app_monitor_started = True
-            self.current_app_monitor.start()
-
-        if old_session is not None:
-            old_session.close()
-
-        self.logger.info("A new session has been established")
-
-    def disconnect(self) -> None:
-        if self.current_session is not None:
-            self.current_session.close()
-
-    def send_message(self, message: str) -> None:
-        if self.logger.level <= logging.DEBUG:
-            self.logger.debug(f"Sending a message: {message}")
-        try:
-            self.current_session.send(message)
-        except WebSocketException as e:
-            # We rarely get this exception while replacing the underlying WebSocket connections.
-            # We can do one more try here as the self.current_session should be ready now.
-            if self.logger.level <= logging.DEBUG:
-                self.logger.debug(
-                    f"Failed to send a message (error: {e}, message: {message})"
-                    " as the underlying connection was replaced. Retrying the same request only one time..."
-                )
-            # Although acquiring self.connect_operation_lock also for the first method call is the safest way,
-            # we avoid synchronizing a lot for better performance. That's why we are doing a retry here.
-            with self.connect_operation_lock:
-                if self.is_connected():
-                    self.current_session.send(message)
-                else:
-                    self.logger.warning(
-                        f"The current session (session id: {self.session_id()}) is no longer active. "
-                        "Failed to send a message"
-                    )
-                    raise e
-
-    def close(self):
-        self.closed = True
-        self.auto_reconnect_enabled = False
-        self.disconnect()
-        self.current_app_monitor.shutdown()
-        self.message_processor.shutdown()
-        self.message_workers.shutdown()
-
-    def _run_current_session(self):
-        if self.current_session is not None:
-            try:
-                self.logger.info("Starting to receive messages from a new connection")
-                self.current_session.run_forever(
-                    ping_interval=self.ping_interval,
-                    http_proxy_host=self.http_proxy_host,
-                    http_proxy_port=self.http_proxy_port,
-                    http_proxy_auth=self.http_proxy_auth,
-                    proxy_type=self.proxy_type,
-                )
-                self.logger.info("Stopped receiving messages from a connection")
-            except Exception as e:
-                self.logger.exception(
-                    f"Failed to start or stop the current session: {e}"
-                )
-
-    def _monitor_current_session(self):
-        if self.current_app_monitor_started:
-            try:
-                if self.auto_reconnect_enabled and (
-                    self.current_session is None or self.current_session.sock is None
-                ):
-                    self.logger.info(
-                        "The session seems to be already closed. Reconnecting..."
-                    )
-                    self.connect_to_new_endpoint()
-            except Exception as e:
-                self.logger.error(
-                    "Failed to check the current session or reconnect to the server "
-                    f"(error: {type(e).__name__}, message: {e})"
-                )
-
-

Ancestors

- -

Class variables

-
-
var app_token :ย str
-
-
-
-
var auto_reconnect_enabled :ย bool
-
-
-
-
var connect_operation_lock :ย 
-
-
-
-
var current_app_monitor :ย IntervalRunner
-
-
-
-
var current_app_monitor_started :ย bool
-
-
-
-
var current_session :ย Optional[websocket._app.WebSocketApp]
-
-
-
-
var current_session_runner :ย IntervalRunner
-
-
-
-
var default_auto_reconnect_enabled :ย bool
-
-
-
-
var logger :ย logging.Logger
-
-
-
-
var message_listeners :ย List[Union[WebSocketMessageListener,ย Callable[[BaseSocketModeClient,ย dict,ย Optional[str]],ย None]]]
-
-
-
-
var message_processor :ย IntervalRunner
-
-
-
-
var message_queue :ย queue.Queue
-
-
-
-
var message_workers :ย concurrent.futures.thread.ThreadPoolExecutor
-
-
-
-
var on_close_listeners :ย List[Callable[[websocket._app.WebSocketApp],ย None]]
-
-
-
-
var on_error_listeners :ย List[Callable[[websocket._app.WebSocketApp,ย Exception],ย None]]
-
-
-
-
var on_message_listeners :ย List[Callable[[websocket._app.WebSocketApp,ย str],ย None]]
-
-
-
-
var on_open_listeners :ย List[Callable[[websocket._app.WebSocketApp],ย None]]
-
-
-
-
var socket_mode_request_listeners :ย List[Union[SocketModeRequestListener,ย Callable[[BaseSocketModeClient,ย SocketModeRequest],ย None]]]
-
-
-
-
var web_client :ย WebClient
-
-
-
-
var wss_uri :ย Optional[str]
-
-
-
-
-

Methods

-
-
-def close(self) โ€‘>ย bool -
-
-
-
- -Expand source code - -
def close(self):
-    self.closed = True
-    self.auto_reconnect_enabled = False
-    self.disconnect()
-    self.current_app_monitor.shutdown()
-    self.message_processor.shutdown()
-    self.message_workers.shutdown()
-
-
-
-def connect(self) โ€‘>ย None -
-
-
-
- -Expand source code - -
def connect(self) -> None:
-    def on_open(ws: WebSocketApp):
-        if self.logger.level <= logging.DEBUG:
-            self.logger.debug("on_open invoked")
-        for listener in self.on_open_listeners:
-            listener(ws)
-
-    def on_message(ws: WebSocketApp, message: str):
-        if self.logger.level <= logging.DEBUG:
-            self.logger.debug(f"on_message invoked: (message: {message})")
-        self.enqueue_message(message)
-        for listener in self.on_message_listeners:
-            listener(ws, message)
-
-    def on_error(ws: WebSocketApp, error: Exception):
-        self.logger.error(
-            f"on_error invoked (error: {type(error).__name__}, message: {error})"
-        )
-        for listener in self.on_error_listeners:
-            listener(ws, error)
-
-    def on_close(
-        ws: WebSocketApp,
-        close_status_code: Optional[int] = None,
-        close_msg: Optional[str] = None,
-    ):
-        if self.logger.level <= logging.DEBUG:
-            self.logger.debug(
-                f"on_close invoked: (code: {close_status_code}, message: {close_msg})"
-            )
-        if self.auto_reconnect_enabled:
-            self.logger.info("Received CLOSE event. Reconnecting...")
-            self.connect_to_new_endpoint()
-        for listener in self.on_close_listeners:
-            listener(ws)
-
-    old_session: Optional[WebSocketApp] = self.current_session
-
-    if self.wss_uri is None:
-        self.wss_uri = self.issue_new_wss_url()
-
-    self.current_session = websocket.WebSocketApp(
-        self.wss_uri,
-        on_open=on_open,
-        on_message=on_message,
-        on_error=on_error,
-        on_close=on_close,
-    )
-    self.auto_reconnect_enabled = self.default_auto_reconnect_enabled
-
-    if not self.current_app_monitor_started:
-        self.current_app_monitor_started = True
-        self.current_app_monitor.start()
-
-    if old_session is not None:
-        old_session.close()
-
-    self.logger.info("A new session has been established")
-
-
-
-def disconnect(self) โ€‘>ย None -
-
-
-
- -Expand source code - -
def disconnect(self) -> None:
-    if self.current_session is not None:
-        self.current_session.close()
-
-
-
-def is_connected(self) โ€‘>ย bool -
-
-
-
- -Expand source code - -
def is_connected(self) -> bool:
-    return self.current_session is not None
-
-
-
-def send_message(self, message:ย str) โ€‘>ย None -
-
-
-
- -Expand source code - -
def send_message(self, message: str) -> None:
-    if self.logger.level <= logging.DEBUG:
-        self.logger.debug(f"Sending a message: {message}")
-    try:
-        self.current_session.send(message)
-    except WebSocketException as e:
-        # We rarely get this exception while replacing the underlying WebSocket connections.
-        # We can do one more try here as the self.current_session should be ready now.
-        if self.logger.level <= logging.DEBUG:
-            self.logger.debug(
-                f"Failed to send a message (error: {e}, message: {message})"
-                " as the underlying connection was replaced. Retrying the same request only one time..."
-            )
-        # Although acquiring self.connect_operation_lock also for the first method call is the safest way,
-        # we avoid synchronizing a lot for better performance. That's why we are doing a retry here.
-        with self.connect_operation_lock:
-            if self.is_connected():
-                self.current_session.send(message)
-            else:
-                self.logger.warning(
-                    f"The current session (session id: {self.session_id()}) is no longer active. "
-                    "Failed to send a message"
-                )
-                raise e
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/socket_mode/websockets/index.html b/docs/api-docs/slack_sdk/socket_mode/websockets/index.html deleted file mode 100644 index 7653289dc..000000000 --- a/docs/api-docs/slack_sdk/socket_mode/websockets/index.html +++ /dev/null @@ -1,1027 +0,0 @@ - - - - - - -slack_sdk.socket_mode.websockets API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.socket_mode.websockets

-
-
-

websockets bassd Socket Mode client

- -
- -Expand source code - -
"""websockets bassd Socket Mode client
-
-* https://api.slack.com/apis/connections/socket
-* https://slack.dev/python-slack-sdk/socket-mode/
-* https://pypi.org/project/websockets/
-
-"""
-import asyncio
-import logging
-from asyncio import Future, Lock
-from logging import Logger
-from asyncio import Queue
-from typing import Union, Optional, List, Callable, Awaitable
-
-import websockets
-from websockets.exceptions import WebSocketException
-
-# To keep compatibility with websockets 8.x, we use this import over .legacy.client
-from websockets import WebSocketClientProtocol
-
-from slack_sdk.socket_mode.async_client import AsyncBaseSocketModeClient
-from slack_sdk.socket_mode.async_listeners import (
-    AsyncWebSocketMessageListener,
-    AsyncSocketModeRequestListener,
-)
-from slack_sdk.socket_mode.request import SocketModeRequest
-from slack_sdk.web.async_client import AsyncWebClient
-
-
-class SocketModeClient(AsyncBaseSocketModeClient):
-    logger: Logger
-    web_client: AsyncWebClient
-    app_token: str
-    wss_uri: Optional[str]
-    auto_reconnect_enabled: bool
-    message_queue: Queue
-    message_listeners: List[
-        Union[
-            AsyncWebSocketMessageListener,
-            Callable[
-                ["AsyncBaseSocketModeClient", dict, Optional[str]], Awaitable[None]
-            ],
-        ]
-    ]
-    socket_mode_request_listeners: List[
-        Union[
-            AsyncSocketModeRequestListener,
-            Callable[["AsyncBaseSocketModeClient", SocketModeRequest], Awaitable[None]],
-        ]
-    ]
-
-    message_receiver: Optional[Future]
-    message_processor: Future
-
-    ping_interval: float
-    trace_enabled: bool
-
-    current_session: Optional[WebSocketClientProtocol]
-    current_session_monitor: Optional[Future]
-
-    auto_reconnect_enabled: bool
-    default_auto_reconnect_enabled: bool
-    closed: bool
-    connect_operation_lock: Lock
-
-    def __init__(
-        self,
-        app_token: str,
-        logger: Optional[Logger] = None,
-        web_client: Optional[AsyncWebClient] = None,
-        auto_reconnect_enabled: bool = True,
-        ping_interval: float = 10,
-        trace_enabled: bool = False,
-    ):
-        """Socket Mode client
-
-        Args:
-            app_token: App-level token
-            logger: Custom logger
-            web_client: Web API client
-            auto_reconnect_enabled: True if automatic reconnection is enabled (default: True)
-            ping_interval: interval for ping-pong with Slack servers (seconds)
-            trace_enabled: True if more verbose logs to see what's happening under the hood
-        """
-        self.app_token = app_token
-        self.logger = logger or logging.getLogger(__name__)
-        self.web_client = web_client or AsyncWebClient()
-        self.closed = False
-        self.connect_operation_lock = Lock()
-        self.default_auto_reconnect_enabled = auto_reconnect_enabled
-        self.auto_reconnect_enabled = self.default_auto_reconnect_enabled
-        self.ping_interval = ping_interval
-        self.trace_enabled = trace_enabled
-        self.wss_uri = None
-        self.message_queue = Queue()
-        self.message_listeners = []
-        self.socket_mode_request_listeners = []
-        self.current_session = None
-        self.current_session_monitor = None
-
-        self.message_receiver = None
-        self.message_processor = asyncio.ensure_future(self.process_messages())
-
-    async def monitor_current_session(self) -> None:
-        # In the asyncio runtime, accessing a shared object (self.current_session here) from
-        # multiple tasks can cause race conditions and errors.
-        # To avoid such, we access only the session that is active when this loop starts.
-        session: WebSocketClientProtocol = self.current_session
-        session_id: str = await self.session_id()
-        if self.logger.level <= logging.DEBUG:
-            self.logger.debug(
-                f"A new monitor_current_session() execution loop for {session_id} started"
-            )
-        try:
-            while not self.closed:
-                if session != self.current_session:
-                    if self.logger.level <= logging.DEBUG:
-                        self.logger.debug(
-                            f"The monitor_current_session task for {session_id} is now cancelled"
-                        )
-                    break
-                await asyncio.sleep(self.ping_interval)
-                try:
-                    if self.auto_reconnect_enabled and (
-                        session is None or session.closed
-                    ):
-                        self.logger.info(
-                            f"The session ({session_id}) seems to be already closed. Reconnecting..."
-                        )
-                        await self.connect_to_new_endpoint()
-                except Exception as e:
-                    self.logger.error(
-                        "Failed to check the current session or reconnect to the server "
-                        f"(error: {type(e).__name__}, message: {e}, session: {session_id})"
-                    )
-        except asyncio.CancelledError:
-            if self.logger.level <= logging.DEBUG:
-                self.logger.debug(
-                    f"The monitor_current_session task for {session_id} is now cancelled"
-                )
-            raise
-
-    async def receive_messages(self) -> None:
-        # In the asyncio runtime, accessing a shared object (self.current_session here) from
-        # multiple tasks can cause race conditions and errors.
-        # To avoid such, we access only the session that is active when this loop starts.
-        session: WebSocketClientProtocol = self.current_session
-        session_id: str = await self.session_id()
-        consecutive_error_count = 0
-        if self.logger.level <= logging.DEBUG:
-            self.logger.debug(
-                f"A new receive_messages() execution loop with {session_id} started"
-            )
-        try:
-            while not self.closed:
-                if session != self.current_session:
-                    if self.logger.level <= logging.DEBUG:
-                        self.logger.debug(
-                            f"The running receive_messages task for {session_id} is now cancelled"
-                        )
-                    break
-                try:
-                    message = await session.recv()
-                    if message is not None:
-                        if isinstance(message, bytes):
-                            message = message.decode("utf-8")
-                        if self.logger.level <= logging.DEBUG:
-                            self.logger.debug(
-                                f"Received message: {message}, session: {session_id}"
-                            )
-                        await self.enqueue_message(message)
-                    consecutive_error_count = 0
-                except Exception as e:
-                    consecutive_error_count += 1
-                    self.logger.error(
-                        f"Failed to receive or enqueue a message: {type(e).__name__}, error: {e}, session: {session_id}"
-                    )
-                    if isinstance(e, websockets.ConnectionClosedError):
-                        await asyncio.sleep(self.ping_interval)
-                    else:
-                        await asyncio.sleep(consecutive_error_count)
-        except asyncio.CancelledError:
-            if self.logger.level <= logging.DEBUG:
-                self.logger.debug(
-                    f"The running receive_messages task for {session_id} is now cancelled"
-                )
-            raise
-
-    async def is_connected(self) -> bool:
-        return (
-            not self.closed
-            and self.current_session is not None
-            and not self.current_session.closed
-        )
-
-    async def session_id(self) -> str:
-        return self.build_session_id(self.current_session)
-
-    async def connect(self):
-        if self.wss_uri is None:
-            self.wss_uri = await self.issue_new_wss_url()
-        old_session: Optional[WebSocketClientProtocol] = (
-            None if self.current_session is None else self.current_session
-        )
-        # NOTE: websockets does not support proxy settings
-        self.current_session = await websockets.connect(
-            uri=self.wss_uri,
-            ping_interval=self.ping_interval,
-        )
-        session_id = await self.session_id()
-        self.auto_reconnect_enabled = self.default_auto_reconnect_enabled
-        self.logger.info(f"A new session ({session_id}) has been established")
-
-        if self.current_session_monitor is not None:
-            self.current_session_monitor.cancel()
-        self.current_session_monitor = asyncio.ensure_future(
-            self.monitor_current_session()
-        )
-
-        if self.logger.level <= logging.DEBUG:
-            self.logger.debug(
-                f"A new monitor_current_session() executor has been recreated for {session_id}"
-            )
-
-        if self.message_receiver is not None:
-            self.message_receiver.cancel()
-        self.message_receiver = asyncio.ensure_future(self.receive_messages())
-
-        if self.logger.level <= logging.DEBUG:
-            self.logger.debug(
-                f"A new receive_messages() executor has been recreated for {session_id}"
-            )
-
-        if old_session is not None:
-            await old_session.close()
-            old_session_id = self.build_session_id(old_session)
-            self.logger.info(f"The old session ({old_session_id}) has been abandoned")
-
-    async def disconnect(self):
-        if self.current_session is not None:
-            await self.current_session.close()
-
-    async def send_message(self, message: str):
-        session = self.current_session
-        session_id = self.build_session_id(session)
-        if self.logger.level <= logging.DEBUG:
-            self.logger.debug(f"Sending a message: {message}, session: {session_id}")
-        try:
-            await session.send(message)
-        except WebSocketException as e:
-            # We rarely get this exception while replacing the underlying WebSocket connections.
-            # We can do one more try here as the self.current_session should be ready now.
-            if self.logger.level <= logging.DEBUG:
-                self.logger.debug(
-                    f"Failed to send a message (error: {e}, message: {message}, session: {session_id})"
-                    " as the underlying connection was replaced. Retrying the same request only one time..."
-                )
-            # Although acquiring self.connect_operation_lock also for the first method call is the safest way,
-            # we avoid synchronizing a lot for better performance. That's why we are doing a retry here.
-            try:
-                if await self.is_connected():
-                    await self.current_session.send(message)
-                else:
-                    self.logger.warning(
-                        f"The current session ({session_id}) is no longer active. Failed to send a message"
-                    )
-                    raise e
-            finally:
-                if self.connect_operation_lock.locked() is True:
-                    self.connect_operation_lock.release()
-
-    async def close(self):
-        self.closed = True
-        self.auto_reconnect_enabled = False
-        await self.disconnect()
-        self.message_processor.cancel()
-        if self.current_session_monitor is not None:
-            self.current_session_monitor.cancel()
-        if self.message_receiver is not None:
-            self.message_receiver.cancel()
-
-    @classmethod
-    def build_session_id(cls, session: WebSocketClientProtocol) -> str:
-        if session is None:
-            return None
-        return "s_" + str(hash(session))
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class SocketModeClient -(app_token:ย str, logger:ย Optional[logging.Logger]ย =ย None, web_client:ย Optional[AsyncWebClient]ย =ย None, auto_reconnect_enabled:ย boolย =ย True, ping_interval:ย floatย =ย 10, trace_enabled:ย boolย =ย False) -
-
-

Socket Mode client

-

Args

-
-
app_token
-
App-level token
-
logger
-
Custom logger
-
web_client
-
Web API client
-
auto_reconnect_enabled
-
True if automatic reconnection is enabled (default: True)
-
ping_interval
-
interval for ping-pong with Slack servers (seconds)
-
trace_enabled
-
True if more verbose logs to see what's happening under the hood
-
-
- -Expand source code - -
class SocketModeClient(AsyncBaseSocketModeClient):
-    logger: Logger
-    web_client: AsyncWebClient
-    app_token: str
-    wss_uri: Optional[str]
-    auto_reconnect_enabled: bool
-    message_queue: Queue
-    message_listeners: List[
-        Union[
-            AsyncWebSocketMessageListener,
-            Callable[
-                ["AsyncBaseSocketModeClient", dict, Optional[str]], Awaitable[None]
-            ],
-        ]
-    ]
-    socket_mode_request_listeners: List[
-        Union[
-            AsyncSocketModeRequestListener,
-            Callable[["AsyncBaseSocketModeClient", SocketModeRequest], Awaitable[None]],
-        ]
-    ]
-
-    message_receiver: Optional[Future]
-    message_processor: Future
-
-    ping_interval: float
-    trace_enabled: bool
-
-    current_session: Optional[WebSocketClientProtocol]
-    current_session_monitor: Optional[Future]
-
-    auto_reconnect_enabled: bool
-    default_auto_reconnect_enabled: bool
-    closed: bool
-    connect_operation_lock: Lock
-
-    def __init__(
-        self,
-        app_token: str,
-        logger: Optional[Logger] = None,
-        web_client: Optional[AsyncWebClient] = None,
-        auto_reconnect_enabled: bool = True,
-        ping_interval: float = 10,
-        trace_enabled: bool = False,
-    ):
-        """Socket Mode client
-
-        Args:
-            app_token: App-level token
-            logger: Custom logger
-            web_client: Web API client
-            auto_reconnect_enabled: True if automatic reconnection is enabled (default: True)
-            ping_interval: interval for ping-pong with Slack servers (seconds)
-            trace_enabled: True if more verbose logs to see what's happening under the hood
-        """
-        self.app_token = app_token
-        self.logger = logger or logging.getLogger(__name__)
-        self.web_client = web_client or AsyncWebClient()
-        self.closed = False
-        self.connect_operation_lock = Lock()
-        self.default_auto_reconnect_enabled = auto_reconnect_enabled
-        self.auto_reconnect_enabled = self.default_auto_reconnect_enabled
-        self.ping_interval = ping_interval
-        self.trace_enabled = trace_enabled
-        self.wss_uri = None
-        self.message_queue = Queue()
-        self.message_listeners = []
-        self.socket_mode_request_listeners = []
-        self.current_session = None
-        self.current_session_monitor = None
-
-        self.message_receiver = None
-        self.message_processor = asyncio.ensure_future(self.process_messages())
-
-    async def monitor_current_session(self) -> None:
-        # In the asyncio runtime, accessing a shared object (self.current_session here) from
-        # multiple tasks can cause race conditions and errors.
-        # To avoid such, we access only the session that is active when this loop starts.
-        session: WebSocketClientProtocol = self.current_session
-        session_id: str = await self.session_id()
-        if self.logger.level <= logging.DEBUG:
-            self.logger.debug(
-                f"A new monitor_current_session() execution loop for {session_id} started"
-            )
-        try:
-            while not self.closed:
-                if session != self.current_session:
-                    if self.logger.level <= logging.DEBUG:
-                        self.logger.debug(
-                            f"The monitor_current_session task for {session_id} is now cancelled"
-                        )
-                    break
-                await asyncio.sleep(self.ping_interval)
-                try:
-                    if self.auto_reconnect_enabled and (
-                        session is None or session.closed
-                    ):
-                        self.logger.info(
-                            f"The session ({session_id}) seems to be already closed. Reconnecting..."
-                        )
-                        await self.connect_to_new_endpoint()
-                except Exception as e:
-                    self.logger.error(
-                        "Failed to check the current session or reconnect to the server "
-                        f"(error: {type(e).__name__}, message: {e}, session: {session_id})"
-                    )
-        except asyncio.CancelledError:
-            if self.logger.level <= logging.DEBUG:
-                self.logger.debug(
-                    f"The monitor_current_session task for {session_id} is now cancelled"
-                )
-            raise
-
-    async def receive_messages(self) -> None:
-        # In the asyncio runtime, accessing a shared object (self.current_session here) from
-        # multiple tasks can cause race conditions and errors.
-        # To avoid such, we access only the session that is active when this loop starts.
-        session: WebSocketClientProtocol = self.current_session
-        session_id: str = await self.session_id()
-        consecutive_error_count = 0
-        if self.logger.level <= logging.DEBUG:
-            self.logger.debug(
-                f"A new receive_messages() execution loop with {session_id} started"
-            )
-        try:
-            while not self.closed:
-                if session != self.current_session:
-                    if self.logger.level <= logging.DEBUG:
-                        self.logger.debug(
-                            f"The running receive_messages task for {session_id} is now cancelled"
-                        )
-                    break
-                try:
-                    message = await session.recv()
-                    if message is not None:
-                        if isinstance(message, bytes):
-                            message = message.decode("utf-8")
-                        if self.logger.level <= logging.DEBUG:
-                            self.logger.debug(
-                                f"Received message: {message}, session: {session_id}"
-                            )
-                        await self.enqueue_message(message)
-                    consecutive_error_count = 0
-                except Exception as e:
-                    consecutive_error_count += 1
-                    self.logger.error(
-                        f"Failed to receive or enqueue a message: {type(e).__name__}, error: {e}, session: {session_id}"
-                    )
-                    if isinstance(e, websockets.ConnectionClosedError):
-                        await asyncio.sleep(self.ping_interval)
-                    else:
-                        await asyncio.sleep(consecutive_error_count)
-        except asyncio.CancelledError:
-            if self.logger.level <= logging.DEBUG:
-                self.logger.debug(
-                    f"The running receive_messages task for {session_id} is now cancelled"
-                )
-            raise
-
-    async def is_connected(self) -> bool:
-        return (
-            not self.closed
-            and self.current_session is not None
-            and not self.current_session.closed
-        )
-
-    async def session_id(self) -> str:
-        return self.build_session_id(self.current_session)
-
-    async def connect(self):
-        if self.wss_uri is None:
-            self.wss_uri = await self.issue_new_wss_url()
-        old_session: Optional[WebSocketClientProtocol] = (
-            None if self.current_session is None else self.current_session
-        )
-        # NOTE: websockets does not support proxy settings
-        self.current_session = await websockets.connect(
-            uri=self.wss_uri,
-            ping_interval=self.ping_interval,
-        )
-        session_id = await self.session_id()
-        self.auto_reconnect_enabled = self.default_auto_reconnect_enabled
-        self.logger.info(f"A new session ({session_id}) has been established")
-
-        if self.current_session_monitor is not None:
-            self.current_session_monitor.cancel()
-        self.current_session_monitor = asyncio.ensure_future(
-            self.monitor_current_session()
-        )
-
-        if self.logger.level <= logging.DEBUG:
-            self.logger.debug(
-                f"A new monitor_current_session() executor has been recreated for {session_id}"
-            )
-
-        if self.message_receiver is not None:
-            self.message_receiver.cancel()
-        self.message_receiver = asyncio.ensure_future(self.receive_messages())
-
-        if self.logger.level <= logging.DEBUG:
-            self.logger.debug(
-                f"A new receive_messages() executor has been recreated for {session_id}"
-            )
-
-        if old_session is not None:
-            await old_session.close()
-            old_session_id = self.build_session_id(old_session)
-            self.logger.info(f"The old session ({old_session_id}) has been abandoned")
-
-    async def disconnect(self):
-        if self.current_session is not None:
-            await self.current_session.close()
-
-    async def send_message(self, message: str):
-        session = self.current_session
-        session_id = self.build_session_id(session)
-        if self.logger.level <= logging.DEBUG:
-            self.logger.debug(f"Sending a message: {message}, session: {session_id}")
-        try:
-            await session.send(message)
-        except WebSocketException as e:
-            # We rarely get this exception while replacing the underlying WebSocket connections.
-            # We can do one more try here as the self.current_session should be ready now.
-            if self.logger.level <= logging.DEBUG:
-                self.logger.debug(
-                    f"Failed to send a message (error: {e}, message: {message}, session: {session_id})"
-                    " as the underlying connection was replaced. Retrying the same request only one time..."
-                )
-            # Although acquiring self.connect_operation_lock also for the first method call is the safest way,
-            # we avoid synchronizing a lot for better performance. That's why we are doing a retry here.
-            try:
-                if await self.is_connected():
-                    await self.current_session.send(message)
-                else:
-                    self.logger.warning(
-                        f"The current session ({session_id}) is no longer active. Failed to send a message"
-                    )
-                    raise e
-            finally:
-                if self.connect_operation_lock.locked() is True:
-                    self.connect_operation_lock.release()
-
-    async def close(self):
-        self.closed = True
-        self.auto_reconnect_enabled = False
-        await self.disconnect()
-        self.message_processor.cancel()
-        if self.current_session_monitor is not None:
-            self.current_session_monitor.cancel()
-        if self.message_receiver is not None:
-            self.message_receiver.cancel()
-
-    @classmethod
-    def build_session_id(cls, session: WebSocketClientProtocol) -> str:
-        if session is None:
-            return None
-        return "s_" + str(hash(session))
-
-

Ancestors

- -

Class variables

-
-
var app_token :ย str
-
-
-
-
var auto_reconnect_enabled :ย bool
-
-
-
-
var closed :ย bool
-
-
-
-
var connect_operation_lock :ย asyncio.locks.Lock
-
-
-
-
var current_session :ย Optional[websockets.legacy.client.WebSocketClientProtocol]
-
-
-
-
var current_session_monitor :ย Optional[_asyncio.Future]
-
-
-
-
var default_auto_reconnect_enabled :ย bool
-
-
-
-
var logger :ย logging.Logger
-
-
-
-
var message_listeners :ย List[Union[AsyncWebSocketMessageListener,ย Callable[[AsyncBaseSocketModeClient,ย dict,ย Optional[str]],ย Awaitable[None]]]]
-
-
-
-
var message_processor :ย _asyncio.Future
-
-
-
-
var message_queue :ย asyncio.queues.Queue
-
-
-
-
var message_receiver :ย Optional[_asyncio.Future]
-
-
-
-
var ping_interval :ย float
-
-
-
-
var socket_mode_request_listeners :ย List[Union[AsyncSocketModeRequestListener,ย Callable[[AsyncBaseSocketModeClient,ย SocketModeRequest],ย Awaitable[None]]]]
-
-
-
-
var trace_enabled :ย bool
-
-
-
-
var web_client :ย AsyncWebClient
-
-
-
-
var wss_uri :ย Optional[str]
-
-
-
-
-

Static methods

-
-
-def build_session_id(session:ย websockets.legacy.client.WebSocketClientProtocol) โ€‘>ย str -
-
-
-
- -Expand source code - -
@classmethod
-def build_session_id(cls, session: WebSocketClientProtocol) -> str:
-    if session is None:
-        return None
-    return "s_" + str(hash(session))
-
-
-
-

Methods

-
-
-async def close(self) -
-
-
-
- -Expand source code - -
async def close(self):
-    self.closed = True
-    self.auto_reconnect_enabled = False
-    await self.disconnect()
-    self.message_processor.cancel()
-    if self.current_session_monitor is not None:
-        self.current_session_monitor.cancel()
-    if self.message_receiver is not None:
-        self.message_receiver.cancel()
-
-
-
-async def connect(self) -
-
-
-
- -Expand source code - -
async def connect(self):
-    if self.wss_uri is None:
-        self.wss_uri = await self.issue_new_wss_url()
-    old_session: Optional[WebSocketClientProtocol] = (
-        None if self.current_session is None else self.current_session
-    )
-    # NOTE: websockets does not support proxy settings
-    self.current_session = await websockets.connect(
-        uri=self.wss_uri,
-        ping_interval=self.ping_interval,
-    )
-    session_id = await self.session_id()
-    self.auto_reconnect_enabled = self.default_auto_reconnect_enabled
-    self.logger.info(f"A new session ({session_id}) has been established")
-
-    if self.current_session_monitor is not None:
-        self.current_session_monitor.cancel()
-    self.current_session_monitor = asyncio.ensure_future(
-        self.monitor_current_session()
-    )
-
-    if self.logger.level <= logging.DEBUG:
-        self.logger.debug(
-            f"A new monitor_current_session() executor has been recreated for {session_id}"
-        )
-
-    if self.message_receiver is not None:
-        self.message_receiver.cancel()
-    self.message_receiver = asyncio.ensure_future(self.receive_messages())
-
-    if self.logger.level <= logging.DEBUG:
-        self.logger.debug(
-            f"A new receive_messages() executor has been recreated for {session_id}"
-        )
-
-    if old_session is not None:
-        await old_session.close()
-        old_session_id = self.build_session_id(old_session)
-        self.logger.info(f"The old session ({old_session_id}) has been abandoned")
-
-
-
-async def disconnect(self) -
-
-
-
- -Expand source code - -
async def disconnect(self):
-    if self.current_session is not None:
-        await self.current_session.close()
-
-
-
-async def is_connected(self) โ€‘>ย bool -
-
-
-
- -Expand source code - -
async def is_connected(self) -> bool:
-    return (
-        not self.closed
-        and self.current_session is not None
-        and not self.current_session.closed
-    )
-
-
-
-async def monitor_current_session(self) โ€‘>ย None -
-
-
-
- -Expand source code - -
async def monitor_current_session(self) -> None:
-    # In the asyncio runtime, accessing a shared object (self.current_session here) from
-    # multiple tasks can cause race conditions and errors.
-    # To avoid such, we access only the session that is active when this loop starts.
-    session: WebSocketClientProtocol = self.current_session
-    session_id: str = await self.session_id()
-    if self.logger.level <= logging.DEBUG:
-        self.logger.debug(
-            f"A new monitor_current_session() execution loop for {session_id} started"
-        )
-    try:
-        while not self.closed:
-            if session != self.current_session:
-                if self.logger.level <= logging.DEBUG:
-                    self.logger.debug(
-                        f"The monitor_current_session task for {session_id} is now cancelled"
-                    )
-                break
-            await asyncio.sleep(self.ping_interval)
-            try:
-                if self.auto_reconnect_enabled and (
-                    session is None or session.closed
-                ):
-                    self.logger.info(
-                        f"The session ({session_id}) seems to be already closed. Reconnecting..."
-                    )
-                    await self.connect_to_new_endpoint()
-            except Exception as e:
-                self.logger.error(
-                    "Failed to check the current session or reconnect to the server "
-                    f"(error: {type(e).__name__}, message: {e}, session: {session_id})"
-                )
-    except asyncio.CancelledError:
-        if self.logger.level <= logging.DEBUG:
-            self.logger.debug(
-                f"The monitor_current_session task for {session_id} is now cancelled"
-            )
-        raise
-
-
-
-async def receive_messages(self) โ€‘>ย None -
-
-
-
- -Expand source code - -
async def receive_messages(self) -> None:
-    # In the asyncio runtime, accessing a shared object (self.current_session here) from
-    # multiple tasks can cause race conditions and errors.
-    # To avoid such, we access only the session that is active when this loop starts.
-    session: WebSocketClientProtocol = self.current_session
-    session_id: str = await self.session_id()
-    consecutive_error_count = 0
-    if self.logger.level <= logging.DEBUG:
-        self.logger.debug(
-            f"A new receive_messages() execution loop with {session_id} started"
-        )
-    try:
-        while not self.closed:
-            if session != self.current_session:
-                if self.logger.level <= logging.DEBUG:
-                    self.logger.debug(
-                        f"The running receive_messages task for {session_id} is now cancelled"
-                    )
-                break
-            try:
-                message = await session.recv()
-                if message is not None:
-                    if isinstance(message, bytes):
-                        message = message.decode("utf-8")
-                    if self.logger.level <= logging.DEBUG:
-                        self.logger.debug(
-                            f"Received message: {message}, session: {session_id}"
-                        )
-                    await self.enqueue_message(message)
-                consecutive_error_count = 0
-            except Exception as e:
-                consecutive_error_count += 1
-                self.logger.error(
-                    f"Failed to receive or enqueue a message: {type(e).__name__}, error: {e}, session: {session_id}"
-                )
-                if isinstance(e, websockets.ConnectionClosedError):
-                    await asyncio.sleep(self.ping_interval)
-                else:
-                    await asyncio.sleep(consecutive_error_count)
-    except asyncio.CancelledError:
-        if self.logger.level <= logging.DEBUG:
-            self.logger.debug(
-                f"The running receive_messages task for {session_id} is now cancelled"
-            )
-        raise
-
-
-
-async def send_message(self, message:ย str) -
-
-
-
- -Expand source code - -
async def send_message(self, message: str):
-    session = self.current_session
-    session_id = self.build_session_id(session)
-    if self.logger.level <= logging.DEBUG:
-        self.logger.debug(f"Sending a message: {message}, session: {session_id}")
-    try:
-        await session.send(message)
-    except WebSocketException as e:
-        # We rarely get this exception while replacing the underlying WebSocket connections.
-        # We can do one more try here as the self.current_session should be ready now.
-        if self.logger.level <= logging.DEBUG:
-            self.logger.debug(
-                f"Failed to send a message (error: {e}, message: {message}, session: {session_id})"
-                " as the underlying connection was replaced. Retrying the same request only one time..."
-            )
-        # Although acquiring self.connect_operation_lock also for the first method call is the safest way,
-        # we avoid synchronizing a lot for better performance. That's why we are doing a retry here.
-        try:
-            if await self.is_connected():
-                await self.current_session.send(message)
-            else:
-                self.logger.warning(
-                    f"The current session ({session_id}) is no longer active. Failed to send a message"
-                )
-                raise e
-        finally:
-            if self.connect_operation_lock.locked() is True:
-                self.connect_operation_lock.release()
-
-
-
-async def session_id(self) โ€‘>ย str -
-
-
-
- -Expand source code - -
async def session_id(self) -> str:
-    return self.build_session_id(self.current_session)
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/version.html b/docs/api-docs/slack_sdk/version.html deleted file mode 100644 index 7bbdbba55..000000000 --- a/docs/api-docs/slack_sdk/version.html +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - -slack_sdk.version API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.version

-
-
-

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

-
- -Expand source code - -
"""Check the latest version at https://pypi.org/project/slack-sdk/"""
-__version__ = "3.11.2"
-
-
-
-
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/web/async_base_client.html b/docs/api-docs/slack_sdk/web/async_base_client.html deleted file mode 100644 index 2f73b1fe1..000000000 --- a/docs/api-docs/slack_sdk/web/async_base_client.html +++ /dev/null @@ -1,609 +0,0 @@ - - - - - - -slack_sdk.web.async_base_client API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.web.async_base_client

-
-
-
- -Expand source code - -
import logging
-from ssl import SSLContext
-from typing import Optional, Union, Dict, Any, List
-
-import aiohttp
-from aiohttp import FormData, BasicAuth
-
-from .async_internal_utils import (
-    _files_to_data,
-    _request_with_session,
-)
-from .async_slack_response import AsyncSlackResponse
-from .deprecation import show_2020_01_deprecation
-from .internal_utils import (
-    convert_bool_to_0_or_1,
-    _build_req_args,
-    _get_url,
-    get_user_agent,
-)
-from ..proxy_env_variable_loader import load_http_proxy_from_env
-
-from slack_sdk.http_retry.builtin_async_handlers import async_default_handlers
-from slack_sdk.http_retry.handler import RetryHandler
-
-
-class AsyncBaseClient:
-    BASE_URL = "https://www.slack.com/api/"
-
-    def __init__(
-        self,
-        token: Optional[str] = None,
-        base_url: str = BASE_URL,
-        timeout: int = 30,
-        ssl: Optional[SSLContext] = None,
-        proxy: Optional[str] = None,
-        session: Optional[aiohttp.ClientSession] = None,
-        trust_env_in_session: bool = False,
-        headers: Optional[dict] = None,
-        user_agent_prefix: Optional[str] = None,
-        user_agent_suffix: Optional[str] = None,
-        # for Org-Wide App installation
-        team_id: Optional[str] = None,
-        logger: Optional[logging.Logger] = None,
-        retry_handlers: Optional[List[RetryHandler]] = None,
-    ):
-        self.token = None if token is None else token.strip()
-        self.base_url = base_url
-        self.timeout = timeout
-        self.ssl = ssl
-        self.proxy = proxy
-        self.session = session
-        # https://github.com/slackapi/python-slack-sdk/issues/738
-        self.trust_env_in_session = trust_env_in_session
-        self.headers = headers or {}
-        self.headers["User-Agent"] = get_user_agent(
-            user_agent_prefix, user_agent_suffix
-        )
-        self.default_params = {}
-        if team_id is not None:
-            self.default_params["team_id"] = team_id
-        self._logger = logger if logger is not None else logging.getLogger(__name__)
-        self.retry_handlers = (
-            retry_handlers if retry_handlers is not None else async_default_handlers()
-        )
-
-        if self.proxy is None or len(self.proxy.strip()) == 0:
-            env_variable = load_http_proxy_from_env(self._logger)
-            if env_variable is not None:
-                self.proxy = env_variable
-
-    async def api_call(  # skipcq: PYL-R1710
-        self,
-        api_method: str,
-        *,
-        http_verb: str = "POST",
-        files: dict = None,
-        data: Union[dict, FormData] = None,
-        params: dict = None,
-        json: dict = None,  # skipcq: PYL-W0621
-        headers: dict = None,
-        auth: dict = None,
-    ) -> AsyncSlackResponse:
-        """Create a request and execute the API call to Slack.
-
-        Args:
-            api_method (str): The target Slack API method.
-                e.g. 'chat.postMessage'
-            http_verb (str): HTTP Verb. e.g. 'POST'
-            files (dict): Files to multipart upload.
-                e.g. {image OR file: file_object OR file_path}
-            data: The body to attach to the request. If a dictionary is
-                provided, form-encoding will take place.
-                e.g. {'key1': 'value1', 'key2': 'value2'}
-            params (dict): The URL parameters to append to the URL.
-                e.g. {'key1': 'value1', 'key2': 'value2'}
-            json (dict): JSON for the body to attach to the request
-                (if files or data is not specified).
-                e.g. {'key1': 'value1', 'key2': 'value2'}
-            headers (dict): Additional request headers
-            auth (dict): A dictionary that consists of client_id and client_secret
-
-        Returns:
-            (AsyncSlackResponse)
-                The server's response to an HTTP request. Data
-                from the response can be accessed like a dict.
-                If the response included 'next_cursor' it can
-                be iterated on to execute subsequent requests.
-
-        Raises:
-            SlackApiError: The following Slack API call failed:
-                'chat.postMessage'.
-            SlackRequestError: Json data can only be submitted as
-                POST requests.
-        """
-
-        api_url = _get_url(self.base_url, api_method)
-        if auth is not None:
-            if isinstance(auth, dict):
-                auth = BasicAuth(auth["client_id"], auth["client_secret"])
-            if isinstance(auth, BasicAuth):
-                if headers is None:
-                    headers = {}
-                headers["Authorization"] = auth.encode()
-                auth = None
-
-        headers = headers or {}
-        headers.update(self.headers)
-        req_args = _build_req_args(
-            token=self.token,
-            http_verb=http_verb,
-            files=files,
-            data=data,
-            default_params=self.default_params,
-            params=params,
-            json=json,  # skipcq: PYL-W0621
-            headers=headers,
-            auth=auth,
-            ssl=self.ssl,
-            proxy=self.proxy,
-        )
-
-        show_2020_01_deprecation(api_method)
-
-        return await self._send(
-            http_verb=http_verb,
-            api_url=api_url,
-            req_args=req_args,
-        )
-
-    async def _send(
-        self, http_verb: str, api_url: str, req_args: dict
-    ) -> AsyncSlackResponse:
-        """Sends the request out for transmission.
-
-        Args:
-            http_verb (str): The HTTP verb. e.g. 'GET' or 'POST'.
-            api_url (str): The Slack API url. e.g. 'https://slack.com/api/chat.postMessage'
-            req_args (dict): The request arguments to be attached to the request.
-            e.g.
-            {
-                json: {
-                    'attachments': [{"pretext": "pre-hello", "text": "text-world"}],
-                    'channel': '#random'
-                }
-            }
-        Returns:
-            The response parsed into a AsyncSlackResponse object.
-        """
-        open_files = _files_to_data(req_args)
-        try:
-            if "params" in req_args:
-                # True/False -> "1"/"0"
-                req_args["params"] = convert_bool_to_0_or_1(req_args["params"])
-
-            res = await self._request(
-                http_verb=http_verb, api_url=api_url, req_args=req_args
-            )
-        finally:
-            for f in open_files:
-                f.close()
-
-        data = {
-            "client": self,
-            "http_verb": http_verb,
-            "api_url": api_url,
-            "req_args": req_args,
-        }
-        return AsyncSlackResponse(**{**data, **res}).validate()
-
-    async def _request(self, *, http_verb, api_url, req_args) -> Dict[str, Any]:
-        """Submit the HTTP request with the running session or a new session.
-        Returns:
-            A dictionary of the response data.
-        """
-        return await _request_with_session(
-            current_session=self.session,
-            timeout=self.timeout,
-            logger=self._logger,
-            http_verb=http_verb,
-            api_url=api_url,
-            req_args=req_args,
-            retry_handlers=self.retry_handlers,
-        )
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class AsyncBaseClient -(token:ย Optional[str]ย =ย None, base_url:ย strย =ย 'https://www.slack.com/api/', timeout:ย intย =ย 30, ssl:ย Optional[ssl.SSLContext]ย =ย None, proxy:ย Optional[str]ย =ย None, session:ย Optional[aiohttp.client.ClientSession]ย =ย None, trust_env_in_session:ย boolย =ย False, headers:ย Optional[dict]ย =ย None, user_agent_prefix:ย Optional[str]ย =ย None, user_agent_suffix:ย Optional[str]ย =ย None, team_id:ย Optional[str]ย =ย None, logger:ย Optional[logging.Logger]ย =ย None, retry_handlers:ย Optional[List[RetryHandler]]ย =ย None) -
-
-
-
- -Expand source code - -
class AsyncBaseClient:
-    BASE_URL = "https://www.slack.com/api/"
-
-    def __init__(
-        self,
-        token: Optional[str] = None,
-        base_url: str = BASE_URL,
-        timeout: int = 30,
-        ssl: Optional[SSLContext] = None,
-        proxy: Optional[str] = None,
-        session: Optional[aiohttp.ClientSession] = None,
-        trust_env_in_session: bool = False,
-        headers: Optional[dict] = None,
-        user_agent_prefix: Optional[str] = None,
-        user_agent_suffix: Optional[str] = None,
-        # for Org-Wide App installation
-        team_id: Optional[str] = None,
-        logger: Optional[logging.Logger] = None,
-        retry_handlers: Optional[List[RetryHandler]] = None,
-    ):
-        self.token = None if token is None else token.strip()
-        self.base_url = base_url
-        self.timeout = timeout
-        self.ssl = ssl
-        self.proxy = proxy
-        self.session = session
-        # https://github.com/slackapi/python-slack-sdk/issues/738
-        self.trust_env_in_session = trust_env_in_session
-        self.headers = headers or {}
-        self.headers["User-Agent"] = get_user_agent(
-            user_agent_prefix, user_agent_suffix
-        )
-        self.default_params = {}
-        if team_id is not None:
-            self.default_params["team_id"] = team_id
-        self._logger = logger if logger is not None else logging.getLogger(__name__)
-        self.retry_handlers = (
-            retry_handlers if retry_handlers is not None else async_default_handlers()
-        )
-
-        if self.proxy is None or len(self.proxy.strip()) == 0:
-            env_variable = load_http_proxy_from_env(self._logger)
-            if env_variable is not None:
-                self.proxy = env_variable
-
-    async def api_call(  # skipcq: PYL-R1710
-        self,
-        api_method: str,
-        *,
-        http_verb: str = "POST",
-        files: dict = None,
-        data: Union[dict, FormData] = None,
-        params: dict = None,
-        json: dict = None,  # skipcq: PYL-W0621
-        headers: dict = None,
-        auth: dict = None,
-    ) -> AsyncSlackResponse:
-        """Create a request and execute the API call to Slack.
-
-        Args:
-            api_method (str): The target Slack API method.
-                e.g. 'chat.postMessage'
-            http_verb (str): HTTP Verb. e.g. 'POST'
-            files (dict): Files to multipart upload.
-                e.g. {image OR file: file_object OR file_path}
-            data: The body to attach to the request. If a dictionary is
-                provided, form-encoding will take place.
-                e.g. {'key1': 'value1', 'key2': 'value2'}
-            params (dict): The URL parameters to append to the URL.
-                e.g. {'key1': 'value1', 'key2': 'value2'}
-            json (dict): JSON for the body to attach to the request
-                (if files or data is not specified).
-                e.g. {'key1': 'value1', 'key2': 'value2'}
-            headers (dict): Additional request headers
-            auth (dict): A dictionary that consists of client_id and client_secret
-
-        Returns:
-            (AsyncSlackResponse)
-                The server's response to an HTTP request. Data
-                from the response can be accessed like a dict.
-                If the response included 'next_cursor' it can
-                be iterated on to execute subsequent requests.
-
-        Raises:
-            SlackApiError: The following Slack API call failed:
-                'chat.postMessage'.
-            SlackRequestError: Json data can only be submitted as
-                POST requests.
-        """
-
-        api_url = _get_url(self.base_url, api_method)
-        if auth is not None:
-            if isinstance(auth, dict):
-                auth = BasicAuth(auth["client_id"], auth["client_secret"])
-            if isinstance(auth, BasicAuth):
-                if headers is None:
-                    headers = {}
-                headers["Authorization"] = auth.encode()
-                auth = None
-
-        headers = headers or {}
-        headers.update(self.headers)
-        req_args = _build_req_args(
-            token=self.token,
-            http_verb=http_verb,
-            files=files,
-            data=data,
-            default_params=self.default_params,
-            params=params,
-            json=json,  # skipcq: PYL-W0621
-            headers=headers,
-            auth=auth,
-            ssl=self.ssl,
-            proxy=self.proxy,
-        )
-
-        show_2020_01_deprecation(api_method)
-
-        return await self._send(
-            http_verb=http_verb,
-            api_url=api_url,
-            req_args=req_args,
-        )
-
-    async def _send(
-        self, http_verb: str, api_url: str, req_args: dict
-    ) -> AsyncSlackResponse:
-        """Sends the request out for transmission.
-
-        Args:
-            http_verb (str): The HTTP verb. e.g. 'GET' or 'POST'.
-            api_url (str): The Slack API url. e.g. 'https://slack.com/api/chat.postMessage'
-            req_args (dict): The request arguments to be attached to the request.
-            e.g.
-            {
-                json: {
-                    'attachments': [{"pretext": "pre-hello", "text": "text-world"}],
-                    'channel': '#random'
-                }
-            }
-        Returns:
-            The response parsed into a AsyncSlackResponse object.
-        """
-        open_files = _files_to_data(req_args)
-        try:
-            if "params" in req_args:
-                # True/False -> "1"/"0"
-                req_args["params"] = convert_bool_to_0_or_1(req_args["params"])
-
-            res = await self._request(
-                http_verb=http_verb, api_url=api_url, req_args=req_args
-            )
-        finally:
-            for f in open_files:
-                f.close()
-
-        data = {
-            "client": self,
-            "http_verb": http_verb,
-            "api_url": api_url,
-            "req_args": req_args,
-        }
-        return AsyncSlackResponse(**{**data, **res}).validate()
-
-    async def _request(self, *, http_verb, api_url, req_args) -> Dict[str, Any]:
-        """Submit the HTTP request with the running session or a new session.
-        Returns:
-            A dictionary of the response data.
-        """
-        return await _request_with_session(
-            current_session=self.session,
-            timeout=self.timeout,
-            logger=self._logger,
-            http_verb=http_verb,
-            api_url=api_url,
-            req_args=req_args,
-            retry_handlers=self.retry_handlers,
-        )
-
-

Subclasses

- -

Class variables

-
-
var BASE_URL
-
-
-
-
-

Methods

-
-
-async def api_call(self, api_method:ย str, *, http_verb:ย strย =ย 'POST', files:ย dictย =ย None, data:ย Union[dict,ย aiohttp.formdata.FormData]ย =ย None, params:ย dictย =ย None, json:ย dictย =ย None, headers:ย dictย =ย None, auth:ย dictย =ย None) โ€‘>ย AsyncSlackResponse -
-
-

Create a request and execute the API call to Slack.

-

Args

-
-
api_method : str
-
The target Slack API method. -e.g. 'chat.postMessage'
-
http_verb : str
-
HTTP Verb. e.g. 'POST'
-
files : dict
-
Files to multipart upload. -e.g. {image OR file: file_object OR file_path}
-
data
-
The body to attach to the request. If a dictionary is -provided, form-encoding will take place. -e.g. {'key1': 'value1', 'key2': 'value2'}
-
params : dict
-
The URL parameters to append to the URL. -e.g. {'key1': 'value1', 'key2': 'value2'}
-
json : dict
-
JSON for the body to attach to the request -(if files or data is not specified). -e.g. {'key1': 'value1', 'key2': 'value2'}
-
headers : dict
-
Additional request headers
-
auth : dict
-
A dictionary that consists of client_id and client_secret
-
-

Returns

-

(AsyncSlackResponse) -The server's response to an HTTP request. Data -from the response can be accessed like a dict. -If the response included 'next_cursor' it can -be iterated on to execute subsequent requests.

-

Raises

-
-
SlackApiError
-
The following Slack API call failed: -'chat.postMessage'.
-
SlackRequestError
-
Json data can only be submitted as -POST requests.
-
-
- -Expand source code - -
async def api_call(  # skipcq: PYL-R1710
-    self,
-    api_method: str,
-    *,
-    http_verb: str = "POST",
-    files: dict = None,
-    data: Union[dict, FormData] = None,
-    params: dict = None,
-    json: dict = None,  # skipcq: PYL-W0621
-    headers: dict = None,
-    auth: dict = None,
-) -> AsyncSlackResponse:
-    """Create a request and execute the API call to Slack.
-
-    Args:
-        api_method (str): The target Slack API method.
-            e.g. 'chat.postMessage'
-        http_verb (str): HTTP Verb. e.g. 'POST'
-        files (dict): Files to multipart upload.
-            e.g. {image OR file: file_object OR file_path}
-        data: The body to attach to the request. If a dictionary is
-            provided, form-encoding will take place.
-            e.g. {'key1': 'value1', 'key2': 'value2'}
-        params (dict): The URL parameters to append to the URL.
-            e.g. {'key1': 'value1', 'key2': 'value2'}
-        json (dict): JSON for the body to attach to the request
-            (if files or data is not specified).
-            e.g. {'key1': 'value1', 'key2': 'value2'}
-        headers (dict): Additional request headers
-        auth (dict): A dictionary that consists of client_id and client_secret
-
-    Returns:
-        (AsyncSlackResponse)
-            The server's response to an HTTP request. Data
-            from the response can be accessed like a dict.
-            If the response included 'next_cursor' it can
-            be iterated on to execute subsequent requests.
-
-    Raises:
-        SlackApiError: The following Slack API call failed:
-            'chat.postMessage'.
-        SlackRequestError: Json data can only be submitted as
-            POST requests.
-    """
-
-    api_url = _get_url(self.base_url, api_method)
-    if auth is not None:
-        if isinstance(auth, dict):
-            auth = BasicAuth(auth["client_id"], auth["client_secret"])
-        if isinstance(auth, BasicAuth):
-            if headers is None:
-                headers = {}
-            headers["Authorization"] = auth.encode()
-            auth = None
-
-    headers = headers or {}
-    headers.update(self.headers)
-    req_args = _build_req_args(
-        token=self.token,
-        http_verb=http_verb,
-        files=files,
-        data=data,
-        default_params=self.default_params,
-        params=params,
-        json=json,  # skipcq: PYL-W0621
-        headers=headers,
-        auth=auth,
-        ssl=self.ssl,
-        proxy=self.proxy,
-    )
-
-    show_2020_01_deprecation(api_method)
-
-    return await self._send(
-        http_verb=http_verb,
-        api_url=api_url,
-        req_args=req_args,
-    )
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/web/async_internal_utils.html b/docs/api-docs/slack_sdk/web/async_internal_utils.html deleted file mode 100644 index 846c90148..000000000 --- a/docs/api-docs/slack_sdk/web/async_internal_utils.html +++ /dev/null @@ -1,273 +0,0 @@ - - - - - - -slack_sdk.web.async_internal_utils API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.web.async_internal_utils

-
-
-
- -Expand source code - -
import asyncio
-import json
-import logging
-from asyncio import AbstractEventLoop
-from logging import Logger
-from typing import Optional, BinaryIO, Dict, Sequence, Union, List
-
-import aiohttp
-from aiohttp import ClientSession
-
-from slack_sdk.errors import SlackApiError
-from slack_sdk.web.internal_utils import _build_unexpected_body_error_message
-
-from slack_sdk.http_retry.async_handler import AsyncRetryHandler
-from slack_sdk.http_retry.request import HttpRequest as RetryHttpRequest
-from slack_sdk.http_retry.response import HttpResponse as RetryHttpResponse
-from slack_sdk.http_retry.state import RetryState
-
-
-def _get_event_loop() -> AbstractEventLoop:
-    """Retrieves the event loop or creates a new one."""
-    try:
-        return asyncio.get_event_loop()
-    except RuntimeError:
-        loop = asyncio.new_event_loop()
-        asyncio.set_event_loop(loop)
-        return loop
-
-
-def _files_to_data(req_args: dict) -> Sequence[BinaryIO]:
-    open_files = []
-    files = req_args.pop("files", None)
-    if files is not None:
-        for k, v in files.items():
-            if isinstance(v, str):
-                f = open(v.encode("utf-8", "ignore"), "rb")
-                open_files.append(f)
-                req_args["data"].update({k: f})
-            else:
-                req_args["data"].update({k: v})
-    return open_files
-
-
-async def _request_with_session(
-    *,
-    current_session: Optional[ClientSession],
-    timeout: int,
-    logger: Logger,
-    http_verb: str,
-    api_url: str,
-    req_args: dict,
-    # set the default to an empty array for legacy clients
-    retry_handlers: List[AsyncRetryHandler] = [],
-) -> Dict[str, any]:
-    """Submit the HTTP request with the running session or a new session.
-    Returns:
-        A dictionary of the response data.
-    """
-    session = None
-    use_running_session = current_session and not current_session.closed
-    if use_running_session:
-        session = current_session
-    else:
-        session = aiohttp.ClientSession(
-            timeout=aiohttp.ClientTimeout(total=timeout),
-            auth=req_args.pop("auth", None),
-        )
-
-    last_error: Optional[Exception] = None
-    resp: Optional[Dict[str, any]] = None
-    try:
-        retry_request = RetryHttpRequest(
-            method=http_verb,
-            url=api_url,
-            headers=req_args.get("headers", {}),
-            body_params=req_args.get("params"),
-            data=req_args.get("data"),
-        )
-
-        retry_state = RetryState()
-        counter_for_safety = 0
-        while counter_for_safety < 100:
-            counter_for_safety += 1
-            # If this is a retry, the next try started here. We can reset the flag.
-            retry_state.next_attempt_requested = False
-            retry_response: Optional[RetryHttpResponse] = None
-
-            if logger.level <= logging.DEBUG:
-
-                def convert_params(values: dict) -> dict:
-                    if not values or not isinstance(values, dict):
-                        return {}
-                    return {
-                        k: ("(bytes)" if isinstance(v, bytes) else v)
-                        for k, v in values.items()
-                    }
-
-                headers = {
-                    k: "(redacted)" if k.lower() == "authorization" else v
-                    for k, v in req_args.get("headers", {}).items()
-                }
-                logger.debug(
-                    f"Sending a request - url: {http_verb} {api_url}, "
-                    f"params: {convert_params(req_args.get('params'))}, "
-                    f"files: {convert_params(req_args.get('files'))}, "
-                    f"data: {convert_params(req_args.get('data'))}, "
-                    f"json: {convert_params(req_args.get('json'))}, "
-                    f"proxy: {convert_params(req_args.get('proxy'))}, "
-                    f"headers: {headers}"
-                )
-
-            try:
-                async with session.request(http_verb, api_url, **req_args) as res:
-                    data: Union[dict, bytes] = {}
-                    if res.content_type == "application/gzip":
-                        # admin.analytics.getFile
-                        data = await res.read()
-                        retry_response = RetryHttpResponse(
-                            status_code=res.status,
-                            headers=res.headers,
-                            data=data,
-                        )
-                    else:
-                        try:
-                            data = await res.json()
-                            retry_response = RetryHttpResponse(
-                                status_code=res.status,
-                                headers=res.headers,
-                                body=data,
-                            )
-                        except aiohttp.ContentTypeError:
-                            logger.debug(
-                                f"No response data returned from the following API call: {api_url}."
-                            )
-                        except json.decoder.JSONDecodeError:
-                            try:
-                                body: str = await res.text()
-                                message = _build_unexpected_body_error_message(body)
-                                raise SlackApiError(message, res)
-                            except Exception as e:
-                                raise SlackApiError(
-                                    f"Unexpectedly failed to read the response body: {str(e)}",
-                                    res,
-                                )
-
-                    if logger.level <= logging.DEBUG:
-                        body = data if isinstance(data, dict) else "(binary)"
-                        logger.debug(
-                            "Received the following response - "
-                            f"status: {res.status}, "
-                            f"headers: {dict(res.headers)}, "
-                            f"body: {body}"
-                        )
-
-                    if res.status == 429:
-                        for handler in retry_handlers:
-                            if await handler.can_retry_async(
-                                state=retry_state,
-                                request=retry_request,
-                                response=retry_response,
-                            ):
-                                if logger.level <= logging.DEBUG:
-                                    logger.info(
-                                        f"A retry handler found: {type(handler).__name__} "
-                                        f"for {http_verb} {api_url} - rate_limited"
-                                    )
-                                await handler.prepare_for_next_attempt_async(
-                                    state=retry_state,
-                                    request=retry_request,
-                                    response=retry_response,
-                                )
-                                break
-
-                    if retry_state.next_attempt_requested is False:
-                        response = {
-                            "data": data,
-                            "headers": res.headers,
-                            "status_code": res.status,
-                        }
-                        return response
-
-            except Exception as e:
-                last_error = e
-                for handler in retry_handlers:
-                    if await handler.can_retry_async(
-                        state=retry_state,
-                        request=retry_request,
-                        response=retry_response,
-                        error=e,
-                    ):
-                        if logger.level <= logging.DEBUG:
-                            logger.info(
-                                f"A retry handler found: {type(handler).__name__} "
-                                f"for {http_verb} {api_url} - {e}"
-                            )
-                        await handler.prepare_for_next_attempt_async(
-                            state=retry_state,
-                            request=retry_request,
-                            response=retry_response,
-                            error=e,
-                        )
-                        break
-
-                if retry_state.next_attempt_requested is False:
-                    raise last_error
-
-        if resp is not None:
-            return resp
-        raise last_error
-
-    finally:
-        if not use_running_session:
-            await session.close()
-
-    return response
-
-
-
-
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/web/async_slack_response.html b/docs/api-docs/slack_sdk/web/async_slack_response.html deleted file mode 100644 index 4e13b5bd9..000000000 --- a/docs/api-docs/slack_sdk/web/async_slack_response.html +++ /dev/null @@ -1,604 +0,0 @@ - - - - - - -slack_sdk.web.async_slack_response API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.web.async_slack_response

-
-
-

A Python module for interacting and consuming responses from Slack.

-
- -Expand source code - -
"""A Python module for interacting and consuming responses from Slack."""
-
-import logging
-from typing import Union
-
-import slack_sdk.errors as e
-from .internal_utils import _next_cursor_is_present
-
-
-class AsyncSlackResponse:
-    """An iterable container of response data.
-
-    Attributes:
-        data (dict): The json-encoded content of the response. Along
-            with the headers and status code information.
-
-    Methods:
-        validate: Check if the response from Slack was successful.
-        get: Retrieves any key from the response data.
-        next: Retrieves the next portion of results,
-            if 'next_cursor' is present.
-
-    Example:
-    ```python
-    import os
-    import slack
-
-    client = slack.AsyncWebClient(token=os.environ['SLACK_API_TOKEN'])
-
-    response1 = await client.auth_revoke(test='true')
-    assert not response1['revoked']
-
-    response2 = await client.auth_test()
-    assert response2.get('ok', False)
-
-    users = []
-    async for page in await client.users_list(limit=2):
-        users = users + page['members']
-    ```
-
-    Note:
-        Some responses return collections of information
-        like channel and user lists. If they do it's likely
-        that you'll only receive a portion of results. This
-        object allows you to iterate over the response which
-        makes subsequent API requests until your code hits
-        'break' or there are no more results to be found.
-
-        Any attributes or methods prefixed with _underscores are
-        intended to be "private" internal use only. They may be changed or
-        removed at anytime.
-    """
-
-    def __init__(
-        self,
-        *,
-        client,  # AsyncWebClient
-        http_verb: str,
-        api_url: str,
-        req_args: dict,
-        data: Union[dict, bytes],  # data can be binary data
-        headers: dict,
-        status_code: int,
-    ):
-        self.http_verb = http_verb
-        self.api_url = api_url
-        self.req_args = req_args
-        self.data = data
-        self.headers = headers
-        self.status_code = status_code
-        self._initial_data = data
-        self._iteration = None  # for __iter__ & __next__
-        self._client = client
-        self._logger = logging.getLogger(__name__)
-
-    def __str__(self):
-        """Return the Response data if object is converted to a string."""
-        if isinstance(self.data, bytes):
-            raise ValueError(
-                "As the response.data is binary data, this operation is unsupported"
-            )
-        return f"{self.data}"
-
-    def __contains__(self, key: str) -> bool:
-        return self.get(key) is not None
-
-    def __getitem__(self, key):
-        """Retrieves any key from the data store.
-
-        Note:
-            This is implemented so users can reference the
-            SlackResponse object like a dictionary.
-            e.g. response["ok"]
-
-        Returns:
-            The value from data or None.
-        """
-        if isinstance(self.data, bytes):
-            raise ValueError(
-                "As the response.data is binary data, this operation is unsupported"
-            )
-        if self.data is None:
-            raise ValueError(
-                "As the response.data is empty, this operation is unsupported"
-            )
-        return self.data.get(key, None)
-
-    def __aiter__(self):
-        """Enables the ability to iterate over the response.
-        It's required async-for the iterator protocol.
-
-        Note:
-            This enables Slack cursor-based pagination.
-
-        Returns:
-            (AsyncSlackResponse) self
-        """
-        if isinstance(self.data, bytes):
-            raise ValueError(
-                "As the response.data is binary data, this operation is unsupported"
-            )
-        self._iteration = 0
-        self.data = self._initial_data
-        return self
-
-    async def __anext__(self):
-        """Retrieves the next portion of results, if 'next_cursor' is present.
-
-        Note:
-            Some responses return collections of information
-            like channel and user lists. If they do it's likely
-            that you'll only receive a portion of results. This
-            method allows you to iterate over the response until
-            your code hits 'break' or there are no more results
-            to be found.
-
-        Returns:
-            (AsyncSlackResponse) self
-                With the new response data now attached to this object.
-
-        Raises:
-            SlackApiError: If the request to the Slack API failed.
-            StopAsyncIteration: If 'next_cursor' is not present or empty.
-        """
-        if isinstance(self.data, bytes):
-            raise ValueError(
-                "As the response.data is binary data, this operation is unsupported"
-            )
-        self._iteration += 1
-        if self._iteration == 1:
-            return self
-        if _next_cursor_is_present(self.data):  # skipcq: PYL-R1705
-            params = self.req_args.get("params", {})
-            if params is None:
-                params = {}
-            params.update({"cursor": self.data["response_metadata"]["next_cursor"]})
-            self.req_args.update({"params": params})
-
-            response = await self._client._request(  # skipcq: PYL-W0212
-                http_verb=self.http_verb,
-                api_url=self.api_url,
-                req_args=self.req_args,
-            )
-
-            self.data = response["data"]
-            self.headers = response["headers"]
-            self.status_code = response["status_code"]
-            return self.validate()
-        else:
-            raise StopAsyncIteration
-
-    def get(self, key, default=None):
-        """Retrieves any key from the response data.
-
-        Note:
-            This is implemented so users can reference the
-            SlackResponse object like a dictionary.
-            e.g. response.get("ok", False)
-
-        Returns:
-            The value from data or the specified default.
-        """
-        if isinstance(self.data, bytes):
-            raise ValueError(
-                "As the response.data is binary data, this operation is unsupported"
-            )
-        if self.data is None:
-            return None
-        return self.data.get(key, default)
-
-    def validate(self):
-        """Check if the response from Slack was successful.
-
-        Returns:
-            (AsyncSlackResponse)
-                This method returns it's own object. e.g. 'self'
-
-        Raises:
-            SlackApiError: The request to the Slack API failed.
-        """
-        if (
-            self.status_code == 200
-            and self.data
-            and (isinstance(self.data, bytes) or self.data.get("ok", False))
-        ):
-            return self
-        msg = f"The request to the Slack API failed. (url: {self.api_url})"
-        raise e.SlackApiError(message=msg, response=self)
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class AsyncSlackResponse -(*, client, http_verb:ย str, api_url:ย str, req_args:ย dict, data:ย Union[dict,ย bytes], headers:ย dict, status_code:ย int) -
-
-

An iterable container of response data.

-

Attributes

-
-
data : dict
-
The json-encoded content of the response. Along -with the headers and status code information.
-
-

Methods

-

validate: Check if the response from Slack was successful. -get: Retrieves any key from the response data. -next: Retrieves the next portion of results, -if 'next_cursor' is present.

-

Example:

-
import os
-import slack
-
-client = slack.AsyncWebClient(token=os.environ['SLACK_API_TOKEN'])
-
-response1 = await client.auth_revoke(test='true')
-assert not response1['revoked']
-
-response2 = await client.auth_test()
-assert response2.get('ok', False)
-
-users = []
-async for page in await client.users_list(limit=2):
-    users = users + page['members']
-
-

Note

-

Some responses return collections of information -like channel and user lists. If they do it's likely -that you'll only receive a portion of results. This -object allows you to iterate over the response which -makes subsequent API requests until your code hits -'break' or there are no more results to be found.

-

Any attributes or methods prefixed with _underscores are -intended to be "private" internal use only. They may be changed or -removed at anytime.

-
- -Expand source code - -
class AsyncSlackResponse:
-    """An iterable container of response data.
-
-    Attributes:
-        data (dict): The json-encoded content of the response. Along
-            with the headers and status code information.
-
-    Methods:
-        validate: Check if the response from Slack was successful.
-        get: Retrieves any key from the response data.
-        next: Retrieves the next portion of results,
-            if 'next_cursor' is present.
-
-    Example:
-    ```python
-    import os
-    import slack
-
-    client = slack.AsyncWebClient(token=os.environ['SLACK_API_TOKEN'])
-
-    response1 = await client.auth_revoke(test='true')
-    assert not response1['revoked']
-
-    response2 = await client.auth_test()
-    assert response2.get('ok', False)
-
-    users = []
-    async for page in await client.users_list(limit=2):
-        users = users + page['members']
-    ```
-
-    Note:
-        Some responses return collections of information
-        like channel and user lists. If they do it's likely
-        that you'll only receive a portion of results. This
-        object allows you to iterate over the response which
-        makes subsequent API requests until your code hits
-        'break' or there are no more results to be found.
-
-        Any attributes or methods prefixed with _underscores are
-        intended to be "private" internal use only. They may be changed or
-        removed at anytime.
-    """
-
-    def __init__(
-        self,
-        *,
-        client,  # AsyncWebClient
-        http_verb: str,
-        api_url: str,
-        req_args: dict,
-        data: Union[dict, bytes],  # data can be binary data
-        headers: dict,
-        status_code: int,
-    ):
-        self.http_verb = http_verb
-        self.api_url = api_url
-        self.req_args = req_args
-        self.data = data
-        self.headers = headers
-        self.status_code = status_code
-        self._initial_data = data
-        self._iteration = None  # for __iter__ & __next__
-        self._client = client
-        self._logger = logging.getLogger(__name__)
-
-    def __str__(self):
-        """Return the Response data if object is converted to a string."""
-        if isinstance(self.data, bytes):
-            raise ValueError(
-                "As the response.data is binary data, this operation is unsupported"
-            )
-        return f"{self.data}"
-
-    def __contains__(self, key: str) -> bool:
-        return self.get(key) is not None
-
-    def __getitem__(self, key):
-        """Retrieves any key from the data store.
-
-        Note:
-            This is implemented so users can reference the
-            SlackResponse object like a dictionary.
-            e.g. response["ok"]
-
-        Returns:
-            The value from data or None.
-        """
-        if isinstance(self.data, bytes):
-            raise ValueError(
-                "As the response.data is binary data, this operation is unsupported"
-            )
-        if self.data is None:
-            raise ValueError(
-                "As the response.data is empty, this operation is unsupported"
-            )
-        return self.data.get(key, None)
-
-    def __aiter__(self):
-        """Enables the ability to iterate over the response.
-        It's required async-for the iterator protocol.
-
-        Note:
-            This enables Slack cursor-based pagination.
-
-        Returns:
-            (AsyncSlackResponse) self
-        """
-        if isinstance(self.data, bytes):
-            raise ValueError(
-                "As the response.data is binary data, this operation is unsupported"
-            )
-        self._iteration = 0
-        self.data = self._initial_data
-        return self
-
-    async def __anext__(self):
-        """Retrieves the next portion of results, if 'next_cursor' is present.
-
-        Note:
-            Some responses return collections of information
-            like channel and user lists. If they do it's likely
-            that you'll only receive a portion of results. This
-            method allows you to iterate over the response until
-            your code hits 'break' or there are no more results
-            to be found.
-
-        Returns:
-            (AsyncSlackResponse) self
-                With the new response data now attached to this object.
-
-        Raises:
-            SlackApiError: If the request to the Slack API failed.
-            StopAsyncIteration: If 'next_cursor' is not present or empty.
-        """
-        if isinstance(self.data, bytes):
-            raise ValueError(
-                "As the response.data is binary data, this operation is unsupported"
-            )
-        self._iteration += 1
-        if self._iteration == 1:
-            return self
-        if _next_cursor_is_present(self.data):  # skipcq: PYL-R1705
-            params = self.req_args.get("params", {})
-            if params is None:
-                params = {}
-            params.update({"cursor": self.data["response_metadata"]["next_cursor"]})
-            self.req_args.update({"params": params})
-
-            response = await self._client._request(  # skipcq: PYL-W0212
-                http_verb=self.http_verb,
-                api_url=self.api_url,
-                req_args=self.req_args,
-            )
-
-            self.data = response["data"]
-            self.headers = response["headers"]
-            self.status_code = response["status_code"]
-            return self.validate()
-        else:
-            raise StopAsyncIteration
-
-    def get(self, key, default=None):
-        """Retrieves any key from the response data.
-
-        Note:
-            This is implemented so users can reference the
-            SlackResponse object like a dictionary.
-            e.g. response.get("ok", False)
-
-        Returns:
-            The value from data or the specified default.
-        """
-        if isinstance(self.data, bytes):
-            raise ValueError(
-                "As the response.data is binary data, this operation is unsupported"
-            )
-        if self.data is None:
-            return None
-        return self.data.get(key, default)
-
-    def validate(self):
-        """Check if the response from Slack was successful.
-
-        Returns:
-            (AsyncSlackResponse)
-                This method returns it's own object. e.g. 'self'
-
-        Raises:
-            SlackApiError: The request to the Slack API failed.
-        """
-        if (
-            self.status_code == 200
-            and self.data
-            and (isinstance(self.data, bytes) or self.data.get("ok", False))
-        ):
-            return self
-        msg = f"The request to the Slack API failed. (url: {self.api_url})"
-        raise e.SlackApiError(message=msg, response=self)
-
-

Methods

-
-
-def get(self, key, default=None) -
-
-

Retrieves any key from the response data.

-

Note

-

This is implemented so users can reference the -SlackResponse object like a dictionary. -e.g. response.get("ok", False)

-

Returns

-

The value from data or the specified default.

-
- -Expand source code - -
def get(self, key, default=None):
-    """Retrieves any key from the response data.
-
-    Note:
-        This is implemented so users can reference the
-        SlackResponse object like a dictionary.
-        e.g. response.get("ok", False)
-
-    Returns:
-        The value from data or the specified default.
-    """
-    if isinstance(self.data, bytes):
-        raise ValueError(
-            "As the response.data is binary data, this operation is unsupported"
-        )
-    if self.data is None:
-        return None
-    return self.data.get(key, default)
-
-
-
-def validate(self) -
-
-

Check if the response from Slack was successful.

-

Returns

-

(AsyncSlackResponse) -This method returns it's own object. e.g. 'self'

-

Raises

-
-
SlackApiError
-
The request to the Slack API failed.
-
-
- -Expand source code - -
def validate(self):
-    """Check if the response from Slack was successful.
-
-    Returns:
-        (AsyncSlackResponse)
-            This method returns it's own object. e.g. 'self'
-
-    Raises:
-        SlackApiError: The request to the Slack API failed.
-    """
-    if (
-        self.status_code == 200
-        and self.data
-        and (isinstance(self.data, bytes) or self.data.get("ok", False))
-    ):
-        return self
-    msg = f"The request to the Slack API failed. (url: {self.api_url})"
-    raise e.SlackApiError(message=msg, response=self)
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/web/base_client.html b/docs/api-docs/slack_sdk/web/base_client.html deleted file mode 100644 index 26583be97..000000000 --- a/docs/api-docs/slack_sdk/web/base_client.html +++ /dev/null @@ -1,1445 +0,0 @@ - - - - - - -slack_sdk.web.base_client API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.web.base_client

-
-
-

A Python module for interacting with Slack's Web API.

-
- -Expand source code - -
"""A Python module for interacting with Slack's Web API."""
-
-import copy
-import hashlib
-import hmac
-import io
-import json
-import logging
-import mimetypes
-import urllib
-import uuid
-import warnings
-from base64 import b64encode
-from http.client import HTTPResponse
-from ssl import SSLContext
-from typing import BinaryIO, Dict, List
-from typing import Optional, Union
-from urllib.error import HTTPError
-from urllib.parse import urlencode
-from urllib.request import Request, urlopen, OpenerDirector, ProxyHandler, HTTPSHandler
-
-import slack_sdk.errors as err
-from slack_sdk.errors import SlackRequestError
-from .deprecation import show_2020_01_deprecation
-from .internal_utils import (
-    convert_bool_to_0_or_1,
-    get_user_agent,
-    _get_url,
-    _build_req_args,
-    _build_unexpected_body_error_message,
-)
-from .slack_response import SlackResponse
-from slack_sdk.http_retry import default_retry_handlers
-from slack_sdk.http_retry.handler import RetryHandler
-from slack_sdk.http_retry.request import HttpRequest as RetryHttpRequest
-from slack_sdk.http_retry.response import HttpResponse as RetryHttpResponse
-from slack_sdk.http_retry.state import RetryState
-from slack_sdk.proxy_env_variable_loader import load_http_proxy_from_env
-
-
-class BaseClient:
-    BASE_URL = "https://www.slack.com/api/"
-
-    def __init__(
-        self,
-        token: Optional[str] = None,
-        base_url: str = BASE_URL,
-        timeout: int = 30,
-        ssl: Optional[SSLContext] = None,
-        proxy: Optional[str] = None,
-        headers: Optional[dict] = None,
-        user_agent_prefix: Optional[str] = None,
-        user_agent_suffix: Optional[str] = None,
-        # for Org-Wide App installation
-        team_id: Optional[str] = None,
-        logger: Optional[logging.Logger] = None,
-        retry_handlers: Optional[List[RetryHandler]] = None,
-    ):
-        self.token = None if token is None else token.strip()
-        self.base_url = base_url
-        self.timeout = timeout
-        self.ssl = ssl
-        self.proxy = proxy
-        self.headers = headers or {}
-        self.headers["User-Agent"] = get_user_agent(
-            user_agent_prefix, user_agent_suffix
-        )
-        self.default_params = {}
-        if team_id is not None:
-            self.default_params["team_id"] = team_id
-        self._logger = logger if logger is not None else logging.getLogger(__name__)
-
-        self.retry_handlers = (
-            retry_handlers if retry_handlers is not None else default_retry_handlers()
-        )
-
-        if self.proxy is None or len(self.proxy.strip()) == 0:
-            env_variable = load_http_proxy_from_env(self._logger)
-            if env_variable is not None:
-                self.proxy = env_variable
-
-    def api_call(  # skipcq: PYL-R1710
-        self,
-        api_method: str,
-        *,
-        http_verb: str = "POST",
-        files: dict = None,
-        data: Union[dict] = None,
-        params: dict = None,
-        json: dict = None,  # skipcq: PYL-W0621
-        headers: dict = None,
-        auth: dict = None,
-    ) -> SlackResponse:
-        """Create a request and execute the API call to Slack.
-
-        Args:
-            api_method (str): The target Slack API method.
-                e.g. 'chat.postMessage'
-            http_verb (str): HTTP Verb. e.g. 'POST'
-            files (dict): Files to multipart upload.
-                e.g. {image OR file: file_object OR file_path}
-            data: The body to attach to the request. If a dictionary is
-                provided, form-encoding will take place.
-                e.g. {'key1': 'value1', 'key2': 'value2'}
-            params (dict): The URL parameters to append to the URL.
-                e.g. {'key1': 'value1', 'key2': 'value2'}
-            json (dict): JSON for the body to attach to the request
-                (if files or data is not specified).
-                e.g. {'key1': 'value1', 'key2': 'value2'}
-            headers (dict): Additional request headers
-            auth (dict): A dictionary that consists of client_id and client_secret
-
-        Returns:
-            (SlackResponse)
-                The server's response to an HTTP request. Data
-                from the response can be accessed like a dict.
-                If the response included 'next_cursor' it can
-                be iterated on to execute subsequent requests.
-
-        Raises:
-            SlackApiError: The following Slack API call failed:
-                'chat.postMessage'.
-            SlackRequestError: Json data can only be submitted as
-                POST requests.
-        """
-
-        api_url = _get_url(self.base_url, api_method)
-        headers = headers or {}
-        headers.update(self.headers)
-        req_args = _build_req_args(
-            token=self.token,
-            http_verb=http_verb,
-            files=files,
-            data=data,
-            default_params=self.default_params,
-            params=params,
-            json=json,  # skipcq: PYL-W0621
-            headers=headers,
-            auth=auth,
-            ssl=self.ssl,
-            proxy=self.proxy,
-        )
-
-        show_2020_01_deprecation(api_method)
-        return self._sync_send(api_url=api_url, req_args=req_args)
-
-    # =================================================================
-    # urllib based WebClient
-    # =================================================================
-
-    def _sync_send(self, api_url, req_args) -> SlackResponse:
-        params = req_args["params"] if "params" in req_args else None
-        data = req_args["data"] if "data" in req_args else None
-        files = req_args["files"] if "files" in req_args else None
-        _json = req_args["json"] if "json" in req_args else None
-        headers = req_args["headers"] if "headers" in req_args else None
-        token = params.get("token") if params and "token" in params else None
-        auth = (
-            req_args["auth"] if "auth" in req_args else None
-        )  # Basic Auth for oauth.v2.access / oauth.access
-        if auth is not None:
-            if isinstance(auth, str):
-                headers["Authorization"] = auth
-            elif isinstance(auth, dict):
-                client_id, client_secret = auth["client_id"], auth["client_secret"]
-                value = b64encode(
-                    f"{client_id}:{client_secret}".encode("utf-8")
-                ).decode("ascii")
-                headers["Authorization"] = f"Basic {value}"
-            else:
-                self._logger.warning(
-                    f"As the auth: {auth}: {type(auth)} is unsupported, skipped"
-                )
-
-        body_params = {}
-        if params:
-            body_params.update(params)
-        if data:
-            body_params.update(data)
-
-        return self._urllib_api_call(
-            token=token,
-            url=api_url,
-            query_params={},
-            body_params=body_params,
-            files=files,
-            json_body=_json,
-            additional_headers=headers,
-        )
-
-    def _request_for_pagination(self, api_url, req_args) -> Dict[str, any]:
-        """This method is supposed to be used only for SlackResponse pagination
-
-        You can paginate using Python's for iterator as below:
-
-          for response in client.conversations_list(limit=100):
-              # do something with each response here
-        """
-        response = self._perform_urllib_http_request(url=api_url, args=req_args)
-        return {
-            "status_code": int(response["status"]),
-            "headers": dict(response["headers"]),
-            "data": json.loads(response["body"]),
-        }
-
-    def _urllib_api_call(
-        self,
-        *,
-        token: str = None,
-        url: str,
-        query_params: Dict[str, str] = {},
-        json_body: Dict = {},
-        body_params: Dict[str, str] = {},
-        files: Dict[str, io.BytesIO] = {},
-        additional_headers: Dict[str, str] = {},
-    ) -> SlackResponse:
-        """Performs a Slack API request and returns the result.
-
-        Args:
-            token: Slack API Token (either bot token or user token)
-            url: Complete URL (e.g., https://www.slack.com/api/chat.postMessage)
-            query_params: Query string
-            json_body: JSON data structure (it's still a dict at this point),
-                if you give this argument, body_params and files will be skipped
-            body_params: Form body params
-            files: Files to upload
-            additional_headers: Request headers to append
-
-        Returns:
-            API response
-        """
-        files_to_close: List[BinaryIO] = []
-        try:
-            # True/False -> "1"/"0"
-            query_params = convert_bool_to_0_or_1(query_params)
-            body_params = convert_bool_to_0_or_1(body_params)
-
-            if self._logger.level <= logging.DEBUG:
-
-                def convert_params(values: dict) -> dict:
-                    if not values or not isinstance(values, dict):
-                        return {}
-                    return {
-                        k: ("(bytes)" if isinstance(v, bytes) else v)
-                        for k, v in values.items()
-                    }
-
-                headers = {
-                    k: "(redacted)" if k.lower() == "authorization" else v
-                    for k, v in additional_headers.items()
-                }
-                self._logger.debug(
-                    f"Sending a request - url: {url}, "
-                    f"query_params: {convert_params(query_params)}, "
-                    f"body_params: {convert_params(body_params)}, "
-                    f"files: {convert_params(files)}, "
-                    f"json_body: {json_body}, "
-                    f"headers: {headers}"
-                )
-
-            request_data = {}
-            if files is not None and isinstance(files, dict) and len(files) > 0:
-                if body_params:
-                    for k, v in body_params.items():
-                        request_data.update({k: v})
-
-                for k, v in files.items():
-                    if isinstance(v, str):
-                        f: BinaryIO = open(v.encode("utf-8", "ignore"), "rb")
-                        files_to_close.append(f)
-                        request_data.update({k: f})
-                    elif isinstance(v, (bytearray, bytes)):
-                        request_data.update({k: io.BytesIO(v)})
-                    else:
-                        request_data.update({k: v})
-
-            request_headers = self._build_urllib_request_headers(
-                token=token or self.token,
-                has_json=json is not None,
-                has_files=files is not None,
-                additional_headers=additional_headers,
-            )
-            request_args = {
-                "headers": request_headers,
-                "data": request_data,
-                "params": body_params,
-                "files": files,
-                "json": json_body,
-            }
-            if query_params:
-                q = urlencode(query_params)
-                url = f"{url}&{q}" if "?" in url else f"{url}?{q}"
-
-            response = self._perform_urllib_http_request(url=url, args=request_args)
-            response_body = response.get("body", None)  # skipcq: PTC-W0039
-            response_body_data: Optional[Union[dict, bytes]] = response_body
-            if response_body is not None and not isinstance(response_body, bytes):
-                try:
-                    response_body_data = json.loads(response["body"])
-                except json.decoder.JSONDecodeError:
-                    message = _build_unexpected_body_error_message(
-                        response.get("body", "")
-                    )
-                    raise err.SlackApiError(message, response)
-
-            if query_params:
-                all_params = copy.copy(body_params)
-                all_params.update(query_params)
-            else:
-                all_params = body_params
-            request_args["params"] = all_params  # for backward-compatibility
-
-            return SlackResponse(
-                client=self,
-                http_verb="POST",  # you can use POST method for all the Web APIs
-                api_url=url,
-                req_args=request_args,
-                data=response_body_data,
-                headers=dict(response["headers"]),
-                status_code=response["status"],
-            ).validate()
-        finally:
-            for f in files_to_close:
-                if not f.closed:
-                    f.close()
-
-    def _perform_urllib_http_request(
-        self, *, url: str, args: Dict[str, Dict[str, any]]
-    ) -> Dict[str, any]:
-        """Performs an HTTP request and parses the response.
-
-        Args:
-            url: Complete URL (e.g., https://www.slack.com/api/chat.postMessage)
-            args: args has "headers", "data", "params", and "json"
-                "headers": Dict[str, str]
-                "data": Dict[str, any]
-                "params": Dict[str, str],
-                "json": Dict[str, any],
-
-        Returns:
-            dict {status: int, headers: Headers, body: str}
-        """
-        headers = args["headers"]
-        if args["json"]:
-            body = json.dumps(args["json"])
-            headers["Content-Type"] = "application/json;charset=utf-8"
-        elif args["data"]:
-            boundary = f"--------------{uuid.uuid4()}"
-            sep_boundary = b"\r\n--" + boundary.encode("ascii")
-            end_boundary = sep_boundary + b"--\r\n"
-            body = io.BytesIO()
-            data = args["data"]
-            for key, value in data.items():
-                readable = getattr(value, "readable", None)
-                if readable and value.readable():
-                    filename = "Uploaded file"
-                    name_attr = getattr(value, "name", None)
-                    if name_attr:
-                        filename = (
-                            name_attr.decode("utf-8")
-                            if isinstance(name_attr, bytes)
-                            else name_attr
-                        )
-                    if "filename" in data:
-                        filename = data["filename"]
-                    mimetype = (
-                        mimetypes.guess_type(filename)[0] or "application/octet-stream"
-                    )
-                    title = (
-                        f'\r\nContent-Disposition: form-data; name="{key}"; filename="{filename}"\r\n'
-                        + f"Content-Type: {mimetype}\r\n"
-                    )
-                    value = value.read()
-                else:
-                    title = f'\r\nContent-Disposition: form-data; name="{key}"\r\n'
-                    value = str(value).encode("utf-8")
-                body.write(sep_boundary)
-                body.write(title.encode("utf-8"))
-                body.write(b"\r\n")
-                body.write(value)
-
-            body.write(end_boundary)
-            body = body.getvalue()
-            headers["Content-Type"] = f"multipart/form-data; boundary={boundary}"
-            headers["Content-Length"] = len(body)
-        elif args["params"]:
-            body = urlencode(args["params"])
-            headers["Content-Type"] = "application/x-www-form-urlencoded"
-        else:
-            body = None
-
-        if isinstance(body, str):
-            body = body.encode("utf-8")
-
-        # NOTE: Intentionally ignore the `http_verb` here
-        # Slack APIs accepts any API method requests with POST methods
-        req = Request(method="POST", url=url, data=body, headers=headers)
-        resp = None
-        last_error = None
-
-        retry_state = RetryState()
-        counter_for_safety = 0
-        while counter_for_safety < 100:
-            counter_for_safety += 1
-            # If this is a retry, the next try started here. We can reset the flag.
-            retry_state.next_attempt_requested = False
-
-            try:
-                resp = self._perform_urllib_http_request_internal(url, req)
-                # The resp is a 200 OK response
-                return resp
-
-            except HTTPError as e:
-                resp = {"status": e.code, "headers": e.headers}
-                if e.code == 429:
-                    # for compatibility with aiohttp
-                    resp["headers"]["Retry-After"] = resp["headers"]["retry-after"]
-
-                # read the response body here
-                charset = e.headers.get_content_charset() or "utf-8"
-                response_body: str = e.read().decode(charset)
-                resp["body"] = response_body
-
-                # Try to find a retry handler for this error
-                retry_request = RetryHttpRequest.from_urllib_http_request(req)
-                retry_response = RetryHttpResponse(
-                    status_code=e.code,
-                    headers={k: [v] for k, v in e.headers.items()},
-                    data=response_body.encode("utf-8")
-                    if response_body is not None
-                    else None,
-                )
-                for handler in self.retry_handlers:
-                    if handler.can_retry(
-                        state=retry_state,
-                        request=retry_request,
-                        response=retry_response,
-                        error=e,
-                    ):
-                        if self._logger.level <= logging.DEBUG:
-                            self._logger.info(
-                                f"A retry handler found: {type(handler).__name__} for {req.method} {req.full_url} - {e}"
-                            )
-                        handler.prepare_for_next_attempt(
-                            state=retry_state,
-                            request=retry_request,
-                            response=retry_response,
-                            error=e,
-                        )
-                        break
-
-                if retry_state.next_attempt_requested is False:
-                    return resp
-
-            except Exception as err:
-                last_error = err
-                self._logger.error(
-                    f"Failed to send a request to Slack API server: {err}"
-                )
-
-                # Try to find a retry handler for this error
-                retry_request = RetryHttpRequest.from_urllib_http_request(req)
-                for handler in self.retry_handlers:
-                    if handler.can_retry(
-                        state=retry_state,
-                        request=retry_request,
-                        response=None,
-                        error=err,
-                    ):
-                        if self._logger.level <= logging.DEBUG:
-                            self._logger.info(
-                                f"A retry handler found: {type(handler).__name__} for {req.method} {req.full_url} - {err}"
-                            )
-                        handler.prepare_for_next_attempt(
-                            state=retry_state,
-                            request=retry_request,
-                            response=None,
-                            error=err,
-                        )
-                        self._logger.info(
-                            f"Going to retry the same request: {req.method} {req.full_url}"
-                        )
-                        break
-
-                if retry_state.next_attempt_requested is False:
-                    raise err
-
-        if resp is not None:
-            return resp
-        raise last_error
-
-    def _perform_urllib_http_request_internal(
-        self,
-        url: str,
-        req: Request,
-    ) -> Dict[str, any]:
-        # urllib not only opens http:// or https:// URLs, but also ftp:// and file://.
-        # With this it might be possible to open local files on the executing machine
-        # which might be a security risk if the URL to open can be manipulated by an external user.
-        # (BAN-B310)
-        if url.lower().startswith("http"):
-            opener: Optional[OpenerDirector] = None
-            if self.proxy is not None:
-                if isinstance(self.proxy, str):
-                    opener = urllib.request.build_opener(
-                        ProxyHandler({"http": self.proxy, "https": self.proxy}),
-                        HTTPSHandler(context=self.ssl),
-                    )
-                else:
-                    raise SlackRequestError(
-                        f"Invalid proxy detected: {self.proxy} must be a str value"
-                    )
-
-            # NOTE: BAN-B310 is already checked above
-            resp: Optional[HTTPResponse] = None
-            if opener:
-                resp = opener.open(req, timeout=self.timeout)  # skipcq: BAN-B310
-            else:
-                resp = urlopen(  # skipcq: BAN-B310
-                    req, context=self.ssl, timeout=self.timeout
-                )
-            if resp.headers.get_content_type() == "application/gzip":
-                # admin.analytics.getFile
-                body: bytes = resp.read()
-                if self._logger.level <= logging.DEBUG:
-                    self._logger.debug(
-                        "Received the following response - "
-                        f"status: {resp.code}, "
-                        f"headers: {dict(resp.headers)}, "
-                        f"body: (binary)"
-                    )
-                return {"status": resp.code, "headers": resp.headers, "body": body}
-
-            charset = resp.headers.get_content_charset() or "utf-8"
-            body: str = resp.read().decode(charset)  # read the response body here
-            if self._logger.level <= logging.DEBUG:
-                self._logger.debug(
-                    "Received the following response - "
-                    f"status: {resp.code}, "
-                    f"headers: {dict(resp.headers)}, "
-                    f"body: {body}"
-                )
-            return {"status": resp.code, "headers": resp.headers, "body": body}
-        raise SlackRequestError(f"Invalid URL detected: {url}")
-
-    def _build_urllib_request_headers(
-        self, token: str, has_json: bool, has_files: bool, additional_headers: dict
-    ) -> Dict[str, str]:
-        headers = {"Content-Type": "application/x-www-form-urlencoded"}
-        headers.update(self.headers)
-        if token:
-            headers.update({"Authorization": "Bearer {}".format(token)})
-        if additional_headers:
-            headers.update(additional_headers)
-        if has_json:
-            headers.update({"Content-Type": "application/json;charset=utf-8"})
-        if has_files:
-            # will be set afterwards
-            headers.pop("Content-Type", None)
-        return headers
-
-    # =================================================================
-
-    @staticmethod
-    def validate_slack_signature(
-        *, signing_secret: str, data: str, timestamp: str, signature: str
-    ) -> bool:
-        """
-        Slack creates a unique string for your app and shares it with you. Verify
-        requests from Slack with confidence by verifying signatures using your
-        signing secret.
-
-        On each HTTP request that Slack sends, we add an X-Slack-Signature HTTP
-        header. The signature is created by combining the signing secret with the
-        body of the request we're sending using a standard HMAC-SHA256 keyed hash.
-
-        https://api.slack.com/docs/verifying-requests-from-slack#how_to_make_a_request_signature_in_4_easy_steps__an_overview
-
-        Args:
-            signing_secret: Your application's signing secret, available in the
-                Slack API dashboard
-            data: The raw body of the incoming request - no headers, just the body.
-            timestamp: from the 'X-Slack-Request-Timestamp' header
-            signature: from the 'X-Slack-Signature' header - the calculated signature
-                should match this.
-
-        Returns:
-            True if signatures matches
-        """
-        warnings.warn(
-            "As this method is deprecated since slackclient 2.6.0, "
-            "use `from slack.signature import SignatureVerifier` instead",
-            DeprecationWarning,
-        )
-        format_req = str.encode(f"v0:{timestamp}:{data}")
-        encoded_secret = str.encode(signing_secret)
-        request_hash = hmac.new(encoded_secret, format_req, hashlib.sha256).hexdigest()
-        calculated_signature = f"v0={request_hash}"
-        return hmac.compare_digest(calculated_signature, signature)
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class BaseClient -(token:ย Optional[str]ย =ย None, base_url:ย strย =ย 'https://www.slack.com/api/', timeout:ย intย =ย 30, ssl:ย Optional[ssl.SSLContext]ย =ย None, proxy:ย Optional[str]ย =ย None, headers:ย Optional[dict]ย =ย None, user_agent_prefix:ย Optional[str]ย =ย None, user_agent_suffix:ย Optional[str]ย =ย None, team_id:ย Optional[str]ย =ย None, logger:ย Optional[logging.Logger]ย =ย None, retry_handlers:ย Optional[List[RetryHandler]]ย =ย None) -
-
-
-
- -Expand source code - -
class BaseClient:
-    BASE_URL = "https://www.slack.com/api/"
-
-    def __init__(
-        self,
-        token: Optional[str] = None,
-        base_url: str = BASE_URL,
-        timeout: int = 30,
-        ssl: Optional[SSLContext] = None,
-        proxy: Optional[str] = None,
-        headers: Optional[dict] = None,
-        user_agent_prefix: Optional[str] = None,
-        user_agent_suffix: Optional[str] = None,
-        # for Org-Wide App installation
-        team_id: Optional[str] = None,
-        logger: Optional[logging.Logger] = None,
-        retry_handlers: Optional[List[RetryHandler]] = None,
-    ):
-        self.token = None if token is None else token.strip()
-        self.base_url = base_url
-        self.timeout = timeout
-        self.ssl = ssl
-        self.proxy = proxy
-        self.headers = headers or {}
-        self.headers["User-Agent"] = get_user_agent(
-            user_agent_prefix, user_agent_suffix
-        )
-        self.default_params = {}
-        if team_id is not None:
-            self.default_params["team_id"] = team_id
-        self._logger = logger if logger is not None else logging.getLogger(__name__)
-
-        self.retry_handlers = (
-            retry_handlers if retry_handlers is not None else default_retry_handlers()
-        )
-
-        if self.proxy is None or len(self.proxy.strip()) == 0:
-            env_variable = load_http_proxy_from_env(self._logger)
-            if env_variable is not None:
-                self.proxy = env_variable
-
-    def api_call(  # skipcq: PYL-R1710
-        self,
-        api_method: str,
-        *,
-        http_verb: str = "POST",
-        files: dict = None,
-        data: Union[dict] = None,
-        params: dict = None,
-        json: dict = None,  # skipcq: PYL-W0621
-        headers: dict = None,
-        auth: dict = None,
-    ) -> SlackResponse:
-        """Create a request and execute the API call to Slack.
-
-        Args:
-            api_method (str): The target Slack API method.
-                e.g. 'chat.postMessage'
-            http_verb (str): HTTP Verb. e.g. 'POST'
-            files (dict): Files to multipart upload.
-                e.g. {image OR file: file_object OR file_path}
-            data: The body to attach to the request. If a dictionary is
-                provided, form-encoding will take place.
-                e.g. {'key1': 'value1', 'key2': 'value2'}
-            params (dict): The URL parameters to append to the URL.
-                e.g. {'key1': 'value1', 'key2': 'value2'}
-            json (dict): JSON for the body to attach to the request
-                (if files or data is not specified).
-                e.g. {'key1': 'value1', 'key2': 'value2'}
-            headers (dict): Additional request headers
-            auth (dict): A dictionary that consists of client_id and client_secret
-
-        Returns:
-            (SlackResponse)
-                The server's response to an HTTP request. Data
-                from the response can be accessed like a dict.
-                If the response included 'next_cursor' it can
-                be iterated on to execute subsequent requests.
-
-        Raises:
-            SlackApiError: The following Slack API call failed:
-                'chat.postMessage'.
-            SlackRequestError: Json data can only be submitted as
-                POST requests.
-        """
-
-        api_url = _get_url(self.base_url, api_method)
-        headers = headers or {}
-        headers.update(self.headers)
-        req_args = _build_req_args(
-            token=self.token,
-            http_verb=http_verb,
-            files=files,
-            data=data,
-            default_params=self.default_params,
-            params=params,
-            json=json,  # skipcq: PYL-W0621
-            headers=headers,
-            auth=auth,
-            ssl=self.ssl,
-            proxy=self.proxy,
-        )
-
-        show_2020_01_deprecation(api_method)
-        return self._sync_send(api_url=api_url, req_args=req_args)
-
-    # =================================================================
-    # urllib based WebClient
-    # =================================================================
-
-    def _sync_send(self, api_url, req_args) -> SlackResponse:
-        params = req_args["params"] if "params" in req_args else None
-        data = req_args["data"] if "data" in req_args else None
-        files = req_args["files"] if "files" in req_args else None
-        _json = req_args["json"] if "json" in req_args else None
-        headers = req_args["headers"] if "headers" in req_args else None
-        token = params.get("token") if params and "token" in params else None
-        auth = (
-            req_args["auth"] if "auth" in req_args else None
-        )  # Basic Auth for oauth.v2.access / oauth.access
-        if auth is not None:
-            if isinstance(auth, str):
-                headers["Authorization"] = auth
-            elif isinstance(auth, dict):
-                client_id, client_secret = auth["client_id"], auth["client_secret"]
-                value = b64encode(
-                    f"{client_id}:{client_secret}".encode("utf-8")
-                ).decode("ascii")
-                headers["Authorization"] = f"Basic {value}"
-            else:
-                self._logger.warning(
-                    f"As the auth: {auth}: {type(auth)} is unsupported, skipped"
-                )
-
-        body_params = {}
-        if params:
-            body_params.update(params)
-        if data:
-            body_params.update(data)
-
-        return self._urllib_api_call(
-            token=token,
-            url=api_url,
-            query_params={},
-            body_params=body_params,
-            files=files,
-            json_body=_json,
-            additional_headers=headers,
-        )
-
-    def _request_for_pagination(self, api_url, req_args) -> Dict[str, any]:
-        """This method is supposed to be used only for SlackResponse pagination
-
-        You can paginate using Python's for iterator as below:
-
-          for response in client.conversations_list(limit=100):
-              # do something with each response here
-        """
-        response = self._perform_urllib_http_request(url=api_url, args=req_args)
-        return {
-            "status_code": int(response["status"]),
-            "headers": dict(response["headers"]),
-            "data": json.loads(response["body"]),
-        }
-
-    def _urllib_api_call(
-        self,
-        *,
-        token: str = None,
-        url: str,
-        query_params: Dict[str, str] = {},
-        json_body: Dict = {},
-        body_params: Dict[str, str] = {},
-        files: Dict[str, io.BytesIO] = {},
-        additional_headers: Dict[str, str] = {},
-    ) -> SlackResponse:
-        """Performs a Slack API request and returns the result.
-
-        Args:
-            token: Slack API Token (either bot token or user token)
-            url: Complete URL (e.g., https://www.slack.com/api/chat.postMessage)
-            query_params: Query string
-            json_body: JSON data structure (it's still a dict at this point),
-                if you give this argument, body_params and files will be skipped
-            body_params: Form body params
-            files: Files to upload
-            additional_headers: Request headers to append
-
-        Returns:
-            API response
-        """
-        files_to_close: List[BinaryIO] = []
-        try:
-            # True/False -> "1"/"0"
-            query_params = convert_bool_to_0_or_1(query_params)
-            body_params = convert_bool_to_0_or_1(body_params)
-
-            if self._logger.level <= logging.DEBUG:
-
-                def convert_params(values: dict) -> dict:
-                    if not values or not isinstance(values, dict):
-                        return {}
-                    return {
-                        k: ("(bytes)" if isinstance(v, bytes) else v)
-                        for k, v in values.items()
-                    }
-
-                headers = {
-                    k: "(redacted)" if k.lower() == "authorization" else v
-                    for k, v in additional_headers.items()
-                }
-                self._logger.debug(
-                    f"Sending a request - url: {url}, "
-                    f"query_params: {convert_params(query_params)}, "
-                    f"body_params: {convert_params(body_params)}, "
-                    f"files: {convert_params(files)}, "
-                    f"json_body: {json_body}, "
-                    f"headers: {headers}"
-                )
-
-            request_data = {}
-            if files is not None and isinstance(files, dict) and len(files) > 0:
-                if body_params:
-                    for k, v in body_params.items():
-                        request_data.update({k: v})
-
-                for k, v in files.items():
-                    if isinstance(v, str):
-                        f: BinaryIO = open(v.encode("utf-8", "ignore"), "rb")
-                        files_to_close.append(f)
-                        request_data.update({k: f})
-                    elif isinstance(v, (bytearray, bytes)):
-                        request_data.update({k: io.BytesIO(v)})
-                    else:
-                        request_data.update({k: v})
-
-            request_headers = self._build_urllib_request_headers(
-                token=token or self.token,
-                has_json=json is not None,
-                has_files=files is not None,
-                additional_headers=additional_headers,
-            )
-            request_args = {
-                "headers": request_headers,
-                "data": request_data,
-                "params": body_params,
-                "files": files,
-                "json": json_body,
-            }
-            if query_params:
-                q = urlencode(query_params)
-                url = f"{url}&{q}" if "?" in url else f"{url}?{q}"
-
-            response = self._perform_urllib_http_request(url=url, args=request_args)
-            response_body = response.get("body", None)  # skipcq: PTC-W0039
-            response_body_data: Optional[Union[dict, bytes]] = response_body
-            if response_body is not None and not isinstance(response_body, bytes):
-                try:
-                    response_body_data = json.loads(response["body"])
-                except json.decoder.JSONDecodeError:
-                    message = _build_unexpected_body_error_message(
-                        response.get("body", "")
-                    )
-                    raise err.SlackApiError(message, response)
-
-            if query_params:
-                all_params = copy.copy(body_params)
-                all_params.update(query_params)
-            else:
-                all_params = body_params
-            request_args["params"] = all_params  # for backward-compatibility
-
-            return SlackResponse(
-                client=self,
-                http_verb="POST",  # you can use POST method for all the Web APIs
-                api_url=url,
-                req_args=request_args,
-                data=response_body_data,
-                headers=dict(response["headers"]),
-                status_code=response["status"],
-            ).validate()
-        finally:
-            for f in files_to_close:
-                if not f.closed:
-                    f.close()
-
-    def _perform_urllib_http_request(
-        self, *, url: str, args: Dict[str, Dict[str, any]]
-    ) -> Dict[str, any]:
-        """Performs an HTTP request and parses the response.
-
-        Args:
-            url: Complete URL (e.g., https://www.slack.com/api/chat.postMessage)
-            args: args has "headers", "data", "params", and "json"
-                "headers": Dict[str, str]
-                "data": Dict[str, any]
-                "params": Dict[str, str],
-                "json": Dict[str, any],
-
-        Returns:
-            dict {status: int, headers: Headers, body: str}
-        """
-        headers = args["headers"]
-        if args["json"]:
-            body = json.dumps(args["json"])
-            headers["Content-Type"] = "application/json;charset=utf-8"
-        elif args["data"]:
-            boundary = f"--------------{uuid.uuid4()}"
-            sep_boundary = b"\r\n--" + boundary.encode("ascii")
-            end_boundary = sep_boundary + b"--\r\n"
-            body = io.BytesIO()
-            data = args["data"]
-            for key, value in data.items():
-                readable = getattr(value, "readable", None)
-                if readable and value.readable():
-                    filename = "Uploaded file"
-                    name_attr = getattr(value, "name", None)
-                    if name_attr:
-                        filename = (
-                            name_attr.decode("utf-8")
-                            if isinstance(name_attr, bytes)
-                            else name_attr
-                        )
-                    if "filename" in data:
-                        filename = data["filename"]
-                    mimetype = (
-                        mimetypes.guess_type(filename)[0] or "application/octet-stream"
-                    )
-                    title = (
-                        f'\r\nContent-Disposition: form-data; name="{key}"; filename="{filename}"\r\n'
-                        + f"Content-Type: {mimetype}\r\n"
-                    )
-                    value = value.read()
-                else:
-                    title = f'\r\nContent-Disposition: form-data; name="{key}"\r\n'
-                    value = str(value).encode("utf-8")
-                body.write(sep_boundary)
-                body.write(title.encode("utf-8"))
-                body.write(b"\r\n")
-                body.write(value)
-
-            body.write(end_boundary)
-            body = body.getvalue()
-            headers["Content-Type"] = f"multipart/form-data; boundary={boundary}"
-            headers["Content-Length"] = len(body)
-        elif args["params"]:
-            body = urlencode(args["params"])
-            headers["Content-Type"] = "application/x-www-form-urlencoded"
-        else:
-            body = None
-
-        if isinstance(body, str):
-            body = body.encode("utf-8")
-
-        # NOTE: Intentionally ignore the `http_verb` here
-        # Slack APIs accepts any API method requests with POST methods
-        req = Request(method="POST", url=url, data=body, headers=headers)
-        resp = None
-        last_error = None
-
-        retry_state = RetryState()
-        counter_for_safety = 0
-        while counter_for_safety < 100:
-            counter_for_safety += 1
-            # If this is a retry, the next try started here. We can reset the flag.
-            retry_state.next_attempt_requested = False
-
-            try:
-                resp = self._perform_urllib_http_request_internal(url, req)
-                # The resp is a 200 OK response
-                return resp
-
-            except HTTPError as e:
-                resp = {"status": e.code, "headers": e.headers}
-                if e.code == 429:
-                    # for compatibility with aiohttp
-                    resp["headers"]["Retry-After"] = resp["headers"]["retry-after"]
-
-                # read the response body here
-                charset = e.headers.get_content_charset() or "utf-8"
-                response_body: str = e.read().decode(charset)
-                resp["body"] = response_body
-
-                # Try to find a retry handler for this error
-                retry_request = RetryHttpRequest.from_urllib_http_request(req)
-                retry_response = RetryHttpResponse(
-                    status_code=e.code,
-                    headers={k: [v] for k, v in e.headers.items()},
-                    data=response_body.encode("utf-8")
-                    if response_body is not None
-                    else None,
-                )
-                for handler in self.retry_handlers:
-                    if handler.can_retry(
-                        state=retry_state,
-                        request=retry_request,
-                        response=retry_response,
-                        error=e,
-                    ):
-                        if self._logger.level <= logging.DEBUG:
-                            self._logger.info(
-                                f"A retry handler found: {type(handler).__name__} for {req.method} {req.full_url} - {e}"
-                            )
-                        handler.prepare_for_next_attempt(
-                            state=retry_state,
-                            request=retry_request,
-                            response=retry_response,
-                            error=e,
-                        )
-                        break
-
-                if retry_state.next_attempt_requested is False:
-                    return resp
-
-            except Exception as err:
-                last_error = err
-                self._logger.error(
-                    f"Failed to send a request to Slack API server: {err}"
-                )
-
-                # Try to find a retry handler for this error
-                retry_request = RetryHttpRequest.from_urllib_http_request(req)
-                for handler in self.retry_handlers:
-                    if handler.can_retry(
-                        state=retry_state,
-                        request=retry_request,
-                        response=None,
-                        error=err,
-                    ):
-                        if self._logger.level <= logging.DEBUG:
-                            self._logger.info(
-                                f"A retry handler found: {type(handler).__name__} for {req.method} {req.full_url} - {err}"
-                            )
-                        handler.prepare_for_next_attempt(
-                            state=retry_state,
-                            request=retry_request,
-                            response=None,
-                            error=err,
-                        )
-                        self._logger.info(
-                            f"Going to retry the same request: {req.method} {req.full_url}"
-                        )
-                        break
-
-                if retry_state.next_attempt_requested is False:
-                    raise err
-
-        if resp is not None:
-            return resp
-        raise last_error
-
-    def _perform_urllib_http_request_internal(
-        self,
-        url: str,
-        req: Request,
-    ) -> Dict[str, any]:
-        # urllib not only opens http:// or https:// URLs, but also ftp:// and file://.
-        # With this it might be possible to open local files on the executing machine
-        # which might be a security risk if the URL to open can be manipulated by an external user.
-        # (BAN-B310)
-        if url.lower().startswith("http"):
-            opener: Optional[OpenerDirector] = None
-            if self.proxy is not None:
-                if isinstance(self.proxy, str):
-                    opener = urllib.request.build_opener(
-                        ProxyHandler({"http": self.proxy, "https": self.proxy}),
-                        HTTPSHandler(context=self.ssl),
-                    )
-                else:
-                    raise SlackRequestError(
-                        f"Invalid proxy detected: {self.proxy} must be a str value"
-                    )
-
-            # NOTE: BAN-B310 is already checked above
-            resp: Optional[HTTPResponse] = None
-            if opener:
-                resp = opener.open(req, timeout=self.timeout)  # skipcq: BAN-B310
-            else:
-                resp = urlopen(  # skipcq: BAN-B310
-                    req, context=self.ssl, timeout=self.timeout
-                )
-            if resp.headers.get_content_type() == "application/gzip":
-                # admin.analytics.getFile
-                body: bytes = resp.read()
-                if self._logger.level <= logging.DEBUG:
-                    self._logger.debug(
-                        "Received the following response - "
-                        f"status: {resp.code}, "
-                        f"headers: {dict(resp.headers)}, "
-                        f"body: (binary)"
-                    )
-                return {"status": resp.code, "headers": resp.headers, "body": body}
-
-            charset = resp.headers.get_content_charset() or "utf-8"
-            body: str = resp.read().decode(charset)  # read the response body here
-            if self._logger.level <= logging.DEBUG:
-                self._logger.debug(
-                    "Received the following response - "
-                    f"status: {resp.code}, "
-                    f"headers: {dict(resp.headers)}, "
-                    f"body: {body}"
-                )
-            return {"status": resp.code, "headers": resp.headers, "body": body}
-        raise SlackRequestError(f"Invalid URL detected: {url}")
-
-    def _build_urllib_request_headers(
-        self, token: str, has_json: bool, has_files: bool, additional_headers: dict
-    ) -> Dict[str, str]:
-        headers = {"Content-Type": "application/x-www-form-urlencoded"}
-        headers.update(self.headers)
-        if token:
-            headers.update({"Authorization": "Bearer {}".format(token)})
-        if additional_headers:
-            headers.update(additional_headers)
-        if has_json:
-            headers.update({"Content-Type": "application/json;charset=utf-8"})
-        if has_files:
-            # will be set afterwards
-            headers.pop("Content-Type", None)
-        return headers
-
-    # =================================================================
-
-    @staticmethod
-    def validate_slack_signature(
-        *, signing_secret: str, data: str, timestamp: str, signature: str
-    ) -> bool:
-        """
-        Slack creates a unique string for your app and shares it with you. Verify
-        requests from Slack with confidence by verifying signatures using your
-        signing secret.
-
-        On each HTTP request that Slack sends, we add an X-Slack-Signature HTTP
-        header. The signature is created by combining the signing secret with the
-        body of the request we're sending using a standard HMAC-SHA256 keyed hash.
-
-        https://api.slack.com/docs/verifying-requests-from-slack#how_to_make_a_request_signature_in_4_easy_steps__an_overview
-
-        Args:
-            signing_secret: Your application's signing secret, available in the
-                Slack API dashboard
-            data: The raw body of the incoming request - no headers, just the body.
-            timestamp: from the 'X-Slack-Request-Timestamp' header
-            signature: from the 'X-Slack-Signature' header - the calculated signature
-                should match this.
-
-        Returns:
-            True if signatures matches
-        """
-        warnings.warn(
-            "As this method is deprecated since slackclient 2.6.0, "
-            "use `from slack.signature import SignatureVerifier` instead",
-            DeprecationWarning,
-        )
-        format_req = str.encode(f"v0:{timestamp}:{data}")
-        encoded_secret = str.encode(signing_secret)
-        request_hash = hmac.new(encoded_secret, format_req, hashlib.sha256).hexdigest()
-        calculated_signature = f"v0={request_hash}"
-        return hmac.compare_digest(calculated_signature, signature)
-
-

Subclasses

- -

Class variables

-
-
var BASE_URL
-
-
-
-
-

Static methods

-
-
-def validate_slack_signature(*, signing_secret:ย str, data:ย str, timestamp:ย str, signature:ย str) โ€‘>ย bool -
-
-

Slack creates a unique string for your app and shares it with you. Verify -requests from Slack with confidence by verifying signatures using your -signing secret.

-

On each HTTP request that Slack sends, we add an X-Slack-Signature HTTP -header. The signature is created by combining the signing secret with the -body of the request we're sending using a standard HMAC-SHA256 keyed hash.

-

https://api.slack.com/docs/verifying-requests-from-slack#how_to_make_a_request_signature_in_4_easy_steps__an_overview

-

Args

-
-
signing_secret
-
Your application's signing secret, available in the -Slack API dashboard
-
data
-
The raw body of the incoming request - no headers, just the body.
-
timestamp
-
from the 'X-Slack-Request-Timestamp' header
-
signature
-
from the 'X-Slack-Signature' header - the calculated signature -should match this.
-
-

Returns

-

True if signatures matches

-
- -Expand source code - -
@staticmethod
-def validate_slack_signature(
-    *, signing_secret: str, data: str, timestamp: str, signature: str
-) -> bool:
-    """
-    Slack creates a unique string for your app and shares it with you. Verify
-    requests from Slack with confidence by verifying signatures using your
-    signing secret.
-
-    On each HTTP request that Slack sends, we add an X-Slack-Signature HTTP
-    header. The signature is created by combining the signing secret with the
-    body of the request we're sending using a standard HMAC-SHA256 keyed hash.
-
-    https://api.slack.com/docs/verifying-requests-from-slack#how_to_make_a_request_signature_in_4_easy_steps__an_overview
-
-    Args:
-        signing_secret: Your application's signing secret, available in the
-            Slack API dashboard
-        data: The raw body of the incoming request - no headers, just the body.
-        timestamp: from the 'X-Slack-Request-Timestamp' header
-        signature: from the 'X-Slack-Signature' header - the calculated signature
-            should match this.
-
-    Returns:
-        True if signatures matches
-    """
-    warnings.warn(
-        "As this method is deprecated since slackclient 2.6.0, "
-        "use `from slack.signature import SignatureVerifier` instead",
-        DeprecationWarning,
-    )
-    format_req = str.encode(f"v0:{timestamp}:{data}")
-    encoded_secret = str.encode(signing_secret)
-    request_hash = hmac.new(encoded_secret, format_req, hashlib.sha256).hexdigest()
-    calculated_signature = f"v0={request_hash}"
-    return hmac.compare_digest(calculated_signature, signature)
-
-
-
-

Methods

-
-
-def api_call(self, api_method:ย str, *, http_verb:ย strย =ย 'POST', files:ย dictย =ย None, data:ย dictย =ย None, params:ย dictย =ย None, json:ย dictย =ย None, headers:ย dictย =ย None, auth:ย dictย =ย None) โ€‘>ย SlackResponse -
-
-

Create a request and execute the API call to Slack.

-

Args

-
-
api_method : str
-
The target Slack API method. -e.g. 'chat.postMessage'
-
http_verb : str
-
HTTP Verb. e.g. 'POST'
-
files : dict
-
Files to multipart upload. -e.g. {image OR file: file_object OR file_path}
-
data
-
The body to attach to the request. If a dictionary is -provided, form-encoding will take place. -e.g. {'key1': 'value1', 'key2': 'value2'}
-
params : dict
-
The URL parameters to append to the URL. -e.g. {'key1': 'value1', 'key2': 'value2'}
-
json : dict
-
JSON for the body to attach to the request -(if files or data is not specified). -e.g. {'key1': 'value1', 'key2': 'value2'}
-
headers : dict
-
Additional request headers
-
auth : dict
-
A dictionary that consists of client_id and client_secret
-
-

Returns

-

(SlackResponse) -The server's response to an HTTP request. Data -from the response can be accessed like a dict. -If the response included 'next_cursor' it can -be iterated on to execute subsequent requests.

-

Raises

-
-
SlackApiError
-
The following Slack API call failed: -'chat.postMessage'.
-
SlackRequestError
-
Json data can only be submitted as -POST requests.
-
-
- -Expand source code - -
def api_call(  # skipcq: PYL-R1710
-    self,
-    api_method: str,
-    *,
-    http_verb: str = "POST",
-    files: dict = None,
-    data: Union[dict] = None,
-    params: dict = None,
-    json: dict = None,  # skipcq: PYL-W0621
-    headers: dict = None,
-    auth: dict = None,
-) -> SlackResponse:
-    """Create a request and execute the API call to Slack.
-
-    Args:
-        api_method (str): The target Slack API method.
-            e.g. 'chat.postMessage'
-        http_verb (str): HTTP Verb. e.g. 'POST'
-        files (dict): Files to multipart upload.
-            e.g. {image OR file: file_object OR file_path}
-        data: The body to attach to the request. If a dictionary is
-            provided, form-encoding will take place.
-            e.g. {'key1': 'value1', 'key2': 'value2'}
-        params (dict): The URL parameters to append to the URL.
-            e.g. {'key1': 'value1', 'key2': 'value2'}
-        json (dict): JSON for the body to attach to the request
-            (if files or data is not specified).
-            e.g. {'key1': 'value1', 'key2': 'value2'}
-        headers (dict): Additional request headers
-        auth (dict): A dictionary that consists of client_id and client_secret
-
-    Returns:
-        (SlackResponse)
-            The server's response to an HTTP request. Data
-            from the response can be accessed like a dict.
-            If the response included 'next_cursor' it can
-            be iterated on to execute subsequent requests.
-
-    Raises:
-        SlackApiError: The following Slack API call failed:
-            'chat.postMessage'.
-        SlackRequestError: Json data can only be submitted as
-            POST requests.
-    """
-
-    api_url = _get_url(self.base_url, api_method)
-    headers = headers or {}
-    headers.update(self.headers)
-    req_args = _build_req_args(
-        token=self.token,
-        http_verb=http_verb,
-        files=files,
-        data=data,
-        default_params=self.default_params,
-        params=params,
-        json=json,  # skipcq: PYL-W0621
-        headers=headers,
-        auth=auth,
-        ssl=self.ssl,
-        proxy=self.proxy,
-    )
-
-    show_2020_01_deprecation(api_method)
-    return self._sync_send(api_url=api_url, req_args=req_args)
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/web/deprecation.html b/docs/api-docs/slack_sdk/web/deprecation.html deleted file mode 100644 index 12b191369..000000000 --- a/docs/api-docs/slack_sdk/web/deprecation.html +++ /dev/null @@ -1,136 +0,0 @@ - - - - - - -slack_sdk.web.deprecation API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.web.deprecation

-
-
-
- -Expand source code - -
import os
-import warnings
-
-# https://api.slack.com/changelog/2020-01-deprecating-antecedents-to-the-conversations-api
-deprecated_method_prefixes_2020_01 = [
-    "channels.",
-    "groups.",
-    "im.",
-    "mpim.",
-    "admin.conversations.whitelist.",
-]
-
-
-def show_2020_01_deprecation(method_name: str):
-    """Prints a warning if the given method is deprecated"""
-
-    skip_deprecation = os.environ.get(
-        "SLACKCLIENT_SKIP_DEPRECATION"
-    )  # for unit tests etc.
-    if skip_deprecation:
-        return
-    if not method_name:
-        return
-
-    matched_prefixes = [
-        prefix
-        for prefix in deprecated_method_prefixes_2020_01
-        if method_name.startswith(prefix)
-    ]
-    if len(matched_prefixes) > 0:
-        message = (
-            f"{method_name} is deprecated. Please use the Conversations API instead. "
-            "For more info, go to "
-            "https://api.slack.com/changelog/2020-01-deprecating-antecedents-to-the-conversations-api"
-        )
-        warnings.warn(message)
-
-
-
-
-
-
-
-

Functions

-
-
-def show_2020_01_deprecation(method_name:ย str) -
-
-

Prints a warning if the given method is deprecated

-
- -Expand source code - -
def show_2020_01_deprecation(method_name: str):
-    """Prints a warning if the given method is deprecated"""
-
-    skip_deprecation = os.environ.get(
-        "SLACKCLIENT_SKIP_DEPRECATION"
-    )  # for unit tests etc.
-    if skip_deprecation:
-        return
-    if not method_name:
-        return
-
-    matched_prefixes = [
-        prefix
-        for prefix in deprecated_method_prefixes_2020_01
-        if method_name.startswith(prefix)
-    ]
-    if len(matched_prefixes) > 0:
-        message = (
-            f"{method_name} is deprecated. Please use the Conversations API instead. "
-            "For more info, go to "
-            "https://api.slack.com/changelog/2020-01-deprecating-antecedents-to-the-conversations-api"
-        )
-        warnings.warn(message)
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/web/index.html b/docs/api-docs/slack_sdk/web/index.html deleted file mode 100644 index fd7c93d03..000000000 --- a/docs/api-docs/slack_sdk/web/index.html +++ /dev/null @@ -1,132 +0,0 @@ - - - - - - -slack_sdk.web API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.web

-
-
-

The Slack Web API allows you to build applications that interact with Slack -in more complex ways than the integrations we provide out of the box.

-
- -Expand source code - -
"""The Slack Web API allows you to build applications that interact with Slack
-in more complex ways than the integrations we provide out of the box."""
-from .client import WebClient  # noqa
-from .slack_response import SlackResponse  # noqa
-
-
-
-

Sub-modules

-
-
slack_sdk.web.async_base_client
-
-
-
-
slack_sdk.web.async_client
-
-

A Python module for interacting with Slack's Web API.

-
-
slack_sdk.web.async_internal_utils
-
-
-
-
slack_sdk.web.async_slack_response
-
-

A Python module for interacting and consuming responses from Slack.

-
-
slack_sdk.web.base_client
-
-

A Python module for interacting with Slack's Web API.

-
-
slack_sdk.web.client
-
-

A Python module for interacting with Slack's Web API.

-
-
slack_sdk.web.deprecation
-
-
-
-
slack_sdk.web.internal_utils
-
-
-
-
slack_sdk.web.legacy_base_client
-
-

A Python module for interacting with Slack's Web API.

-
-
slack_sdk.web.legacy_client
-
-
-
-
slack_sdk.web.legacy_slack_response
-
-

A Python module for interacting and consuming responses from Slack.

-
-
slack_sdk.web.slack_response
-
-

A Python module for interacting and consuming responses from Slack.

-
-
-
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/web/internal_utils.html b/docs/api-docs/slack_sdk/web/internal_utils.html deleted file mode 100644 index 8208c323c..000000000 --- a/docs/api-docs/slack_sdk/web/internal_utils.html +++ /dev/null @@ -1,429 +0,0 @@ - - - - - - -slack_sdk.web.internal_utils API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.web.internal_utils

-
-
-
- -Expand source code - -
import json
-import os
-import platform
-import sys
-import warnings
-from ssl import SSLContext
-from typing import Dict, Union, Optional, Any, Sequence
-from urllib.parse import urljoin
-
-from slack_sdk import version
-from slack_sdk.errors import SlackRequestError
-from slack_sdk.models.attachments import Attachment
-from slack_sdk.models.blocks import Block
-
-
-def convert_bool_to_0_or_1(
-    params: Optional[Dict[str, Any]]
-) -> Optional[Dict[str, Any]]:
-    """Converts all bool values in dict to "0" or "1".
-
-    Slack APIs safely accept "0"/"1" as boolean values.
-    Using True/False (bool in Python) doesn't work with aiohttp.
-    This method converts only the bool values in top-level of a given dict.
-
-    Args:
-        params: params as a dict
-
-    Returns:
-        Modified dict
-    """
-    if params:
-        return {k: _to_0_or_1_if_bool(v) for k, v in params.items()}
-    return None
-
-
-def get_user_agent(prefix: Optional[str] = None, suffix: Optional[str] = None):
-    """Construct the user-agent header with the package info,
-    Python version and OS version.
-
-    Returns:
-        The user agent string.
-        e.g. 'Python/3.6.7 slackclient/2.0.0 Darwin/17.7.0'
-    """
-    # __name__ returns all classes, we only want the client
-    client = "{0}/{1}".format("slackclient", version.__version__)
-    python_version = "Python/{v.major}.{v.minor}.{v.micro}".format(v=sys.version_info)
-    system_info = "{0}/{1}".format(platform.system(), platform.release())
-    user_agent_string = " ".join([python_version, client, system_info])
-    prefix = f"{prefix} " if prefix else ""
-    suffix = f" {suffix}" if suffix else ""
-    return prefix + user_agent_string + suffix
-
-
-def _get_url(base_url: str, api_method: str) -> str:
-    """Joins the base Slack URL and an API method to form an absolute URL.
-
-    Args:
-        base_url (str): The base URL
-        api_method (str): The Slack Web API method. e.g. 'chat.postMessage'
-
-    Returns:
-        The absolute API URL.
-            e.g. 'https://www.slack.com/api/chat.postMessage'
-    """
-    return urljoin(base_url, api_method)
-
-
-def _get_headers(
-    *,
-    headers: dict,
-    token: Optional[str],
-    has_json: bool,
-    has_files: bool,
-    request_specific_headers: Optional[dict],
-) -> Dict[str, str]:
-    """Constructs the headers need for a request.
-    Args:
-        has_json (bool): Whether or not the request has json.
-        has_files (bool): Whether or not the request has files.
-        request_specific_headers (dict): Additional headers specified by the user for a specific request.
-
-    Returns:
-        The headers dictionary.
-            e.g. {
-                'Content-Type': 'application/json;charset=utf-8',
-                'Authorization': 'Bearer xoxb-1234-1243',
-                'User-Agent': 'Python/3.6.8 slack/2.1.0 Darwin/17.7.0'
-            }
-    """
-    final_headers = {
-        "Content-Type": "application/x-www-form-urlencoded",
-    }
-    if headers is None or "User-Agent" not in headers:
-        final_headers["User-Agent"] = get_user_agent()
-
-    if token:
-        final_headers.update({"Authorization": "Bearer {}".format(token)})
-    if headers is None:
-        headers = {}
-
-    # Merge headers specified at client initialization.
-    final_headers.update(headers)
-
-    # Merge headers specified for a specific request. e.g. oauth.access
-    if request_specific_headers:
-        final_headers.update(request_specific_headers)
-
-    if has_json:
-        final_headers.update({"Content-Type": "application/json;charset=utf-8"})
-
-    if has_files:
-        # These are set automatically by the aiohttp library.
-        final_headers.pop("Content-Type", None)
-
-    return final_headers
-
-
-def _set_default_params(target: dict, default_params: dict) -> None:
-    for name, value in default_params.items():
-        if name not in target:
-            target[name] = value
-
-
-def _build_req_args(
-    *,
-    token: Optional[str],
-    http_verb: str,
-    files: dict,
-    data: dict,
-    default_params: dict,
-    params: dict,
-    json: dict,  # skipcq: PYL-W0621
-    headers: dict,
-    auth: dict,
-    ssl: Optional[SSLContext],
-    proxy: Optional[str],
-) -> dict:
-    has_json = json is not None
-    has_files = files is not None
-    if has_json and http_verb != "POST":
-        msg = "Json data can only be submitted as POST requests. GET requests should use the 'params' argument."
-        raise SlackRequestError(msg)
-
-    if data is not None and isinstance(data, dict):
-        data = {k: v for k, v in data.items() if v is not None}
-        _set_default_params(data, default_params)
-    if files is not None and isinstance(files, dict):
-        files = {k: v for k, v in files.items() if v is not None}
-        # NOTE: We do not need to all #_set_default_params here
-        # because other parameters in binary data requests can exist
-        # only in either data or params, not in files.
-    if params is not None and isinstance(params, dict):
-        params = {k: v for k, v in params.items() if v is not None}
-        _set_default_params(params, default_params)
-    if json is not None and isinstance(json, dict):
-        _set_default_params(json, default_params)
-
-    token: Optional[str] = token
-    if params is not None and "token" in params:
-        token = params.pop("token")
-    if json is not None and "token" in json:
-        token = json.pop("token")
-    req_args = {
-        "headers": _get_headers(
-            headers=headers,
-            token=token,
-            has_json=has_json,
-            has_files=has_files,
-            request_specific_headers=headers,
-        ),
-        "data": data,
-        "files": files,
-        "params": params,
-        "json": json,
-        "ssl": ssl,
-        "proxy": proxy,
-        "auth": auth,
-    }
-    return req_args
-
-
-def _parse_web_class_objects(kwargs) -> None:
-    def to_dict(obj: Union[Dict, Block, Attachment]):
-        if isinstance(obj, Block):
-            return obj.to_dict()
-        if isinstance(obj, Attachment):
-            return obj.to_dict()
-        return obj
-
-    blocks = kwargs.get("blocks", None)
-    if blocks is not None and isinstance(blocks, list):
-        dict_blocks = [to_dict(b) for b in blocks]
-        kwargs.update({"blocks": dict_blocks})
-
-    attachments = kwargs.get("attachments", None)
-    if attachments is not None and isinstance(attachments, list):
-        dict_attachments = [to_dict(a) for a in attachments]
-        kwargs.update({"attachments": dict_attachments})
-
-
-def _update_call_participants(
-    kwargs, users: Union[str, Sequence[Dict[str, str]]]
-) -> None:
-    if users is None:
-        return
-
-    if isinstance(users, list):
-        kwargs.update({"users": json.dumps(users)})
-    elif isinstance(users, str):
-        kwargs.update({"users": users})
-    else:
-        raise SlackRequestError("users must be either str or Sequence[Dict[str, str]]")
-
-
-def _next_cursor_is_present(data) -> bool:
-    """Determine if the response contains 'next_cursor'
-    and 'next_cursor' is not empty.
-
-    Returns:
-        A boolean value.
-    """
-    # Only admin.conversations.search returns next_cursor at the top level
-    present = ("next_cursor" in data and data["next_cursor"] != "") or (
-        "response_metadata" in data
-        and "next_cursor" in data["response_metadata"]
-        and data["response_metadata"]["next_cursor"] != ""
-    )
-    return present
-
-
-def _to_0_or_1_if_bool(v: Any) -> Union[Any, str]:
-    if isinstance(v, bool):
-        return "1" if v else "0"
-    return v
-
-
-def _warn_if_text_is_missing(endpoint: str, kwargs: Dict[str, Any]) -> None:
-    missing = "text"
-    attachments = kwargs.get("attachments")
-    # Note that this method does not verify attachments
-    # if the value is already serialized as a single str value.
-    if attachments is not None and isinstance(attachments, list):
-        # https://api.slack.com/reference/messaging/attachments
-        # Check if the fallback field exists for all the attachments
-        if all(
-            [
-                isinstance(attachment, dict)
-                and len(attachment.get("fallback", "").strip()) > 0
-                for attachment in attachments
-            ]
-        ):
-            # The attachments are all good
-            return
-        missing = "fallback"
-    else:
-        text = kwargs.get("text")
-        if text and len(text.strip()) > 0:
-            # Note that this is applicable only for blocks.
-            return
-
-    message = (
-        f"The `{missing}` argument is missing in the request payload for a {endpoint} call - "
-        f"It's a best practice to always provide a `{missing}` argument when posting a message. "
-        f"The `{missing}` argument is used in places where content cannot be rendered such as: "
-        "system push notifications, assistive technology such as screen readers, etc."
-    )
-    # for unit tests etc.
-    skip_deprecation = os.environ.get("SKIP_SLACK_SDK_WARNING")
-    if skip_deprecation:
-        return
-    warnings.warn(message, UserWarning)
-
-
-def _build_unexpected_body_error_message(body: str) -> str:
-    body_for_logging = "".join(
-        [line.strip() for line in body.replace("\r", "\n").split("\n")]
-    )
-    if len(body_for_logging) > 100:
-        body_for_logging = body_for_logging[:100] + "..."
-    message = f"Received a response in a non-JSON format: {body_for_logging}"
-    return message
-
-
-def _remove_none_values(d: dict) -> dict:
-    # To avoid having null values in JSON (Slack API does not work with null in many situations)
-    #
-    # >>> import json
-    # >>> d = {"a": None, "b":123}
-    # >>> json.dumps(d)
-    # '{"a": null, "b": 123}'
-    #
-    return {k: v for k, v in d.items() if v is not None}
-
-
-
-
-
-
-
-

Functions

-
-
-def convert_bool_to_0_or_1(params:ย Optional[Dict[str,ย Any]]) โ€‘>ย Optional[Dict[str,ย Any]] -
-
-

Converts all bool values in dict to "0" or "1".

-

Slack APIs safely accept "0"/"1" as boolean values. -Using True/False (bool in Python) doesn't work with aiohttp. -This method converts only the bool values in top-level of a given dict.

-

Args

-
-
params
-
params as a dict
-
-

Returns

-

Modified dict

-
- -Expand source code - -
def convert_bool_to_0_or_1(
-    params: Optional[Dict[str, Any]]
-) -> Optional[Dict[str, Any]]:
-    """Converts all bool values in dict to "0" or "1".
-
-    Slack APIs safely accept "0"/"1" as boolean values.
-    Using True/False (bool in Python) doesn't work with aiohttp.
-    This method converts only the bool values in top-level of a given dict.
-
-    Args:
-        params: params as a dict
-
-    Returns:
-        Modified dict
-    """
-    if params:
-        return {k: _to_0_or_1_if_bool(v) for k, v in params.items()}
-    return None
-
-
-
-def get_user_agent(prefix:ย Optional[str]ย =ย None, suffix:ย Optional[str]ย =ย None) -
-
-

Construct the user-agent header with the package info, -Python version and OS version.

-

Returns

-

The user agent string. -e.g. 'Python/3.6.7 slackclient/2.0.0 Darwin/17.7.0'

-
- -Expand source code - -
def get_user_agent(prefix: Optional[str] = None, suffix: Optional[str] = None):
-    """Construct the user-agent header with the package info,
-    Python version and OS version.
-
-    Returns:
-        The user agent string.
-        e.g. 'Python/3.6.7 slackclient/2.0.0 Darwin/17.7.0'
-    """
-    # __name__ returns all classes, we only want the client
-    client = "{0}/{1}".format("slackclient", version.__version__)
-    python_version = "Python/{v.major}.{v.minor}.{v.micro}".format(v=sys.version_info)
-    system_info = "{0}/{1}".format(platform.system(), platform.release())
-    user_agent_string = " ".join([python_version, client, system_info])
-    prefix = f"{prefix} " if prefix else ""
-    suffix = f" {suffix}" if suffix else ""
-    return prefix + user_agent_string + suffix
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/web/legacy_base_client.html b/docs/api-docs/slack_sdk/web/legacy_base_client.html deleted file mode 100644 index 23898f9df..000000000 --- a/docs/api-docs/slack_sdk/web/legacy_base_client.html +++ /dev/null @@ -1,1399 +0,0 @@ - - - - - - -slack_sdk.web.legacy_base_client API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.web.legacy_base_client

-
-
-

A Python module for interacting with Slack's Web API.

-
- -Expand source code - -
"""A Python module for interacting with Slack's Web API."""
-
-import asyncio
-import copy
-import hashlib
-import hmac
-import io
-import json
-import logging
-import mimetypes
-import urllib
-import uuid
-import warnings
-from http.client import HTTPResponse
-from ssl import SSLContext
-from typing import BinaryIO, Dict, List
-from typing import Optional, Union
-from urllib.error import HTTPError
-from urllib.parse import urlencode
-from urllib.request import Request, urlopen, OpenerDirector, ProxyHandler, HTTPSHandler
-
-import aiohttp
-from aiohttp import FormData, BasicAuth
-
-import slack_sdk.errors as err
-from slack_sdk.errors import SlackRequestError
-from .async_internal_utils import _files_to_data, _get_event_loop, _request_with_session
-from .deprecation import show_2020_01_deprecation
-from .internal_utils import (
-    convert_bool_to_0_or_1,
-    get_user_agent,
-    _get_url,
-    _build_req_args,
-    _build_unexpected_body_error_message,
-)
-from .legacy_slack_response import LegacySlackResponse as SlackResponse
-from ..proxy_env_variable_loader import load_http_proxy_from_env
-
-
-class LegacyBaseClient:
-    BASE_URL = "https://www.slack.com/api/"
-
-    def __init__(
-        self,
-        token: Optional[str] = None,
-        base_url: str = BASE_URL,
-        timeout: int = 30,
-        loop: Optional[asyncio.AbstractEventLoop] = None,
-        ssl: Optional[SSLContext] = None,
-        proxy: Optional[str] = None,
-        run_async: bool = False,
-        use_sync_aiohttp: bool = False,
-        session: Optional[aiohttp.ClientSession] = None,
-        headers: Optional[dict] = None,
-        user_agent_prefix: Optional[str] = None,
-        user_agent_suffix: Optional[str] = None,
-        # for Org-Wide App installation
-        team_id: Optional[str] = None,
-        logger: Optional[logging.Logger] = None,
-    ):
-        self.token = None if token is None else token.strip()
-        self.base_url = base_url
-        self.timeout = timeout
-        self.ssl = ssl
-        self.proxy = proxy
-        self.run_async = run_async
-        self.use_sync_aiohttp = use_sync_aiohttp
-        self.session = session
-        self.headers = headers or {}
-        self.headers["User-Agent"] = get_user_agent(
-            user_agent_prefix, user_agent_suffix
-        )
-        self.default_params = {}
-        if team_id is not None:
-            self.default_params["team_id"] = team_id
-        self._logger = logger if logger is not None else logging.getLogger(__name__)
-        if self.proxy is None or len(self.proxy.strip()) == 0:
-            env_variable = load_http_proxy_from_env(self._logger)
-            if env_variable is not None:
-                self.proxy = env_variable
-
-        self._event_loop = loop
-
-    def api_call(  # skipcq: PYL-R1710
-        self,
-        api_method: str,
-        *,
-        http_verb: str = "POST",
-        files: dict = None,
-        data: Union[dict, FormData] = None,
-        params: dict = None,
-        json: dict = None,  # skipcq: PYL-W0621
-        headers: dict = None,
-        auth: dict = None,
-    ) -> Union[asyncio.Future, SlackResponse]:
-        """Create a request and execute the API call to Slack.
-        Args:
-            api_method (str): The target Slack API method.
-                e.g. 'chat.postMessage'
-            http_verb (str): HTTP Verb. e.g. 'POST'
-            files (dict): Files to multipart upload.
-                e.g. {image OR file: file_object OR file_path}
-            data: The body to attach to the request. If a dictionary is
-                provided, form-encoding will take place.
-                e.g. {'key1': 'value1', 'key2': 'value2'}
-            params (dict): The URL parameters to append to the URL.
-                e.g. {'key1': 'value1', 'key2': 'value2'}
-            json (dict): JSON for the body to attach to the request
-                (if files or data is not specified).
-                e.g. {'key1': 'value1', 'key2': 'value2'}
-            headers (dict): Additional request headers
-            auth (dict): A dictionary that consists of client_id and client_secret
-        Returns:
-            (SlackResponse)
-                The server's response to an HTTP request. Data
-                from the response can be accessed like a dict.
-                If the response included 'next_cursor' it can
-                be iterated on to execute subsequent requests.
-        Raises:
-            SlackApiError: The following Slack API call failed:
-                'chat.postMessage'.
-            SlackRequestError: Json data can only be submitted as
-                POST requests.
-        """
-
-        api_url = _get_url(self.base_url, api_method)
-        if isinstance(auth, dict):
-            auth = BasicAuth(auth["client_id"], auth["client_secret"])
-        elif isinstance(auth, BasicAuth):
-            headers["Authorization"] = auth.encode()
-
-        headers = headers or {}
-        headers.update(self.headers)
-        req_args = _build_req_args(
-            token=self.token,
-            http_verb=http_verb,
-            files=files,
-            data=data,
-            default_params=self.default_params,
-            params=params,
-            json=json,  # skipcq: PYL-W0621
-            headers=headers,
-            auth=auth,
-            ssl=self.ssl,
-            proxy=self.proxy,
-        )
-
-        show_2020_01_deprecation(api_method)
-
-        if self.run_async or self.use_sync_aiohttp:
-            if self._event_loop is None:
-                self._event_loop = _get_event_loop()
-
-            future = asyncio.ensure_future(
-                self._send(http_verb=http_verb, api_url=api_url, req_args=req_args),
-                loop=self._event_loop,
-            )
-            if self.run_async:
-                return future
-            if self.use_sync_aiohttp:
-                # Using this is no longer recommended - just keep this for backward-compatibility
-                return self._event_loop.run_until_complete(future)
-        else:
-            return self._sync_send(api_url=api_url, req_args=req_args)
-
-    # =================================================================
-    # aiohttp based async WebClient
-    # =================================================================
-
-    async def _send(
-        self, http_verb: str, api_url: str, req_args: dict
-    ) -> SlackResponse:
-        """Sends the request out for transmission.
-        Args:
-            http_verb (str): The HTTP verb. e.g. 'GET' or 'POST'.
-            api_url (str): The Slack API url. e.g. 'https://slack.com/api/chat.postMessage'
-            req_args (dict): The request arguments to be attached to the request.
-            e.g.
-            {
-                json: {
-                    'attachments': [{"pretext": "pre-hello", "text": "text-world"}],
-                    'channel': '#random'
-                }
-            }
-        Returns:
-            The response parsed into a SlackResponse object.
-        """
-        open_files = _files_to_data(req_args)
-        try:
-            if "params" in req_args:
-                # True/False -> "1"/"0"
-                req_args["params"] = convert_bool_to_0_or_1(req_args["params"])
-
-            res = await self._request(
-                http_verb=http_verb, api_url=api_url, req_args=req_args
-            )
-        finally:
-            for f in open_files:
-                f.close()
-
-        data = {
-            "client": self,
-            "http_verb": http_verb,
-            "api_url": api_url,
-            "req_args": req_args,
-            "use_sync_aiohttp": self.use_sync_aiohttp,
-        }
-        return SlackResponse(**{**data, **res}).validate()
-
-    async def _request(self, *, http_verb, api_url, req_args) -> Dict[str, any]:
-        """Submit the HTTP request with the running session or a new session.
-        Returns:
-            A dictionary of the response data.
-        """
-        return await _request_with_session(
-            current_session=self.session,
-            timeout=self.timeout,
-            logger=self._logger,
-            http_verb=http_verb,
-            api_url=api_url,
-            req_args=req_args,
-        )
-
-    # =================================================================
-    # urllib based WebClient
-    # =================================================================
-
-    def _sync_send(self, api_url, req_args) -> SlackResponse:
-        params = req_args["params"] if "params" in req_args else None
-        data = req_args["data"] if "data" in req_args else None
-        files = req_args["files"] if "files" in req_args else None
-        _json = req_args["json"] if "json" in req_args else None
-        headers = req_args["headers"] if "headers" in req_args else None
-        token = params.get("token") if params and "token" in params else None
-        auth = (
-            req_args["auth"] if "auth" in req_args else None
-        )  # Basic Auth for oauth.v2.access / oauth.access
-        if auth is not None:
-            if isinstance(auth, BasicAuth):
-                headers["Authorization"] = auth.encode()
-            elif isinstance(auth, str):
-                headers["Authorization"] = auth
-            else:
-                self._logger.warning(
-                    f"As the auth: {auth}: {type(auth)} is unsupported, skipped"
-                )
-
-        body_params = {}
-        if params:
-            body_params.update(params)
-        if data:
-            body_params.update(data)
-
-        return self._urllib_api_call(
-            token=token,
-            url=api_url,
-            query_params={},
-            body_params=body_params,
-            files=files,
-            json_body=_json,
-            additional_headers=headers,
-        )
-
-    def _request_for_pagination(self, api_url, req_args) -> Dict[str, any]:
-        """This method is supposed to be used only for SlackResponse pagination
-        You can paginate using Python's for iterator as below:
-          for response in client.conversations_list(limit=100):
-              # do something with each response here
-        """
-        response = self._perform_urllib_http_request(url=api_url, args=req_args)
-        return {
-            "status_code": int(response["status"]),
-            "headers": dict(response["headers"]),
-            "data": json.loads(response["body"]),
-        }
-
-    def _urllib_api_call(
-        self,
-        *,
-        token: str = None,
-        url: str,
-        query_params: Dict[str, str] = {},
-        json_body: Dict = {},
-        body_params: Dict[str, str] = {},
-        files: Dict[str, io.BytesIO] = {},
-        additional_headers: Dict[str, str] = {},
-    ) -> SlackResponse:
-        """Performs a Slack API request and returns the result.
-
-        Args:
-            token: Slack API Token (either bot token or user token)
-            url: Complete URL (e.g., https://www.slack.com/api/chat.postMessage)
-            query_params: Query string
-            json_body: JSON data structure (it's still a dict at this point),
-                if you give this argument, body_params and files will be skipped
-            body_params: Form body params
-            files: Files to upload
-            additional_headers: Request headers to append
-        Returns:
-            API response
-        """
-        files_to_close: List[BinaryIO] = []
-        try:
-            # True/False -> "1"/"0"
-            query_params = convert_bool_to_0_or_1(query_params)
-            body_params = convert_bool_to_0_or_1(body_params)
-
-            if self._logger.level <= logging.DEBUG:
-
-                def convert_params(values: dict) -> dict:
-                    if not values or not isinstance(values, dict):
-                        return {}
-                    return {
-                        k: ("(bytes)" if isinstance(v, bytes) else v)
-                        for k, v in values.items()
-                    }
-
-                headers = {
-                    k: "(redacted)" if k.lower() == "authorization" else v
-                    for k, v in additional_headers.items()
-                }
-                self._logger.debug(
-                    f"Sending a request - url: {url}, "
-                    f"query_params: {convert_params(query_params)}, "
-                    f"body_params: {convert_params(body_params)}, "
-                    f"files: {convert_params(files)}, "
-                    f"json_body: {json_body}, "
-                    f"headers: {headers}"
-                )
-
-            request_data = {}
-            if files is not None and isinstance(files, dict) and len(files) > 0:
-                if body_params:
-                    for k, v in body_params.items():
-                        request_data.update({k: v})
-
-                for k, v in files.items():
-                    if isinstance(v, str):
-                        f: BinaryIO = open(v.encode("utf-8", "ignore"), "rb")
-                        files_to_close.append(f)
-                        request_data.update({k: f})
-                    elif isinstance(v, (bytearray, bytes)):
-                        request_data.update({k: io.BytesIO(v)})
-                    else:
-                        request_data.update({k: v})
-
-            request_headers = self._build_urllib_request_headers(
-                token=token or self.token,
-                has_json=json is not None,
-                has_files=files is not None,
-                additional_headers=additional_headers,
-            )
-            request_args = {
-                "headers": request_headers,
-                "data": request_data,
-                "params": body_params,
-                "files": files,
-                "json": json_body,
-            }
-            if query_params:
-                q = urlencode(query_params)
-                url = f"{url}&{q}" if "?" in url else f"{url}?{q}"
-
-            response = self._perform_urllib_http_request(url=url, args=request_args)
-            body = response.get("body", None)  # skipcq: PTC-W0039
-            response_body_data: Optional[Union[dict, bytes]] = body
-            if body is not None and not isinstance(body, bytes):
-                try:
-                    response_body_data = json.loads(response["body"])
-                except json.decoder.JSONDecodeError:
-                    message = _build_unexpected_body_error_message(
-                        response.get("body", "")
-                    )
-                    raise err.SlackApiError(message, response)
-
-            if query_params:
-                all_params = copy.copy(body_params)
-                all_params.update(query_params)
-            else:
-                all_params = body_params
-            request_args["params"] = all_params  # for backward-compatibility
-
-            return SlackResponse(
-                client=self,
-                http_verb="POST",  # you can use POST method for all the Web APIs
-                api_url=url,
-                req_args=request_args,
-                data=response_body_data,
-                headers=dict(response["headers"]),
-                status_code=response["status"],
-                use_sync_aiohttp=False,
-            ).validate()
-        finally:
-            for f in files_to_close:
-                if not f.closed:
-                    f.close()
-
-    def _perform_urllib_http_request(
-        self, *, url: str, args: Dict[str, Dict[str, any]]
-    ) -> Dict[str, any]:
-        """Performs an HTTP request and parses the response.
-
-        Args:
-            url: Complete URL (e.g., https://www.slack.com/api/chat.postMessage)
-            args: args has "headers", "data", "params", and "json"
-                "headers": Dict[str, str]
-                "data": Dict[str, any]
-                "params": Dict[str, str],
-                "json": Dict[str, any],
-
-        Returns:
-            dict {status: int, headers: Headers, body: str}
-        """
-        headers = args["headers"]
-        if args["json"]:
-            body = json.dumps(args["json"])
-            headers["Content-Type"] = "application/json;charset=utf-8"
-        elif args["data"]:
-            boundary = f"--------------{uuid.uuid4()}"
-            sep_boundary = b"\r\n--" + boundary.encode("ascii")
-            end_boundary = sep_boundary + b"--\r\n"
-            body = io.BytesIO()
-            data = args["data"]
-            for key, value in data.items():
-                readable = getattr(value, "readable", None)
-                if readable and value.readable():
-                    filename = "Uploaded file"
-                    name_attr = getattr(value, "name", None)
-                    if name_attr:
-                        filename = (
-                            name_attr.decode("utf-8")
-                            if isinstance(name_attr, bytes)
-                            else name_attr
-                        )
-                    if "filename" in data:
-                        filename = data["filename"]
-                    mimetype = (
-                        mimetypes.guess_type(filename)[0] or "application/octet-stream"
-                    )
-                    title = (
-                        f'\r\nContent-Disposition: form-data; name="{key}"; filename="{filename}"\r\n'
-                        + f"Content-Type: {mimetype}\r\n"
-                    )
-                    value = value.read()
-                else:
-                    title = f'\r\nContent-Disposition: form-data; name="{key}"\r\n'
-                    value = str(value).encode("utf-8")
-                body.write(sep_boundary)
-                body.write(title.encode("utf-8"))
-                body.write(b"\r\n")
-                body.write(value)
-
-            body.write(end_boundary)
-            body = body.getvalue()
-            headers["Content-Type"] = f"multipart/form-data; boundary={boundary}"
-            headers["Content-Length"] = len(body)
-        elif args["params"]:
-            body = urlencode(args["params"])
-            headers["Content-Type"] = "application/x-www-form-urlencoded"
-        else:
-            body = None
-
-        if isinstance(body, str):
-            body = body.encode("utf-8")
-
-        # NOTE: Intentionally ignore the `http_verb` here
-        # Slack APIs accepts any API method requests with POST methods
-        try:
-            # urllib not only opens http:// or https:// URLs, but also ftp:// and file://.
-            # With this it might be possible to open local files on the executing machine
-            # which might be a security risk if the URL to open can be manipulated by an external user.
-            # (BAN-B310)
-            if url.lower().startswith("http"):
-                req = Request(method="POST", url=url, data=body, headers=headers)
-                opener: Optional[OpenerDirector] = None
-                if self.proxy is not None:
-                    if isinstance(self.proxy, str):
-                        opener = urllib.request.build_opener(
-                            ProxyHandler({"http": self.proxy, "https": self.proxy}),
-                            HTTPSHandler(context=self.ssl),
-                        )
-                    else:
-                        raise SlackRequestError(
-                            f"Invalid proxy detected: {self.proxy} must be a str value"
-                        )
-
-                # NOTE: BAN-B310 is already checked above
-                resp: Optional[HTTPResponse] = None
-                if opener:
-                    resp = opener.open(req, timeout=self.timeout)  # skipcq: BAN-B310
-                else:
-                    resp = urlopen(  # skipcq: BAN-B310
-                        req, context=self.ssl, timeout=self.timeout
-                    )
-                if resp.headers.get_content_type() == "application/gzip":
-                    # admin.analytics.getFile
-                    body: bytes = resp.read()
-                    return {"status": resp.code, "headers": resp.headers, "body": body}
-
-                charset = resp.headers.get_content_charset() or "utf-8"
-                body: str = resp.read().decode(charset)  # read the response body here
-                return {"status": resp.code, "headers": resp.headers, "body": body}
-            raise SlackRequestError(f"Invalid URL detected: {url}")
-        except HTTPError as e:
-            resp = {"status": e.code, "headers": e.headers}
-            if e.code == 429:
-                # for compatibility with aiohttp
-                resp["headers"]["Retry-After"] = resp["headers"]["retry-after"]
-
-            # read the response body here
-            charset = e.headers.get_content_charset() or "utf-8"
-            body: str = e.read().decode(charset)
-            resp["body"] = body
-            return resp
-
-        except Exception as err:
-            self._logger.error(f"Failed to send a request to Slack API server: {err}")
-            raise err
-
-    def _build_urllib_request_headers(
-        self, token: str, has_json: bool, has_files: bool, additional_headers: dict
-    ) -> Dict[str, str]:
-        headers = {"Content-Type": "application/x-www-form-urlencoded"}
-        headers.update(self.headers)
-        if token:
-            headers.update({"Authorization": "Bearer {}".format(token)})
-        if additional_headers:
-            headers.update(additional_headers)
-        if has_json:
-            headers.update({"Content-Type": "application/json;charset=utf-8"})
-        if has_files:
-            # will be set afterwards
-            headers.pop("Content-Type", None)
-        return headers
-
-    # =================================================================
-
-    @staticmethod
-    def validate_slack_signature(
-        *, signing_secret: str, data: str, timestamp: str, signature: str
-    ) -> bool:
-        """
-        Slack creates a unique string for your app and shares it with you. Verify
-        requests from Slack with confidence by verifying signatures using your
-        signing secret.
-        On each HTTP request that Slack sends, we add an X-Slack-Signature HTTP
-        header. The signature is created by combining the signing secret with the
-        body of the request we're sending using a standard HMAC-SHA256 keyed hash.
-        https://api.slack.com/docs/verifying-requests-from-slack#how_to_make_a_request_signature_in_4_easy_steps__an_overview
-        Args:
-            signing_secret: Your application's signing secret, available in the
-                Slack API dashboard
-            data: The raw body of the incoming request - no headers, just the body.
-            timestamp: from the 'X-Slack-Request-Timestamp' header
-            signature: from the 'X-Slack-Signature' header - the calculated signature
-                should match this.
-        Returns:
-            True if signatures matches
-        """
-        warnings.warn(
-            "As this method is deprecated since slackclient 2.6.0, "
-            "use `from slack.signature import SignatureVerifier` instead",
-            DeprecationWarning,
-        )
-        format_req = str.encode(f"v0:{timestamp}:{data}")
-        encoded_secret = str.encode(signing_secret)
-        request_hash = hmac.new(encoded_secret, format_req, hashlib.sha256).hexdigest()
-        calculated_signature = f"v0={request_hash}"
-        return hmac.compare_digest(calculated_signature, signature)
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class LegacyBaseClient -(token:ย Optional[str]ย =ย None, base_url:ย strย =ย 'https://www.slack.com/api/', timeout:ย intย =ย 30, loop:ย Optional[asyncio.events.AbstractEventLoop]ย =ย None, ssl:ย Optional[ssl.SSLContext]ย =ย None, proxy:ย Optional[str]ย =ย None, run_async:ย boolย =ย False, use_sync_aiohttp:ย boolย =ย False, session:ย Optional[aiohttp.client.ClientSession]ย =ย None, headers:ย Optional[dict]ย =ย None, user_agent_prefix:ย Optional[str]ย =ย None, user_agent_suffix:ย Optional[str]ย =ย None, team_id:ย Optional[str]ย =ย None, logger:ย Optional[logging.Logger]ย =ย None) -
-
-
-
- -Expand source code - -
class LegacyBaseClient:
-    BASE_URL = "https://www.slack.com/api/"
-
-    def __init__(
-        self,
-        token: Optional[str] = None,
-        base_url: str = BASE_URL,
-        timeout: int = 30,
-        loop: Optional[asyncio.AbstractEventLoop] = None,
-        ssl: Optional[SSLContext] = None,
-        proxy: Optional[str] = None,
-        run_async: bool = False,
-        use_sync_aiohttp: bool = False,
-        session: Optional[aiohttp.ClientSession] = None,
-        headers: Optional[dict] = None,
-        user_agent_prefix: Optional[str] = None,
-        user_agent_suffix: Optional[str] = None,
-        # for Org-Wide App installation
-        team_id: Optional[str] = None,
-        logger: Optional[logging.Logger] = None,
-    ):
-        self.token = None if token is None else token.strip()
-        self.base_url = base_url
-        self.timeout = timeout
-        self.ssl = ssl
-        self.proxy = proxy
-        self.run_async = run_async
-        self.use_sync_aiohttp = use_sync_aiohttp
-        self.session = session
-        self.headers = headers or {}
-        self.headers["User-Agent"] = get_user_agent(
-            user_agent_prefix, user_agent_suffix
-        )
-        self.default_params = {}
-        if team_id is not None:
-            self.default_params["team_id"] = team_id
-        self._logger = logger if logger is not None else logging.getLogger(__name__)
-        if self.proxy is None or len(self.proxy.strip()) == 0:
-            env_variable = load_http_proxy_from_env(self._logger)
-            if env_variable is not None:
-                self.proxy = env_variable
-
-        self._event_loop = loop
-
-    def api_call(  # skipcq: PYL-R1710
-        self,
-        api_method: str,
-        *,
-        http_verb: str = "POST",
-        files: dict = None,
-        data: Union[dict, FormData] = None,
-        params: dict = None,
-        json: dict = None,  # skipcq: PYL-W0621
-        headers: dict = None,
-        auth: dict = None,
-    ) -> Union[asyncio.Future, SlackResponse]:
-        """Create a request and execute the API call to Slack.
-        Args:
-            api_method (str): The target Slack API method.
-                e.g. 'chat.postMessage'
-            http_verb (str): HTTP Verb. e.g. 'POST'
-            files (dict): Files to multipart upload.
-                e.g. {image OR file: file_object OR file_path}
-            data: The body to attach to the request. If a dictionary is
-                provided, form-encoding will take place.
-                e.g. {'key1': 'value1', 'key2': 'value2'}
-            params (dict): The URL parameters to append to the URL.
-                e.g. {'key1': 'value1', 'key2': 'value2'}
-            json (dict): JSON for the body to attach to the request
-                (if files or data is not specified).
-                e.g. {'key1': 'value1', 'key2': 'value2'}
-            headers (dict): Additional request headers
-            auth (dict): A dictionary that consists of client_id and client_secret
-        Returns:
-            (SlackResponse)
-                The server's response to an HTTP request. Data
-                from the response can be accessed like a dict.
-                If the response included 'next_cursor' it can
-                be iterated on to execute subsequent requests.
-        Raises:
-            SlackApiError: The following Slack API call failed:
-                'chat.postMessage'.
-            SlackRequestError: Json data can only be submitted as
-                POST requests.
-        """
-
-        api_url = _get_url(self.base_url, api_method)
-        if isinstance(auth, dict):
-            auth = BasicAuth(auth["client_id"], auth["client_secret"])
-        elif isinstance(auth, BasicAuth):
-            headers["Authorization"] = auth.encode()
-
-        headers = headers or {}
-        headers.update(self.headers)
-        req_args = _build_req_args(
-            token=self.token,
-            http_verb=http_verb,
-            files=files,
-            data=data,
-            default_params=self.default_params,
-            params=params,
-            json=json,  # skipcq: PYL-W0621
-            headers=headers,
-            auth=auth,
-            ssl=self.ssl,
-            proxy=self.proxy,
-        )
-
-        show_2020_01_deprecation(api_method)
-
-        if self.run_async or self.use_sync_aiohttp:
-            if self._event_loop is None:
-                self._event_loop = _get_event_loop()
-
-            future = asyncio.ensure_future(
-                self._send(http_verb=http_verb, api_url=api_url, req_args=req_args),
-                loop=self._event_loop,
-            )
-            if self.run_async:
-                return future
-            if self.use_sync_aiohttp:
-                # Using this is no longer recommended - just keep this for backward-compatibility
-                return self._event_loop.run_until_complete(future)
-        else:
-            return self._sync_send(api_url=api_url, req_args=req_args)
-
-    # =================================================================
-    # aiohttp based async WebClient
-    # =================================================================
-
-    async def _send(
-        self, http_verb: str, api_url: str, req_args: dict
-    ) -> SlackResponse:
-        """Sends the request out for transmission.
-        Args:
-            http_verb (str): The HTTP verb. e.g. 'GET' or 'POST'.
-            api_url (str): The Slack API url. e.g. 'https://slack.com/api/chat.postMessage'
-            req_args (dict): The request arguments to be attached to the request.
-            e.g.
-            {
-                json: {
-                    'attachments': [{"pretext": "pre-hello", "text": "text-world"}],
-                    'channel': '#random'
-                }
-            }
-        Returns:
-            The response parsed into a SlackResponse object.
-        """
-        open_files = _files_to_data(req_args)
-        try:
-            if "params" in req_args:
-                # True/False -> "1"/"0"
-                req_args["params"] = convert_bool_to_0_or_1(req_args["params"])
-
-            res = await self._request(
-                http_verb=http_verb, api_url=api_url, req_args=req_args
-            )
-        finally:
-            for f in open_files:
-                f.close()
-
-        data = {
-            "client": self,
-            "http_verb": http_verb,
-            "api_url": api_url,
-            "req_args": req_args,
-            "use_sync_aiohttp": self.use_sync_aiohttp,
-        }
-        return SlackResponse(**{**data, **res}).validate()
-
-    async def _request(self, *, http_verb, api_url, req_args) -> Dict[str, any]:
-        """Submit the HTTP request with the running session or a new session.
-        Returns:
-            A dictionary of the response data.
-        """
-        return await _request_with_session(
-            current_session=self.session,
-            timeout=self.timeout,
-            logger=self._logger,
-            http_verb=http_verb,
-            api_url=api_url,
-            req_args=req_args,
-        )
-
-    # =================================================================
-    # urllib based WebClient
-    # =================================================================
-
-    def _sync_send(self, api_url, req_args) -> SlackResponse:
-        params = req_args["params"] if "params" in req_args else None
-        data = req_args["data"] if "data" in req_args else None
-        files = req_args["files"] if "files" in req_args else None
-        _json = req_args["json"] if "json" in req_args else None
-        headers = req_args["headers"] if "headers" in req_args else None
-        token = params.get("token") if params and "token" in params else None
-        auth = (
-            req_args["auth"] if "auth" in req_args else None
-        )  # Basic Auth for oauth.v2.access / oauth.access
-        if auth is not None:
-            if isinstance(auth, BasicAuth):
-                headers["Authorization"] = auth.encode()
-            elif isinstance(auth, str):
-                headers["Authorization"] = auth
-            else:
-                self._logger.warning(
-                    f"As the auth: {auth}: {type(auth)} is unsupported, skipped"
-                )
-
-        body_params = {}
-        if params:
-            body_params.update(params)
-        if data:
-            body_params.update(data)
-
-        return self._urllib_api_call(
-            token=token,
-            url=api_url,
-            query_params={},
-            body_params=body_params,
-            files=files,
-            json_body=_json,
-            additional_headers=headers,
-        )
-
-    def _request_for_pagination(self, api_url, req_args) -> Dict[str, any]:
-        """This method is supposed to be used only for SlackResponse pagination
-        You can paginate using Python's for iterator as below:
-          for response in client.conversations_list(limit=100):
-              # do something with each response here
-        """
-        response = self._perform_urllib_http_request(url=api_url, args=req_args)
-        return {
-            "status_code": int(response["status"]),
-            "headers": dict(response["headers"]),
-            "data": json.loads(response["body"]),
-        }
-
-    def _urllib_api_call(
-        self,
-        *,
-        token: str = None,
-        url: str,
-        query_params: Dict[str, str] = {},
-        json_body: Dict = {},
-        body_params: Dict[str, str] = {},
-        files: Dict[str, io.BytesIO] = {},
-        additional_headers: Dict[str, str] = {},
-    ) -> SlackResponse:
-        """Performs a Slack API request and returns the result.
-
-        Args:
-            token: Slack API Token (either bot token or user token)
-            url: Complete URL (e.g., https://www.slack.com/api/chat.postMessage)
-            query_params: Query string
-            json_body: JSON data structure (it's still a dict at this point),
-                if you give this argument, body_params and files will be skipped
-            body_params: Form body params
-            files: Files to upload
-            additional_headers: Request headers to append
-        Returns:
-            API response
-        """
-        files_to_close: List[BinaryIO] = []
-        try:
-            # True/False -> "1"/"0"
-            query_params = convert_bool_to_0_or_1(query_params)
-            body_params = convert_bool_to_0_or_1(body_params)
-
-            if self._logger.level <= logging.DEBUG:
-
-                def convert_params(values: dict) -> dict:
-                    if not values or not isinstance(values, dict):
-                        return {}
-                    return {
-                        k: ("(bytes)" if isinstance(v, bytes) else v)
-                        for k, v in values.items()
-                    }
-
-                headers = {
-                    k: "(redacted)" if k.lower() == "authorization" else v
-                    for k, v in additional_headers.items()
-                }
-                self._logger.debug(
-                    f"Sending a request - url: {url}, "
-                    f"query_params: {convert_params(query_params)}, "
-                    f"body_params: {convert_params(body_params)}, "
-                    f"files: {convert_params(files)}, "
-                    f"json_body: {json_body}, "
-                    f"headers: {headers}"
-                )
-
-            request_data = {}
-            if files is not None and isinstance(files, dict) and len(files) > 0:
-                if body_params:
-                    for k, v in body_params.items():
-                        request_data.update({k: v})
-
-                for k, v in files.items():
-                    if isinstance(v, str):
-                        f: BinaryIO = open(v.encode("utf-8", "ignore"), "rb")
-                        files_to_close.append(f)
-                        request_data.update({k: f})
-                    elif isinstance(v, (bytearray, bytes)):
-                        request_data.update({k: io.BytesIO(v)})
-                    else:
-                        request_data.update({k: v})
-
-            request_headers = self._build_urllib_request_headers(
-                token=token or self.token,
-                has_json=json is not None,
-                has_files=files is not None,
-                additional_headers=additional_headers,
-            )
-            request_args = {
-                "headers": request_headers,
-                "data": request_data,
-                "params": body_params,
-                "files": files,
-                "json": json_body,
-            }
-            if query_params:
-                q = urlencode(query_params)
-                url = f"{url}&{q}" if "?" in url else f"{url}?{q}"
-
-            response = self._perform_urllib_http_request(url=url, args=request_args)
-            body = response.get("body", None)  # skipcq: PTC-W0039
-            response_body_data: Optional[Union[dict, bytes]] = body
-            if body is not None and not isinstance(body, bytes):
-                try:
-                    response_body_data = json.loads(response["body"])
-                except json.decoder.JSONDecodeError:
-                    message = _build_unexpected_body_error_message(
-                        response.get("body", "")
-                    )
-                    raise err.SlackApiError(message, response)
-
-            if query_params:
-                all_params = copy.copy(body_params)
-                all_params.update(query_params)
-            else:
-                all_params = body_params
-            request_args["params"] = all_params  # for backward-compatibility
-
-            return SlackResponse(
-                client=self,
-                http_verb="POST",  # you can use POST method for all the Web APIs
-                api_url=url,
-                req_args=request_args,
-                data=response_body_data,
-                headers=dict(response["headers"]),
-                status_code=response["status"],
-                use_sync_aiohttp=False,
-            ).validate()
-        finally:
-            for f in files_to_close:
-                if not f.closed:
-                    f.close()
-
-    def _perform_urllib_http_request(
-        self, *, url: str, args: Dict[str, Dict[str, any]]
-    ) -> Dict[str, any]:
-        """Performs an HTTP request and parses the response.
-
-        Args:
-            url: Complete URL (e.g., https://www.slack.com/api/chat.postMessage)
-            args: args has "headers", "data", "params", and "json"
-                "headers": Dict[str, str]
-                "data": Dict[str, any]
-                "params": Dict[str, str],
-                "json": Dict[str, any],
-
-        Returns:
-            dict {status: int, headers: Headers, body: str}
-        """
-        headers = args["headers"]
-        if args["json"]:
-            body = json.dumps(args["json"])
-            headers["Content-Type"] = "application/json;charset=utf-8"
-        elif args["data"]:
-            boundary = f"--------------{uuid.uuid4()}"
-            sep_boundary = b"\r\n--" + boundary.encode("ascii")
-            end_boundary = sep_boundary + b"--\r\n"
-            body = io.BytesIO()
-            data = args["data"]
-            for key, value in data.items():
-                readable = getattr(value, "readable", None)
-                if readable and value.readable():
-                    filename = "Uploaded file"
-                    name_attr = getattr(value, "name", None)
-                    if name_attr:
-                        filename = (
-                            name_attr.decode("utf-8")
-                            if isinstance(name_attr, bytes)
-                            else name_attr
-                        )
-                    if "filename" in data:
-                        filename = data["filename"]
-                    mimetype = (
-                        mimetypes.guess_type(filename)[0] or "application/octet-stream"
-                    )
-                    title = (
-                        f'\r\nContent-Disposition: form-data; name="{key}"; filename="{filename}"\r\n'
-                        + f"Content-Type: {mimetype}\r\n"
-                    )
-                    value = value.read()
-                else:
-                    title = f'\r\nContent-Disposition: form-data; name="{key}"\r\n'
-                    value = str(value).encode("utf-8")
-                body.write(sep_boundary)
-                body.write(title.encode("utf-8"))
-                body.write(b"\r\n")
-                body.write(value)
-
-            body.write(end_boundary)
-            body = body.getvalue()
-            headers["Content-Type"] = f"multipart/form-data; boundary={boundary}"
-            headers["Content-Length"] = len(body)
-        elif args["params"]:
-            body = urlencode(args["params"])
-            headers["Content-Type"] = "application/x-www-form-urlencoded"
-        else:
-            body = None
-
-        if isinstance(body, str):
-            body = body.encode("utf-8")
-
-        # NOTE: Intentionally ignore the `http_verb` here
-        # Slack APIs accepts any API method requests with POST methods
-        try:
-            # urllib not only opens http:// or https:// URLs, but also ftp:// and file://.
-            # With this it might be possible to open local files on the executing machine
-            # which might be a security risk if the URL to open can be manipulated by an external user.
-            # (BAN-B310)
-            if url.lower().startswith("http"):
-                req = Request(method="POST", url=url, data=body, headers=headers)
-                opener: Optional[OpenerDirector] = None
-                if self.proxy is not None:
-                    if isinstance(self.proxy, str):
-                        opener = urllib.request.build_opener(
-                            ProxyHandler({"http": self.proxy, "https": self.proxy}),
-                            HTTPSHandler(context=self.ssl),
-                        )
-                    else:
-                        raise SlackRequestError(
-                            f"Invalid proxy detected: {self.proxy} must be a str value"
-                        )
-
-                # NOTE: BAN-B310 is already checked above
-                resp: Optional[HTTPResponse] = None
-                if opener:
-                    resp = opener.open(req, timeout=self.timeout)  # skipcq: BAN-B310
-                else:
-                    resp = urlopen(  # skipcq: BAN-B310
-                        req, context=self.ssl, timeout=self.timeout
-                    )
-                if resp.headers.get_content_type() == "application/gzip":
-                    # admin.analytics.getFile
-                    body: bytes = resp.read()
-                    return {"status": resp.code, "headers": resp.headers, "body": body}
-
-                charset = resp.headers.get_content_charset() or "utf-8"
-                body: str = resp.read().decode(charset)  # read the response body here
-                return {"status": resp.code, "headers": resp.headers, "body": body}
-            raise SlackRequestError(f"Invalid URL detected: {url}")
-        except HTTPError as e:
-            resp = {"status": e.code, "headers": e.headers}
-            if e.code == 429:
-                # for compatibility with aiohttp
-                resp["headers"]["Retry-After"] = resp["headers"]["retry-after"]
-
-            # read the response body here
-            charset = e.headers.get_content_charset() or "utf-8"
-            body: str = e.read().decode(charset)
-            resp["body"] = body
-            return resp
-
-        except Exception as err:
-            self._logger.error(f"Failed to send a request to Slack API server: {err}")
-            raise err
-
-    def _build_urllib_request_headers(
-        self, token: str, has_json: bool, has_files: bool, additional_headers: dict
-    ) -> Dict[str, str]:
-        headers = {"Content-Type": "application/x-www-form-urlencoded"}
-        headers.update(self.headers)
-        if token:
-            headers.update({"Authorization": "Bearer {}".format(token)})
-        if additional_headers:
-            headers.update(additional_headers)
-        if has_json:
-            headers.update({"Content-Type": "application/json;charset=utf-8"})
-        if has_files:
-            # will be set afterwards
-            headers.pop("Content-Type", None)
-        return headers
-
-    # =================================================================
-
-    @staticmethod
-    def validate_slack_signature(
-        *, signing_secret: str, data: str, timestamp: str, signature: str
-    ) -> bool:
-        """
-        Slack creates a unique string for your app and shares it with you. Verify
-        requests from Slack with confidence by verifying signatures using your
-        signing secret.
-        On each HTTP request that Slack sends, we add an X-Slack-Signature HTTP
-        header. The signature is created by combining the signing secret with the
-        body of the request we're sending using a standard HMAC-SHA256 keyed hash.
-        https://api.slack.com/docs/verifying-requests-from-slack#how_to_make_a_request_signature_in_4_easy_steps__an_overview
-        Args:
-            signing_secret: Your application's signing secret, available in the
-                Slack API dashboard
-            data: The raw body of the incoming request - no headers, just the body.
-            timestamp: from the 'X-Slack-Request-Timestamp' header
-            signature: from the 'X-Slack-Signature' header - the calculated signature
-                should match this.
-        Returns:
-            True if signatures matches
-        """
-        warnings.warn(
-            "As this method is deprecated since slackclient 2.6.0, "
-            "use `from slack.signature import SignatureVerifier` instead",
-            DeprecationWarning,
-        )
-        format_req = str.encode(f"v0:{timestamp}:{data}")
-        encoded_secret = str.encode(signing_secret)
-        request_hash = hmac.new(encoded_secret, format_req, hashlib.sha256).hexdigest()
-        calculated_signature = f"v0={request_hash}"
-        return hmac.compare_digest(calculated_signature, signature)
-
-

Subclasses

- -

Class variables

-
-
var BASE_URL
-
-
-
-
-

Static methods

-
-
-def validate_slack_signature(*, signing_secret:ย str, data:ย str, timestamp:ย str, signature:ย str) โ€‘>ย bool -
-
-

Slack creates a unique string for your app and shares it with you. Verify -requests from Slack with confidence by verifying signatures using your -signing secret. -On each HTTP request that Slack sends, we add an X-Slack-Signature HTTP -header. The signature is created by combining the signing secret with the -body of the request we're sending using a standard HMAC-SHA256 keyed hash. -https://api.slack.com/docs/verifying-requests-from-slack#how_to_make_a_request_signature_in_4_easy_steps__an_overview

-

Args

-
-
signing_secret
-
Your application's signing secret, available in the -Slack API dashboard
-
data
-
The raw body of the incoming request - no headers, just the body.
-
timestamp
-
from the 'X-Slack-Request-Timestamp' header
-
signature
-
from the 'X-Slack-Signature' header - the calculated signature -should match this.
-
-

Returns

-

True if signatures matches

-
- -Expand source code - -
@staticmethod
-def validate_slack_signature(
-    *, signing_secret: str, data: str, timestamp: str, signature: str
-) -> bool:
-    """
-    Slack creates a unique string for your app and shares it with you. Verify
-    requests from Slack with confidence by verifying signatures using your
-    signing secret.
-    On each HTTP request that Slack sends, we add an X-Slack-Signature HTTP
-    header. The signature is created by combining the signing secret with the
-    body of the request we're sending using a standard HMAC-SHA256 keyed hash.
-    https://api.slack.com/docs/verifying-requests-from-slack#how_to_make_a_request_signature_in_4_easy_steps__an_overview
-    Args:
-        signing_secret: Your application's signing secret, available in the
-            Slack API dashboard
-        data: The raw body of the incoming request - no headers, just the body.
-        timestamp: from the 'X-Slack-Request-Timestamp' header
-        signature: from the 'X-Slack-Signature' header - the calculated signature
-            should match this.
-    Returns:
-        True if signatures matches
-    """
-    warnings.warn(
-        "As this method is deprecated since slackclient 2.6.0, "
-        "use `from slack.signature import SignatureVerifier` instead",
-        DeprecationWarning,
-    )
-    format_req = str.encode(f"v0:{timestamp}:{data}")
-    encoded_secret = str.encode(signing_secret)
-    request_hash = hmac.new(encoded_secret, format_req, hashlib.sha256).hexdigest()
-    calculated_signature = f"v0={request_hash}"
-    return hmac.compare_digest(calculated_signature, signature)
-
-
-
-

Methods

-
-
-def api_call(self, api_method:ย str, *, http_verb:ย strย =ย 'POST', files:ย dictย =ย None, data:ย Union[dict,ย aiohttp.formdata.FormData]ย =ย None, params:ย dictย =ย None, json:ย dictย =ย None, headers:ย dictย =ย None, auth:ย dictย =ย None) โ€‘>ย Union[_asyncio.Future,ย LegacySlackResponse] -
-
-

Create a request and execute the API call to Slack.

-

Args

-
-
api_method : str
-
The target Slack API method. -e.g. 'chat.postMessage'
-
http_verb : str
-
HTTP Verb. e.g. 'POST'
-
files : dict
-
Files to multipart upload. -e.g. {image OR file: file_object OR file_path}
-
data
-
The body to attach to the request. If a dictionary is -provided, form-encoding will take place. -e.g. {'key1': 'value1', 'key2': 'value2'}
-
params : dict
-
The URL parameters to append to the URL. -e.g. {'key1': 'value1', 'key2': 'value2'}
-
json : dict
-
JSON for the body to attach to the request -(if files or data is not specified). -e.g. {'key1': 'value1', 'key2': 'value2'}
-
headers : dict
-
Additional request headers
-
auth : dict
-
A dictionary that consists of client_id and client_secret
-
-

Returns

-

(SlackResponse) -The server's response to an HTTP request. Data -from the response can be accessed like a dict. -If the response included 'next_cursor' it can -be iterated on to execute subsequent requests.

-

Raises

-
-
SlackApiError
-
The following Slack API call failed: -'chat.postMessage'.
-
SlackRequestError
-
Json data can only be submitted as -POST requests.
-
-
- -Expand source code - -
def api_call(  # skipcq: PYL-R1710
-    self,
-    api_method: str,
-    *,
-    http_verb: str = "POST",
-    files: dict = None,
-    data: Union[dict, FormData] = None,
-    params: dict = None,
-    json: dict = None,  # skipcq: PYL-W0621
-    headers: dict = None,
-    auth: dict = None,
-) -> Union[asyncio.Future, SlackResponse]:
-    """Create a request and execute the API call to Slack.
-    Args:
-        api_method (str): The target Slack API method.
-            e.g. 'chat.postMessage'
-        http_verb (str): HTTP Verb. e.g. 'POST'
-        files (dict): Files to multipart upload.
-            e.g. {image OR file: file_object OR file_path}
-        data: The body to attach to the request. If a dictionary is
-            provided, form-encoding will take place.
-            e.g. {'key1': 'value1', 'key2': 'value2'}
-        params (dict): The URL parameters to append to the URL.
-            e.g. {'key1': 'value1', 'key2': 'value2'}
-        json (dict): JSON for the body to attach to the request
-            (if files or data is not specified).
-            e.g. {'key1': 'value1', 'key2': 'value2'}
-        headers (dict): Additional request headers
-        auth (dict): A dictionary that consists of client_id and client_secret
-    Returns:
-        (SlackResponse)
-            The server's response to an HTTP request. Data
-            from the response can be accessed like a dict.
-            If the response included 'next_cursor' it can
-            be iterated on to execute subsequent requests.
-    Raises:
-        SlackApiError: The following Slack API call failed:
-            'chat.postMessage'.
-        SlackRequestError: Json data can only be submitted as
-            POST requests.
-    """
-
-    api_url = _get_url(self.base_url, api_method)
-    if isinstance(auth, dict):
-        auth = BasicAuth(auth["client_id"], auth["client_secret"])
-    elif isinstance(auth, BasicAuth):
-        headers["Authorization"] = auth.encode()
-
-    headers = headers or {}
-    headers.update(self.headers)
-    req_args = _build_req_args(
-        token=self.token,
-        http_verb=http_verb,
-        files=files,
-        data=data,
-        default_params=self.default_params,
-        params=params,
-        json=json,  # skipcq: PYL-W0621
-        headers=headers,
-        auth=auth,
-        ssl=self.ssl,
-        proxy=self.proxy,
-    )
-
-    show_2020_01_deprecation(api_method)
-
-    if self.run_async or self.use_sync_aiohttp:
-        if self._event_loop is None:
-            self._event_loop = _get_event_loop()
-
-        future = asyncio.ensure_future(
-            self._send(http_verb=http_verb, api_url=api_url, req_args=req_args),
-            loop=self._event_loop,
-        )
-        if self.run_async:
-            return future
-        if self.use_sync_aiohttp:
-            # Using this is no longer recommended - just keep this for backward-compatibility
-            return self._event_loop.run_until_complete(future)
-    else:
-        return self._sync_send(api_url=api_url, req_args=req_args)
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/web/legacy_slack_response.html b/docs/api-docs/slack_sdk/web/legacy_slack_response.html deleted file mode 100644 index 352317048..000000000 --- a/docs/api-docs/slack_sdk/web/legacy_slack_response.html +++ /dev/null @@ -1,665 +0,0 @@ - - - - - - -slack_sdk.web.legacy_slack_response API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.web.legacy_slack_response

-
-
-

A Python module for interacting and consuming responses from Slack.

-
- -Expand source code - -
"""A Python module for interacting and consuming responses from Slack."""
-
-import asyncio
-
-# Standard Imports
-import logging
-
-# Internal Imports
-from typing import Union
-
-import slack_sdk.errors as e
-
-
-class LegacySlackResponse(object):  # skipcq: PYL-R0205
-    """An iterable container of response data.
-
-    Attributes:
-        data (dict): The json-encoded content of the response. Along
-            with the headers and status code information.
-
-    Methods:
-        validate: Check if the response from Slack was successful.
-        get: Retrieves any key from the response data.
-        next: Retrieves the next portion of results,
-            if 'next_cursor' is present.
-
-    Example:
-    ```python
-    import os
-    import slack
-
-    client = slack.WebClient(token=os.environ['SLACK_API_TOKEN'])
-
-    response1 = client.auth_revoke(test='true')
-    assert not response1['revoked']
-
-    response2 = client.auth_test()
-    assert response2.get('ok', False)
-
-    users = []
-    for page in client.users_list(limit=2):
-        TODO: This example should specify when to break.
-        users = users + page['members']
-    ```
-
-    Note:
-        Some responses return collections of information
-        like channel and user lists. If they do it's likely
-        that you'll only receive a portion of results. This
-        object allows you to iterate over the response which
-        makes subsequent API requests until your code hits
-        'break' or there are no more results to be found.
-
-        Any attributes or methods prefixed with _underscores are
-        intended to be "private" internal use only. They may be changed or
-        removed at anytime.
-    """
-
-    def __init__(
-        self,
-        *,
-        client,
-        http_verb: str,
-        api_url: str,
-        req_args: dict,
-        data: Union[dict, bytes],  # data can be binary data
-        headers: dict,
-        status_code: int,
-        use_sync_aiohttp: bool = True,  # True for backward-compatibility
-    ):
-        self.http_verb = http_verb
-        self.api_url = api_url
-        self.req_args = req_args
-        self.data = data
-        self.headers = headers
-        self.status_code = status_code
-        self._initial_data = data
-        self._client = client  # LegacyWebClient
-        self._use_sync_aiohttp = use_sync_aiohttp
-        self._logger = logging.getLogger(__name__)
-
-    def __str__(self):
-        """Return the Response data if object is converted to a string."""
-        if isinstance(self.data, bytes):
-            raise ValueError(
-                "As the response.data is binary data, this operation is unsupported"
-            )
-        return f"{self.data}"
-
-    def __getitem__(self, key):
-        """Retrieves any key from the data store.
-
-        Note:
-            This is implemented so users can reference the
-            SlackResponse object like a dictionary.
-            e.g. response["ok"]
-
-        Returns:
-            The value from data or None.
-        """
-        if isinstance(self.data, bytes):
-            raise ValueError(
-                "As the response.data is binary data, this operation is unsupported"
-            )
-        return self.data.get(key, None)
-
-    def __iter__(self):
-        """Enables the ability to iterate over the response.
-        It's required for the iterator protocol.
-
-        Note:
-            This enables Slack cursor-based pagination.
-
-        Returns:
-            (SlackResponse) self
-        """
-        if isinstance(self.data, bytes):
-            raise ValueError(
-                "As the response.data is binary data, this operation is unsupported"
-            )
-        self._iteration = 0  # skipcq: PYL-W0201
-        self.data = self._initial_data
-        return self
-
-    def __next__(self):
-        """Retrieves the next portion of results, if 'next_cursor' is present.
-
-        Note:
-            Some responses return collections of information
-            like channel and user lists. If they do it's likely
-            that you'll only receive a portion of results. This
-            method allows you to iterate over the response until
-            your code hits 'break' or there are no more results
-            to be found.
-
-        Returns:
-            (SlackResponse) self
-                With the new response data now attached to this object.
-
-        Raises:
-            SlackApiError: If the request to the Slack API failed.
-            StopIteration: If 'next_cursor' is not present or empty.
-        """
-        if isinstance(self.data, bytes):
-            raise ValueError(
-                "As the response.data is binary data, this operation is unsupported"
-            )
-        self._iteration += 1
-        if self._iteration == 1:
-            return self
-        if self._next_cursor_is_present(self.data):  # skipcq: PYL-R1705
-            params = self.req_args.get("params", {})
-            if params is None:
-                params = {}
-            params.update({"cursor": self.data["response_metadata"]["next_cursor"]})
-            self.req_args.update({"params": params})
-
-            if self._use_sync_aiohttp:
-                # We no longer recommend going with this way
-                response = asyncio.get_event_loop().run_until_complete(
-                    self._client._request(  # skipcq: PYL-W0212
-                        http_verb=self.http_verb,
-                        api_url=self.api_url,
-                        req_args=self.req_args,
-                    )
-                )
-            else:
-                # This method sends a request in a synchronous way
-                response = self._client._request_for_pagination(  # skipcq: PYL-W0212
-                    api_url=self.api_url, req_args=self.req_args
-                )
-
-            self.data = response["data"]
-            self.headers = response["headers"]
-            self.status_code = response["status_code"]
-            return self.validate()
-        else:
-            raise StopIteration
-
-    def get(self, key, default=None):
-        """Retrieves any key from the response data.
-
-        Note:
-            This is implemented so users can reference the
-            SlackResponse object like a dictionary.
-            e.g. response.get("ok", False)
-
-        Returns:
-            The value from data or the specified default.
-        """
-        if isinstance(self.data, bytes):
-            raise ValueError(
-                "As the response.data is binary data, this operation is unsupported"
-            )
-        return self.data.get(key, default)
-
-    def validate(self):
-        """Check if the response from Slack was successful.
-
-        Returns:
-            (SlackResponse)
-                This method returns it's own object. e.g. 'self'
-
-        Raises:
-            SlackApiError: The request to the Slack API failed.
-        """
-        if self._logger.level <= logging.DEBUG:
-            body = self.data if isinstance(self.data, dict) else "(binary)"
-            self._logger.debug(
-                "Received the following response - "
-                f"status: {self.status_code}, "
-                f"headers: {dict(self.headers)}, "
-                f"body: {body}"
-            )
-        if (
-            self.status_code == 200
-            and self.data
-            and (isinstance(self.data, bytes) or self.data.get("ok", False))
-        ):
-            return self
-        msg = "The request to the Slack API failed."
-        raise e.SlackApiError(message=msg, response=self)
-
-    @staticmethod
-    def _next_cursor_is_present(data):
-        """Determine if the response contains 'next_cursor'
-        and 'next_cursor' is not empty.
-
-        Returns:
-            A boolean value.
-        """
-        present = (
-            "response_metadata" in data
-            and "next_cursor" in data["response_metadata"]
-            and data["response_metadata"]["next_cursor"] != ""
-        )
-        return present
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class LegacySlackResponse -(*, client, http_verb:ย str, api_url:ย str, req_args:ย dict, data:ย Union[dict,ย bytes], headers:ย dict, status_code:ย int, use_sync_aiohttp:ย boolย =ย True) -
-
-

An iterable container of response data.

-

Attributes

-
-
data : dict
-
The json-encoded content of the response. Along -with the headers and status code information.
-
-

Methods

-

validate: Check if the response from Slack was successful. -get: Retrieves any key from the response data. -next: Retrieves the next portion of results, -if 'next_cursor' is present.

-

Example:

-
import os
-import slack
-
-client = slack.WebClient(token=os.environ['SLACK_API_TOKEN'])
-
-response1 = client.auth_revoke(test='true')
-assert not response1['revoked']
-
-response2 = client.auth_test()
-assert response2.get('ok', False)
-
-users = []
-for page in client.users_list(limit=2):
-    TODO: This example should specify when to break.
-    users = users + page['members']
-
-

Note

-

Some responses return collections of information -like channel and user lists. If they do it's likely -that you'll only receive a portion of results. This -object allows you to iterate over the response which -makes subsequent API requests until your code hits -'break' or there are no more results to be found.

-

Any attributes or methods prefixed with _underscores are -intended to be "private" internal use only. They may be changed or -removed at anytime.

-
- -Expand source code - -
class LegacySlackResponse(object):  # skipcq: PYL-R0205
-    """An iterable container of response data.
-
-    Attributes:
-        data (dict): The json-encoded content of the response. Along
-            with the headers and status code information.
-
-    Methods:
-        validate: Check if the response from Slack was successful.
-        get: Retrieves any key from the response data.
-        next: Retrieves the next portion of results,
-            if 'next_cursor' is present.
-
-    Example:
-    ```python
-    import os
-    import slack
-
-    client = slack.WebClient(token=os.environ['SLACK_API_TOKEN'])
-
-    response1 = client.auth_revoke(test='true')
-    assert not response1['revoked']
-
-    response2 = client.auth_test()
-    assert response2.get('ok', False)
-
-    users = []
-    for page in client.users_list(limit=2):
-        TODO: This example should specify when to break.
-        users = users + page['members']
-    ```
-
-    Note:
-        Some responses return collections of information
-        like channel and user lists. If they do it's likely
-        that you'll only receive a portion of results. This
-        object allows you to iterate over the response which
-        makes subsequent API requests until your code hits
-        'break' or there are no more results to be found.
-
-        Any attributes or methods prefixed with _underscores are
-        intended to be "private" internal use only. They may be changed or
-        removed at anytime.
-    """
-
-    def __init__(
-        self,
-        *,
-        client,
-        http_verb: str,
-        api_url: str,
-        req_args: dict,
-        data: Union[dict, bytes],  # data can be binary data
-        headers: dict,
-        status_code: int,
-        use_sync_aiohttp: bool = True,  # True for backward-compatibility
-    ):
-        self.http_verb = http_verb
-        self.api_url = api_url
-        self.req_args = req_args
-        self.data = data
-        self.headers = headers
-        self.status_code = status_code
-        self._initial_data = data
-        self._client = client  # LegacyWebClient
-        self._use_sync_aiohttp = use_sync_aiohttp
-        self._logger = logging.getLogger(__name__)
-
-    def __str__(self):
-        """Return the Response data if object is converted to a string."""
-        if isinstance(self.data, bytes):
-            raise ValueError(
-                "As the response.data is binary data, this operation is unsupported"
-            )
-        return f"{self.data}"
-
-    def __getitem__(self, key):
-        """Retrieves any key from the data store.
-
-        Note:
-            This is implemented so users can reference the
-            SlackResponse object like a dictionary.
-            e.g. response["ok"]
-
-        Returns:
-            The value from data or None.
-        """
-        if isinstance(self.data, bytes):
-            raise ValueError(
-                "As the response.data is binary data, this operation is unsupported"
-            )
-        return self.data.get(key, None)
-
-    def __iter__(self):
-        """Enables the ability to iterate over the response.
-        It's required for the iterator protocol.
-
-        Note:
-            This enables Slack cursor-based pagination.
-
-        Returns:
-            (SlackResponse) self
-        """
-        if isinstance(self.data, bytes):
-            raise ValueError(
-                "As the response.data is binary data, this operation is unsupported"
-            )
-        self._iteration = 0  # skipcq: PYL-W0201
-        self.data = self._initial_data
-        return self
-
-    def __next__(self):
-        """Retrieves the next portion of results, if 'next_cursor' is present.
-
-        Note:
-            Some responses return collections of information
-            like channel and user lists. If they do it's likely
-            that you'll only receive a portion of results. This
-            method allows you to iterate over the response until
-            your code hits 'break' or there are no more results
-            to be found.
-
-        Returns:
-            (SlackResponse) self
-                With the new response data now attached to this object.
-
-        Raises:
-            SlackApiError: If the request to the Slack API failed.
-            StopIteration: If 'next_cursor' is not present or empty.
-        """
-        if isinstance(self.data, bytes):
-            raise ValueError(
-                "As the response.data is binary data, this operation is unsupported"
-            )
-        self._iteration += 1
-        if self._iteration == 1:
-            return self
-        if self._next_cursor_is_present(self.data):  # skipcq: PYL-R1705
-            params = self.req_args.get("params", {})
-            if params is None:
-                params = {}
-            params.update({"cursor": self.data["response_metadata"]["next_cursor"]})
-            self.req_args.update({"params": params})
-
-            if self._use_sync_aiohttp:
-                # We no longer recommend going with this way
-                response = asyncio.get_event_loop().run_until_complete(
-                    self._client._request(  # skipcq: PYL-W0212
-                        http_verb=self.http_verb,
-                        api_url=self.api_url,
-                        req_args=self.req_args,
-                    )
-                )
-            else:
-                # This method sends a request in a synchronous way
-                response = self._client._request_for_pagination(  # skipcq: PYL-W0212
-                    api_url=self.api_url, req_args=self.req_args
-                )
-
-            self.data = response["data"]
-            self.headers = response["headers"]
-            self.status_code = response["status_code"]
-            return self.validate()
-        else:
-            raise StopIteration
-
-    def get(self, key, default=None):
-        """Retrieves any key from the response data.
-
-        Note:
-            This is implemented so users can reference the
-            SlackResponse object like a dictionary.
-            e.g. response.get("ok", False)
-
-        Returns:
-            The value from data or the specified default.
-        """
-        if isinstance(self.data, bytes):
-            raise ValueError(
-                "As the response.data is binary data, this operation is unsupported"
-            )
-        return self.data.get(key, default)
-
-    def validate(self):
-        """Check if the response from Slack was successful.
-
-        Returns:
-            (SlackResponse)
-                This method returns it's own object. e.g. 'self'
-
-        Raises:
-            SlackApiError: The request to the Slack API failed.
-        """
-        if self._logger.level <= logging.DEBUG:
-            body = self.data if isinstance(self.data, dict) else "(binary)"
-            self._logger.debug(
-                "Received the following response - "
-                f"status: {self.status_code}, "
-                f"headers: {dict(self.headers)}, "
-                f"body: {body}"
-            )
-        if (
-            self.status_code == 200
-            and self.data
-            and (isinstance(self.data, bytes) or self.data.get("ok", False))
-        ):
-            return self
-        msg = "The request to the Slack API failed."
-        raise e.SlackApiError(message=msg, response=self)
-
-    @staticmethod
-    def _next_cursor_is_present(data):
-        """Determine if the response contains 'next_cursor'
-        and 'next_cursor' is not empty.
-
-        Returns:
-            A boolean value.
-        """
-        present = (
-            "response_metadata" in data
-            and "next_cursor" in data["response_metadata"]
-            and data["response_metadata"]["next_cursor"] != ""
-        )
-        return present
-
-

Methods

-
-
-def get(self, key, default=None) -
-
-

Retrieves any key from the response data.

-

Note

-

This is implemented so users can reference the -SlackResponse object like a dictionary. -e.g. response.get("ok", False)

-

Returns

-

The value from data or the specified default.

-
- -Expand source code - -
def get(self, key, default=None):
-    """Retrieves any key from the response data.
-
-    Note:
-        This is implemented so users can reference the
-        SlackResponse object like a dictionary.
-        e.g. response.get("ok", False)
-
-    Returns:
-        The value from data or the specified default.
-    """
-    if isinstance(self.data, bytes):
-        raise ValueError(
-            "As the response.data is binary data, this operation is unsupported"
-        )
-    return self.data.get(key, default)
-
-
-
-def validate(self) -
-
-

Check if the response from Slack was successful.

-

Returns

-

(SlackResponse) -This method returns it's own object. e.g. 'self'

-

Raises

-
-
SlackApiError
-
The request to the Slack API failed.
-
-
- -Expand source code - -
def validate(self):
-    """Check if the response from Slack was successful.
-
-    Returns:
-        (SlackResponse)
-            This method returns it's own object. e.g. 'self'
-
-    Raises:
-        SlackApiError: The request to the Slack API failed.
-    """
-    if self._logger.level <= logging.DEBUG:
-        body = self.data if isinstance(self.data, dict) else "(binary)"
-        self._logger.debug(
-            "Received the following response - "
-            f"status: {self.status_code}, "
-            f"headers: {dict(self.headers)}, "
-            f"body: {body}"
-        )
-    if (
-        self.status_code == 200
-        and self.data
-        and (isinstance(self.data, bytes) or self.data.get("ok", False))
-    ):
-        return self
-    msg = "The request to the Slack API failed."
-    raise e.SlackApiError(message=msg, response=self)
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/web/slack_response.html b/docs/api-docs/slack_sdk/web/slack_response.html deleted file mode 100644 index e29620ca3..000000000 --- a/docs/api-docs/slack_sdk/web/slack_response.html +++ /dev/null @@ -1,598 +0,0 @@ - - - - - - -slack_sdk.web.slack_response API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.web.slack_response

-
-
-

A Python module for interacting and consuming responses from Slack.

-
- -Expand source code - -
"""A Python module for interacting and consuming responses from Slack."""
-
-import logging
-from typing import Union
-
-import slack_sdk.errors as e
-from .internal_utils import _next_cursor_is_present
-
-
-class SlackResponse:
-    """An iterable container of response data.
-
-    Attributes:
-        data (dict): The json-encoded content of the response. Along
-            with the headers and status code information.
-
-    Methods:
-        validate: Check if the response from Slack was successful.
-        get: Retrieves any key from the response data.
-        next: Retrieves the next portion of results,
-            if 'next_cursor' is present.
-
-    Example:
-    ```python
-    import os
-    import slack
-
-    client = slack.WebClient(token=os.environ['SLACK_API_TOKEN'])
-
-    response1 = client.auth_revoke(test='true')
-    assert not response1['revoked']
-
-    response2 = client.auth_test()
-    assert response2.get('ok', False)
-
-    users = []
-    for page in client.users_list(limit=2):
-        users = users + page['members']
-    ```
-
-    Note:
-        Some responses return collections of information
-        like channel and user lists. If they do it's likely
-        that you'll only receive a portion of results. This
-        object allows you to iterate over the response which
-        makes subsequent API requests until your code hits
-        'break' or there are no more results to be found.
-
-        Any attributes or methods prefixed with _underscores are
-        intended to be "private" internal use only. They may be changed or
-        removed at anytime.
-    """
-
-    def __init__(
-        self,
-        *,
-        client,
-        http_verb: str,
-        api_url: str,
-        req_args: dict,
-        data: Union[dict, bytes],  # data can be binary data
-        headers: dict,
-        status_code: int,
-    ):
-        self.http_verb = http_verb
-        self.api_url = api_url
-        self.req_args = req_args
-        self.data = data
-        self.headers = headers
-        self.status_code = status_code
-        self._initial_data = data
-        self._iteration = None  # for __iter__ & __next__
-        self._client = client
-        self._logger = logging.getLogger(__name__)
-
-    def __str__(self):
-        """Return the Response data if object is converted to a string."""
-        if isinstance(self.data, bytes):
-            raise ValueError(
-                "As the response.data is binary data, this operation is unsupported"
-            )
-        return f"{self.data}"
-
-    def __contains__(self, key: str) -> bool:
-        return self.get(key) is not None
-
-    def __getitem__(self, key):
-        """Retrieves any key from the data store.
-
-        Note:
-            This is implemented so users can reference the
-            SlackResponse object like a dictionary.
-            e.g. response["ok"]
-
-        Returns:
-            The value from data or None.
-        """
-        if isinstance(self.data, bytes):
-            raise ValueError(
-                "As the response.data is binary data, this operation is unsupported"
-            )
-        if self.data is None:
-            raise ValueError(
-                "As the response.data is empty, this operation is unsupported"
-            )
-        return self.data.get(key, None)
-
-    def __iter__(self):
-        """Enables the ability to iterate over the response.
-        It's required for the iterator protocol.
-
-        Note:
-            This enables Slack cursor-based pagination.
-
-        Returns:
-            (SlackResponse) self
-        """
-        self._iteration = 0
-        self.data = self._initial_data
-        return self
-
-    def __next__(self):
-        """Retrieves the next portion of results, if 'next_cursor' is present.
-
-        Note:
-            Some responses return collections of information
-            like channel and user lists. If they do it's likely
-            that you'll only receive a portion of results. This
-            method allows you to iterate over the response until
-            your code hits 'break' or there are no more results
-            to be found.
-
-        Returns:
-            (SlackResponse) self
-                With the new response data now attached to this object.
-
-        Raises:
-            SlackApiError: If the request to the Slack API failed.
-            StopIteration: If 'next_cursor' is not present or empty.
-        """
-        if isinstance(self.data, bytes):
-            raise ValueError(
-                "As the response.data is binary data, this operation is unsupported"
-            )
-        self._iteration += 1
-        if self._iteration == 1:
-            return self
-        if _next_cursor_is_present(self.data):  # skipcq: PYL-R1705
-            params = self.req_args.get("params", {})
-            if params is None:
-                params = {}
-            next_cursor = self.data.get("response_metadata", {}).get(
-                "next_cursor"
-            ) or self.data.get("next_cursor")
-            params.update({"cursor": next_cursor})
-            self.req_args.update({"params": params})
-
-            # This method sends a request in a synchronous way
-            response = self._client._request_for_pagination(  # skipcq: PYL-W0212
-                api_url=self.api_url, req_args=self.req_args
-            )
-            self.data = response["data"]
-            self.headers = response["headers"]
-            self.status_code = response["status_code"]
-            return self.validate()
-        else:
-            raise StopIteration
-
-    def get(self, key, default=None):
-        """Retrieves any key from the response data.
-
-        Note:
-            This is implemented so users can reference the
-            SlackResponse object like a dictionary.
-            e.g. response.get("ok", False)
-
-        Returns:
-            The value from data or the specified default.
-        """
-        if isinstance(self.data, bytes):
-            raise ValueError(
-                "As the response.data is binary data, this operation is unsupported"
-            )
-        if self.data is None:
-            return None
-        return self.data.get(key, default)
-
-    def validate(self):
-        """Check if the response from Slack was successful.
-
-        Returns:
-            (SlackResponse)
-                This method returns it's own object. e.g. 'self'
-
-        Raises:
-            SlackApiError: The request to the Slack API failed.
-        """
-        if (
-            self.status_code == 200
-            and self.data
-            and (isinstance(self.data, bytes) or self.data.get("ok", False))
-        ):
-            return self
-        msg = f"The request to the Slack API failed. (url: {self.api_url})"
-        raise e.SlackApiError(message=msg, response=self)
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class SlackResponse -(*, client, http_verb:ย str, api_url:ย str, req_args:ย dict, data:ย Union[dict,ย bytes], headers:ย dict, status_code:ย int) -
-
-

An iterable container of response data.

-

Attributes

-
-
data : dict
-
The json-encoded content of the response. Along -with the headers and status code information.
-
-

Methods

-

validate: Check if the response from Slack was successful. -get: Retrieves any key from the response data. -next: Retrieves the next portion of results, -if 'next_cursor' is present.

-

Example:

-
import os
-import slack
-
-client = slack.WebClient(token=os.environ['SLACK_API_TOKEN'])
-
-response1 = client.auth_revoke(test='true')
-assert not response1['revoked']
-
-response2 = client.auth_test()
-assert response2.get('ok', False)
-
-users = []
-for page in client.users_list(limit=2):
-    users = users + page['members']
-
-

Note

-

Some responses return collections of information -like channel and user lists. If they do it's likely -that you'll only receive a portion of results. This -object allows you to iterate over the response which -makes subsequent API requests until your code hits -'break' or there are no more results to be found.

-

Any attributes or methods prefixed with _underscores are -intended to be "private" internal use only. They may be changed or -removed at anytime.

-
- -Expand source code - -
class SlackResponse:
-    """An iterable container of response data.
-
-    Attributes:
-        data (dict): The json-encoded content of the response. Along
-            with the headers and status code information.
-
-    Methods:
-        validate: Check if the response from Slack was successful.
-        get: Retrieves any key from the response data.
-        next: Retrieves the next portion of results,
-            if 'next_cursor' is present.
-
-    Example:
-    ```python
-    import os
-    import slack
-
-    client = slack.WebClient(token=os.environ['SLACK_API_TOKEN'])
-
-    response1 = client.auth_revoke(test='true')
-    assert not response1['revoked']
-
-    response2 = client.auth_test()
-    assert response2.get('ok', False)
-
-    users = []
-    for page in client.users_list(limit=2):
-        users = users + page['members']
-    ```
-
-    Note:
-        Some responses return collections of information
-        like channel and user lists. If they do it's likely
-        that you'll only receive a portion of results. This
-        object allows you to iterate over the response which
-        makes subsequent API requests until your code hits
-        'break' or there are no more results to be found.
-
-        Any attributes or methods prefixed with _underscores are
-        intended to be "private" internal use only. They may be changed or
-        removed at anytime.
-    """
-
-    def __init__(
-        self,
-        *,
-        client,
-        http_verb: str,
-        api_url: str,
-        req_args: dict,
-        data: Union[dict, bytes],  # data can be binary data
-        headers: dict,
-        status_code: int,
-    ):
-        self.http_verb = http_verb
-        self.api_url = api_url
-        self.req_args = req_args
-        self.data = data
-        self.headers = headers
-        self.status_code = status_code
-        self._initial_data = data
-        self._iteration = None  # for __iter__ & __next__
-        self._client = client
-        self._logger = logging.getLogger(__name__)
-
-    def __str__(self):
-        """Return the Response data if object is converted to a string."""
-        if isinstance(self.data, bytes):
-            raise ValueError(
-                "As the response.data is binary data, this operation is unsupported"
-            )
-        return f"{self.data}"
-
-    def __contains__(self, key: str) -> bool:
-        return self.get(key) is not None
-
-    def __getitem__(self, key):
-        """Retrieves any key from the data store.
-
-        Note:
-            This is implemented so users can reference the
-            SlackResponse object like a dictionary.
-            e.g. response["ok"]
-
-        Returns:
-            The value from data or None.
-        """
-        if isinstance(self.data, bytes):
-            raise ValueError(
-                "As the response.data is binary data, this operation is unsupported"
-            )
-        if self.data is None:
-            raise ValueError(
-                "As the response.data is empty, this operation is unsupported"
-            )
-        return self.data.get(key, None)
-
-    def __iter__(self):
-        """Enables the ability to iterate over the response.
-        It's required for the iterator protocol.
-
-        Note:
-            This enables Slack cursor-based pagination.
-
-        Returns:
-            (SlackResponse) self
-        """
-        self._iteration = 0
-        self.data = self._initial_data
-        return self
-
-    def __next__(self):
-        """Retrieves the next portion of results, if 'next_cursor' is present.
-
-        Note:
-            Some responses return collections of information
-            like channel and user lists. If they do it's likely
-            that you'll only receive a portion of results. This
-            method allows you to iterate over the response until
-            your code hits 'break' or there are no more results
-            to be found.
-
-        Returns:
-            (SlackResponse) self
-                With the new response data now attached to this object.
-
-        Raises:
-            SlackApiError: If the request to the Slack API failed.
-            StopIteration: If 'next_cursor' is not present or empty.
-        """
-        if isinstance(self.data, bytes):
-            raise ValueError(
-                "As the response.data is binary data, this operation is unsupported"
-            )
-        self._iteration += 1
-        if self._iteration == 1:
-            return self
-        if _next_cursor_is_present(self.data):  # skipcq: PYL-R1705
-            params = self.req_args.get("params", {})
-            if params is None:
-                params = {}
-            next_cursor = self.data.get("response_metadata", {}).get(
-                "next_cursor"
-            ) or self.data.get("next_cursor")
-            params.update({"cursor": next_cursor})
-            self.req_args.update({"params": params})
-
-            # This method sends a request in a synchronous way
-            response = self._client._request_for_pagination(  # skipcq: PYL-W0212
-                api_url=self.api_url, req_args=self.req_args
-            )
-            self.data = response["data"]
-            self.headers = response["headers"]
-            self.status_code = response["status_code"]
-            return self.validate()
-        else:
-            raise StopIteration
-
-    def get(self, key, default=None):
-        """Retrieves any key from the response data.
-
-        Note:
-            This is implemented so users can reference the
-            SlackResponse object like a dictionary.
-            e.g. response.get("ok", False)
-
-        Returns:
-            The value from data or the specified default.
-        """
-        if isinstance(self.data, bytes):
-            raise ValueError(
-                "As the response.data is binary data, this operation is unsupported"
-            )
-        if self.data is None:
-            return None
-        return self.data.get(key, default)
-
-    def validate(self):
-        """Check if the response from Slack was successful.
-
-        Returns:
-            (SlackResponse)
-                This method returns it's own object. e.g. 'self'
-
-        Raises:
-            SlackApiError: The request to the Slack API failed.
-        """
-        if (
-            self.status_code == 200
-            and self.data
-            and (isinstance(self.data, bytes) or self.data.get("ok", False))
-        ):
-            return self
-        msg = f"The request to the Slack API failed. (url: {self.api_url})"
-        raise e.SlackApiError(message=msg, response=self)
-
-

Methods

-
-
-def get(self, key, default=None) -
-
-

Retrieves any key from the response data.

-

Note

-

This is implemented so users can reference the -SlackResponse object like a dictionary. -e.g. response.get("ok", False)

-

Returns

-

The value from data or the specified default.

-
- -Expand source code - -
def get(self, key, default=None):
-    """Retrieves any key from the response data.
-
-    Note:
-        This is implemented so users can reference the
-        SlackResponse object like a dictionary.
-        e.g. response.get("ok", False)
-
-    Returns:
-        The value from data or the specified default.
-    """
-    if isinstance(self.data, bytes):
-        raise ValueError(
-            "As the response.data is binary data, this operation is unsupported"
-        )
-    if self.data is None:
-        return None
-    return self.data.get(key, default)
-
-
-
-def validate(self) -
-
-

Check if the response from Slack was successful.

-

Returns

-

(SlackResponse) -This method returns it's own object. e.g. 'self'

-

Raises

-
-
SlackApiError
-
The request to the Slack API failed.
-
-
- -Expand source code - -
def validate(self):
-    """Check if the response from Slack was successful.
-
-    Returns:
-        (SlackResponse)
-            This method returns it's own object. e.g. 'self'
-
-    Raises:
-        SlackApiError: The request to the Slack API failed.
-    """
-    if (
-        self.status_code == 200
-        and self.data
-        and (isinstance(self.data, bytes) or self.data.get("ok", False))
-    ):
-        return self
-    msg = f"The request to the Slack API failed. (url: {self.api_url})"
-    raise e.SlackApiError(message=msg, response=self)
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/webhook/client.html b/docs/api-docs/slack_sdk/webhook/client.html deleted file mode 100644 index 2978d8229..000000000 --- a/docs/api-docs/slack_sdk/webhook/client.html +++ /dev/null @@ -1,825 +0,0 @@ - - - - - - -slack_sdk.webhook.client API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.webhook.client

-
-
-
- -Expand source code - -
import json
-import logging
-import urllib
-from http.client import HTTPResponse
-from ssl import SSLContext
-from typing import Dict, Union, Sequence, Optional, List
-from urllib.error import HTTPError
-from urllib.request import Request, urlopen, OpenerDirector, ProxyHandler, HTTPSHandler
-
-from slack_sdk.errors import SlackRequestError
-from slack_sdk.models.attachments import Attachment
-from slack_sdk.models.blocks import Block
-from .internal_utils import (
-    _build_body,
-    _build_request_headers,
-    _debug_log_response,
-    get_user_agent,
-)
-from .webhook_response import WebhookResponse
-from slack_sdk.http_retry import default_retry_handlers
-from slack_sdk.http_retry.handler import RetryHandler
-from slack_sdk.http_retry.request import HttpRequest as RetryHttpRequest
-from slack_sdk.http_retry.response import HttpResponse as RetryHttpResponse
-from slack_sdk.http_retry.state import RetryState
-from ..proxy_env_variable_loader import load_http_proxy_from_env
-
-
-class WebhookClient:
-    url: str
-    timeout: int
-    ssl: Optional[SSLContext]
-    proxy: Optional[str]
-    default_headers: Dict[str, str]
-    logger: logging.Logger
-    retry_handlers: List[RetryHandler]
-
-    def __init__(
-        self,
-        url: str,
-        timeout: int = 30,
-        ssl: Optional[SSLContext] = None,
-        proxy: Optional[str] = None,
-        default_headers: Optional[Dict[str, str]] = None,
-        user_agent_prefix: Optional[str] = None,
-        user_agent_suffix: Optional[str] = None,
-        logger: Optional[logging.Logger] = None,
-        retry_handlers: Optional[List[RetryHandler]] = None,
-    ):
-        """API client for Incoming Webhooks and `response_url`
-
-        https://api.slack.com/messaging/webhooks
-
-        Args:
-            url: Complete URL to send data (e.g., `https://hooks.slack.com/XXX`)
-            timeout: Request timeout (in seconds)
-            ssl: `ssl.SSLContext` to use for requests
-            proxy: Proxy URL (e.g., `localhost:9000`, `http://localhost:9000`)
-            default_headers: Request headers to add to all requests
-            user_agent_prefix: Prefix for User-Agent header value
-            user_agent_suffix: Suffix for User-Agent header value
-            logger: Custom logger
-            retry_handlers: Retry handlers
-        """
-        self.url = url
-        self.timeout = timeout
-        self.ssl = ssl
-        self.proxy = proxy
-        self.default_headers = default_headers if default_headers else {}
-        self.default_headers["User-Agent"] = get_user_agent(
-            user_agent_prefix, user_agent_suffix
-        )
-        self.logger = logger if logger is not None else logging.getLogger(__name__)
-        self.retry_handlers = (
-            retry_handlers if retry_handlers is not None else default_retry_handlers()
-        )
-
-        if self.proxy is None or len(self.proxy.strip()) == 0:
-            env_variable = load_http_proxy_from_env(self.logger)
-            if env_variable is not None:
-                self.proxy = env_variable
-
-    def send(
-        self,
-        *,
-        text: Optional[str] = None,
-        attachments: Optional[Sequence[Union[Dict[str, any], Attachment]]] = None,
-        blocks: Optional[Sequence[Union[Dict[str, any], Block]]] = 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,
-        headers: Optional[Dict[str, str]] = None,
-    ) -> WebhookResponse:
-        """Performs a Slack API request and returns the result.
-
-        Args:
-            text: The text message
-                (even when having blocks, setting this as well is recommended as it works as fallback)
-            attachments: A collection of attachments
-            blocks: A collection of Block Kit UI components
-            response_type: The type of message (either 'in_channel' or 'ephemeral')
-            replace_original: True if you use this option for response_url requests
-            delete_original: True if you use this option for response_url requests
-            unfurl_links: Option to indicate whether text url should unfurl
-            unfurl_media: Option to indicate whether media url should unfurl
-            headers: Request headers to append only for this request
-
-        Returns:
-            Webhook response
-        """
-        return self.send_dict(
-            # It's fine to have None value elements here
-            # because _build_body() filters them out when constructing the actual body data
-            body={
-                "text": text,
-                "attachments": attachments,
-                "blocks": blocks,
-                "response_type": response_type,
-                "replace_original": replace_original,
-                "delete_original": delete_original,
-                "unfurl_links": unfurl_links,
-                "unfurl_media": unfurl_media,
-            },
-            headers=headers,
-        )
-
-    def send_dict(
-        self, body: Dict[str, any], headers: Optional[Dict[str, str]] = None
-    ) -> WebhookResponse:
-        """Performs a Slack API request and returns the result.
-
-        Args:
-            body: JSON data structure (it's still a dict at this point),
-                if you give this argument, body_params and files will be skipped
-            headers: Request headers to append only for this request
-        Returns:
-            Webhook response
-        """
-        return self._perform_http_request(
-            body=_build_body(body),
-            headers=_build_request_headers(self.default_headers, headers),
-        )
-
-    def _perform_http_request(
-        self, *, body: Dict[str, any], headers: Dict[str, str]
-    ) -> WebhookResponse:
-        body = json.dumps(body)
-        headers["Content-Type"] = "application/json;charset=utf-8"
-
-        if self.logger.level <= logging.DEBUG:
-            self.logger.debug(
-                f"Sending a request - url: {self.url}, body: {body}, headers: {headers}"
-            )
-
-        url = self.url
-        # NOTE: Intentionally ignore the `http_verb` here
-        # Slack APIs accepts any API method requests with POST methods
-        req = Request(
-            method="POST", url=url, data=body.encode("utf-8"), headers=headers
-        )
-        resp = None
-        last_error = None
-
-        retry_state = RetryState()
-        counter_for_safety = 0
-        while counter_for_safety < 100:
-            counter_for_safety += 1
-            # If this is a retry, the next try started here. We can reset the flag.
-            retry_state.next_attempt_requested = False
-
-            try:
-                resp = self._perform_http_request_internal(url, req)
-                # The resp is a 200 OK response
-                return resp
-
-            except HTTPError as e:
-                # read the response body here
-                charset = e.headers.get_content_charset() or "utf-8"
-                response_body: str = e.read().decode(charset)
-                resp = WebhookResponse(
-                    url=url,
-                    status_code=e.code,
-                    body=response_body,
-                    headers=e.headers,
-                )
-                if e.code == 429:
-                    # for backward-compatibility with WebClient (v.2.5.0 or older)
-                    resp.headers["Retry-After"] = resp.headers["retry-after"]
-                _debug_log_response(self.logger, resp)
-
-                # Try to find a retry handler for this error
-                retry_request = RetryHttpRequest.from_urllib_http_request(req)
-                retry_response = RetryHttpResponse(
-                    status_code=e.code,
-                    headers={k: [v] for k, v in e.headers.items()},
-                    data=response_body.encode("utf-8")
-                    if response_body is not None
-                    else None,
-                )
-                for handler in self.retry_handlers:
-                    if handler.can_retry(
-                        state=retry_state,
-                        request=retry_request,
-                        response=retry_response,
-                        error=e,
-                    ):
-                        if self.logger.level <= logging.DEBUG:
-                            self.logger.info(
-                                f"A retry handler found: {type(handler).__name__} for {req.method} {req.full_url} - {e}"
-                            )
-                        handler.prepare_for_next_attempt(
-                            state=retry_state,
-                            request=retry_request,
-                            response=retry_response,
-                            error=e,
-                        )
-                        break
-
-                if retry_state.next_attempt_requested is False:
-                    return resp
-
-            except Exception as err:
-                last_error = err
-                self.logger.error(
-                    f"Failed to send a request to Slack API server: {err}"
-                )
-
-                # Try to find a retry handler for this error
-                retry_request = RetryHttpRequest.from_urllib_http_request(req)
-                for handler in self.retry_handlers:
-                    if handler.can_retry(
-                        state=retry_state,
-                        request=retry_request,
-                        response=None,
-                        error=err,
-                    ):
-                        if self.logger.level <= logging.DEBUG:
-                            self.logger.info(
-                                f"A retry handler found: {type(handler).__name__} for {req.method} {req.full_url} - {err}"
-                            )
-                        handler.prepare_for_next_attempt(
-                            state=retry_state,
-                            request=retry_request,
-                            response=None,
-                            error=err,
-                        )
-                        self.logger.info(
-                            f"Going to retry the same request: {req.method} {req.full_url}"
-                        )
-                        break
-
-                if retry_state.next_attempt_requested is False:
-                    raise err
-
-        if resp is not None:
-            return resp
-        raise last_error
-
-    def _perform_http_request_internal(self, url: str, req: Request):
-        opener: Optional[OpenerDirector] = None
-        # for security (BAN-B310)
-        if url.lower().startswith("http"):
-            if self.proxy is not None:
-                if isinstance(self.proxy, str):
-                    opener = urllib.request.build_opener(
-                        ProxyHandler({"http": self.proxy, "https": self.proxy}),
-                        HTTPSHandler(context=self.ssl),
-                    )
-                else:
-                    raise SlackRequestError(
-                        f"Invalid proxy detected: {self.proxy} must be a str value"
-                    )
-        else:
-            raise SlackRequestError(f"Invalid URL detected: {url}")
-
-        # NOTE: BAN-B310 is already checked above
-        resp: Optional[HTTPResponse] = None
-        if opener:
-            resp = opener.open(req, timeout=self.timeout)  # skipcq: BAN-B310
-        else:
-            resp = urlopen(  # skipcq: BAN-B310
-                req, context=self.ssl, timeout=self.timeout
-            )
-        charset: str = resp.headers.get_content_charset() or "utf-8"
-        response_body: str = resp.read().decode(charset)
-        resp = WebhookResponse(
-            url=url,
-            status_code=resp.status,
-            body=response_body,
-            headers=resp.headers,
-        )
-        _debug_log_response(self.logger, resp)
-        return resp
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class WebhookClient -(url:ย str, timeout:ย intย =ย 30, ssl:ย Optional[ssl.SSLContext]ย =ย None, proxy:ย Optional[str]ย =ย None, default_headers:ย Optional[Dict[str,ย str]]ย =ย None, user_agent_prefix:ย Optional[str]ย =ย None, user_agent_suffix:ย Optional[str]ย =ย None, logger:ย Optional[logging.Logger]ย =ย None, retry_handlers:ย Optional[List[RetryHandler]]ย =ย None) -
-
-

API client for Incoming Webhooks and response_url

-

https://api.slack.com/messaging/webhooks

-

Args

-
-
url
-
Complete URL to send data (e.g., https://hooks.slack.com/XXX)
-
timeout
-
Request timeout (in seconds)
-
ssl
-
ssl.SSLContext to use for requests
-
proxy
-
Proxy URL (e.g., localhost:9000, http://localhost:9000)
-
default_headers
-
Request headers to add to all requests
-
user_agent_prefix
-
Prefix for User-Agent header value
-
user_agent_suffix
-
Suffix for User-Agent header value
-
logger
-
Custom logger
-
retry_handlers
-
Retry handlers
-
-
- -Expand source code - -
class WebhookClient:
-    url: str
-    timeout: int
-    ssl: Optional[SSLContext]
-    proxy: Optional[str]
-    default_headers: Dict[str, str]
-    logger: logging.Logger
-    retry_handlers: List[RetryHandler]
-
-    def __init__(
-        self,
-        url: str,
-        timeout: int = 30,
-        ssl: Optional[SSLContext] = None,
-        proxy: Optional[str] = None,
-        default_headers: Optional[Dict[str, str]] = None,
-        user_agent_prefix: Optional[str] = None,
-        user_agent_suffix: Optional[str] = None,
-        logger: Optional[logging.Logger] = None,
-        retry_handlers: Optional[List[RetryHandler]] = None,
-    ):
-        """API client for Incoming Webhooks and `response_url`
-
-        https://api.slack.com/messaging/webhooks
-
-        Args:
-            url: Complete URL to send data (e.g., `https://hooks.slack.com/XXX`)
-            timeout: Request timeout (in seconds)
-            ssl: `ssl.SSLContext` to use for requests
-            proxy: Proxy URL (e.g., `localhost:9000`, `http://localhost:9000`)
-            default_headers: Request headers to add to all requests
-            user_agent_prefix: Prefix for User-Agent header value
-            user_agent_suffix: Suffix for User-Agent header value
-            logger: Custom logger
-            retry_handlers: Retry handlers
-        """
-        self.url = url
-        self.timeout = timeout
-        self.ssl = ssl
-        self.proxy = proxy
-        self.default_headers = default_headers if default_headers else {}
-        self.default_headers["User-Agent"] = get_user_agent(
-            user_agent_prefix, user_agent_suffix
-        )
-        self.logger = logger if logger is not None else logging.getLogger(__name__)
-        self.retry_handlers = (
-            retry_handlers if retry_handlers is not None else default_retry_handlers()
-        )
-
-        if self.proxy is None or len(self.proxy.strip()) == 0:
-            env_variable = load_http_proxy_from_env(self.logger)
-            if env_variable is not None:
-                self.proxy = env_variable
-
-    def send(
-        self,
-        *,
-        text: Optional[str] = None,
-        attachments: Optional[Sequence[Union[Dict[str, any], Attachment]]] = None,
-        blocks: Optional[Sequence[Union[Dict[str, any], Block]]] = 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,
-        headers: Optional[Dict[str, str]] = None,
-    ) -> WebhookResponse:
-        """Performs a Slack API request and returns the result.
-
-        Args:
-            text: The text message
-                (even when having blocks, setting this as well is recommended as it works as fallback)
-            attachments: A collection of attachments
-            blocks: A collection of Block Kit UI components
-            response_type: The type of message (either 'in_channel' or 'ephemeral')
-            replace_original: True if you use this option for response_url requests
-            delete_original: True if you use this option for response_url requests
-            unfurl_links: Option to indicate whether text url should unfurl
-            unfurl_media: Option to indicate whether media url should unfurl
-            headers: Request headers to append only for this request
-
-        Returns:
-            Webhook response
-        """
-        return self.send_dict(
-            # It's fine to have None value elements here
-            # because _build_body() filters them out when constructing the actual body data
-            body={
-                "text": text,
-                "attachments": attachments,
-                "blocks": blocks,
-                "response_type": response_type,
-                "replace_original": replace_original,
-                "delete_original": delete_original,
-                "unfurl_links": unfurl_links,
-                "unfurl_media": unfurl_media,
-            },
-            headers=headers,
-        )
-
-    def send_dict(
-        self, body: Dict[str, any], headers: Optional[Dict[str, str]] = None
-    ) -> WebhookResponse:
-        """Performs a Slack API request and returns the result.
-
-        Args:
-            body: JSON data structure (it's still a dict at this point),
-                if you give this argument, body_params and files will be skipped
-            headers: Request headers to append only for this request
-        Returns:
-            Webhook response
-        """
-        return self._perform_http_request(
-            body=_build_body(body),
-            headers=_build_request_headers(self.default_headers, headers),
-        )
-
-    def _perform_http_request(
-        self, *, body: Dict[str, any], headers: Dict[str, str]
-    ) -> WebhookResponse:
-        body = json.dumps(body)
-        headers["Content-Type"] = "application/json;charset=utf-8"
-
-        if self.logger.level <= logging.DEBUG:
-            self.logger.debug(
-                f"Sending a request - url: {self.url}, body: {body}, headers: {headers}"
-            )
-
-        url = self.url
-        # NOTE: Intentionally ignore the `http_verb` here
-        # Slack APIs accepts any API method requests with POST methods
-        req = Request(
-            method="POST", url=url, data=body.encode("utf-8"), headers=headers
-        )
-        resp = None
-        last_error = None
-
-        retry_state = RetryState()
-        counter_for_safety = 0
-        while counter_for_safety < 100:
-            counter_for_safety += 1
-            # If this is a retry, the next try started here. We can reset the flag.
-            retry_state.next_attempt_requested = False
-
-            try:
-                resp = self._perform_http_request_internal(url, req)
-                # The resp is a 200 OK response
-                return resp
-
-            except HTTPError as e:
-                # read the response body here
-                charset = e.headers.get_content_charset() or "utf-8"
-                response_body: str = e.read().decode(charset)
-                resp = WebhookResponse(
-                    url=url,
-                    status_code=e.code,
-                    body=response_body,
-                    headers=e.headers,
-                )
-                if e.code == 429:
-                    # for backward-compatibility with WebClient (v.2.5.0 or older)
-                    resp.headers["Retry-After"] = resp.headers["retry-after"]
-                _debug_log_response(self.logger, resp)
-
-                # Try to find a retry handler for this error
-                retry_request = RetryHttpRequest.from_urllib_http_request(req)
-                retry_response = RetryHttpResponse(
-                    status_code=e.code,
-                    headers={k: [v] for k, v in e.headers.items()},
-                    data=response_body.encode("utf-8")
-                    if response_body is not None
-                    else None,
-                )
-                for handler in self.retry_handlers:
-                    if handler.can_retry(
-                        state=retry_state,
-                        request=retry_request,
-                        response=retry_response,
-                        error=e,
-                    ):
-                        if self.logger.level <= logging.DEBUG:
-                            self.logger.info(
-                                f"A retry handler found: {type(handler).__name__} for {req.method} {req.full_url} - {e}"
-                            )
-                        handler.prepare_for_next_attempt(
-                            state=retry_state,
-                            request=retry_request,
-                            response=retry_response,
-                            error=e,
-                        )
-                        break
-
-                if retry_state.next_attempt_requested is False:
-                    return resp
-
-            except Exception as err:
-                last_error = err
-                self.logger.error(
-                    f"Failed to send a request to Slack API server: {err}"
-                )
-
-                # Try to find a retry handler for this error
-                retry_request = RetryHttpRequest.from_urllib_http_request(req)
-                for handler in self.retry_handlers:
-                    if handler.can_retry(
-                        state=retry_state,
-                        request=retry_request,
-                        response=None,
-                        error=err,
-                    ):
-                        if self.logger.level <= logging.DEBUG:
-                            self.logger.info(
-                                f"A retry handler found: {type(handler).__name__} for {req.method} {req.full_url} - {err}"
-                            )
-                        handler.prepare_for_next_attempt(
-                            state=retry_state,
-                            request=retry_request,
-                            response=None,
-                            error=err,
-                        )
-                        self.logger.info(
-                            f"Going to retry the same request: {req.method} {req.full_url}"
-                        )
-                        break
-
-                if retry_state.next_attempt_requested is False:
-                    raise err
-
-        if resp is not None:
-            return resp
-        raise last_error
-
-    def _perform_http_request_internal(self, url: str, req: Request):
-        opener: Optional[OpenerDirector] = None
-        # for security (BAN-B310)
-        if url.lower().startswith("http"):
-            if self.proxy is not None:
-                if isinstance(self.proxy, str):
-                    opener = urllib.request.build_opener(
-                        ProxyHandler({"http": self.proxy, "https": self.proxy}),
-                        HTTPSHandler(context=self.ssl),
-                    )
-                else:
-                    raise SlackRequestError(
-                        f"Invalid proxy detected: {self.proxy} must be a str value"
-                    )
-        else:
-            raise SlackRequestError(f"Invalid URL detected: {url}")
-
-        # NOTE: BAN-B310 is already checked above
-        resp: Optional[HTTPResponse] = None
-        if opener:
-            resp = opener.open(req, timeout=self.timeout)  # skipcq: BAN-B310
-        else:
-            resp = urlopen(  # skipcq: BAN-B310
-                req, context=self.ssl, timeout=self.timeout
-            )
-        charset: str = resp.headers.get_content_charset() or "utf-8"
-        response_body: str = resp.read().decode(charset)
-        resp = WebhookResponse(
-            url=url,
-            status_code=resp.status,
-            body=response_body,
-            headers=resp.headers,
-        )
-        _debug_log_response(self.logger, resp)
-        return resp
-
-

Class variables

-
-
var default_headers :ย Dict[str,ย str]
-
-
-
-
var logger :ย logging.Logger
-
-
-
-
var proxy :ย Optional[str]
-
-
-
-
var retry_handlers :ย List[RetryHandler]
-
-
-
-
var ssl :ย Optional[ssl.SSLContext]
-
-
-
-
var timeout :ย int
-
-
-
-
var url :ย str
-
-
-
-
-

Methods

-
-
-def send(self, *, text:ย Optional[str]ย =ย None, attachments:ย Optional[Sequence[Union[Dict[str,ย ],ย Attachment]]]ย =ย None, blocks:ย Optional[Sequence[Union[Dict[str,ย ],ย Block]]]ย =ย 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, headers:ย Optional[Dict[str,ย str]]ย =ย None) โ€‘>ย WebhookResponse -
-
-

Performs a Slack API request and returns the result.

-

Args

-
-
text
-
The text message -(even when having blocks, setting this as well is recommended as it works as fallback)
-
attachments
-
A collection of attachments
-
blocks
-
A collection of Block Kit UI components
-
response_type
-
The type of message (either 'in_channel' or 'ephemeral')
-
replace_original
-
True if you use this option for response_url requests
-
delete_original
-
True if you use this option for response_url requests
-
unfurl_links
-
Option to indicate whether text url should unfurl
-
unfurl_media
-
Option to indicate whether media url should unfurl
-
headers
-
Request headers to append only for this request
-
-

Returns

-

Webhook response

-
- -Expand source code - -
def send(
-    self,
-    *,
-    text: Optional[str] = None,
-    attachments: Optional[Sequence[Union[Dict[str, any], Attachment]]] = None,
-    blocks: Optional[Sequence[Union[Dict[str, any], Block]]] = 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,
-    headers: Optional[Dict[str, str]] = None,
-) -> WebhookResponse:
-    """Performs a Slack API request and returns the result.
-
-    Args:
-        text: The text message
-            (even when having blocks, setting this as well is recommended as it works as fallback)
-        attachments: A collection of attachments
-        blocks: A collection of Block Kit UI components
-        response_type: The type of message (either 'in_channel' or 'ephemeral')
-        replace_original: True if you use this option for response_url requests
-        delete_original: True if you use this option for response_url requests
-        unfurl_links: Option to indicate whether text url should unfurl
-        unfurl_media: Option to indicate whether media url should unfurl
-        headers: Request headers to append only for this request
-
-    Returns:
-        Webhook response
-    """
-    return self.send_dict(
-        # It's fine to have None value elements here
-        # because _build_body() filters them out when constructing the actual body data
-        body={
-            "text": text,
-            "attachments": attachments,
-            "blocks": blocks,
-            "response_type": response_type,
-            "replace_original": replace_original,
-            "delete_original": delete_original,
-            "unfurl_links": unfurl_links,
-            "unfurl_media": unfurl_media,
-        },
-        headers=headers,
-    )
-
-
-
-def send_dict(self, body:ย Dict[str,ย ], headers:ย Optional[Dict[str,ย str]]ย =ย None) โ€‘>ย WebhookResponse -
-
-

Performs a Slack API request and returns the result.

-

Args

-
-
body
-
JSON data structure (it's still a dict at this point), -if you give this argument, body_params and files will be skipped
-
headers
-
Request headers to append only for this request
-
-

Returns

-

Webhook response

-
- -Expand source code - -
def send_dict(
-    self, body: Dict[str, any], headers: Optional[Dict[str, str]] = None
-) -> WebhookResponse:
-    """Performs a Slack API request and returns the result.
-
-    Args:
-        body: JSON data structure (it's still a dict at this point),
-            if you give this argument, body_params and files will be skipped
-        headers: Request headers to append only for this request
-    Returns:
-        Webhook response
-    """
-    return self._perform_http_request(
-        body=_build_body(body),
-        headers=_build_request_headers(self.default_headers, headers),
-    )
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/webhook/index.html b/docs/api-docs/slack_sdk/webhook/index.html deleted file mode 100644 index a471af94c..000000000 --- a/docs/api-docs/slack_sdk/webhook/index.html +++ /dev/null @@ -1,94 +0,0 @@ - - - - - - -slack_sdk.webhook API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.webhook

-
-
-

You can use slack_sdk.webhook.WebhookClient for Incoming Webhooks -and message responses using response_url in payloads.

-
- -Expand source code - -
"""You can use slack_sdk.webhook.WebhookClient for Incoming Webhooks
-and message responses using response_url in payloads.
-"""
-# from .async_client import AsyncWebhookClient  # noqa
-from .client import WebhookClient  # noqa
-from .webhook_response import WebhookResponse  # noqa
-
-
-
-

Sub-modules

-
-
slack_sdk.webhook.async_client
-
-
-
-
slack_sdk.webhook.client
-
-
-
-
slack_sdk.webhook.internal_utils
-
-
-
-
slack_sdk.webhook.webhook_response
-
-
-
-
-
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/webhook/internal_utils.html b/docs/api-docs/slack_sdk/webhook/internal_utils.html deleted file mode 100644 index b4694a21e..000000000 --- a/docs/api-docs/slack_sdk/webhook/internal_utils.html +++ /dev/null @@ -1,103 +0,0 @@ - - - - - - -slack_sdk.webhook.internal_utils API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.webhook.internal_utils

-
-
-
- -Expand source code - -
import logging
-from typing import Optional, Dict, Any
-
-from slack_sdk.web.internal_utils import (
-    _parse_web_class_objects,
-    get_user_agent,
-)
-from .webhook_response import WebhookResponse
-
-
-def _build_body(original_body: Optional[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
-    if original_body:
-        body = {k: v for k, v in original_body.items() if v is not None}
-        _parse_web_class_objects(body)
-        return body
-    return None
-
-
-def _build_request_headers(
-    default_headers: Dict[str, str],
-    additional_headers: Optional[Dict[str, str]],
-) -> Dict[str, str]:
-    if default_headers is None and additional_headers is None:
-        return {}
-
-    request_headers = {
-        "Content-Type": "application/json;charset=utf-8",
-    }
-    if default_headers is None or "User-Agent" not in default_headers:
-        request_headers["User-Agent"] = get_user_agent()
-
-    request_headers.update(default_headers)
-    if additional_headers:
-        request_headers.update(additional_headers)
-    return request_headers
-
-
-def _debug_log_response(logger, resp: WebhookResponse) -> None:
-    if logger.level <= logging.DEBUG:
-        logger.debug(
-            "Received the following response - "
-            f"status: {resp.status_code}, "
-            f"headers: {(dict(resp.headers))}, "
-            f"body: {resp.body}"
-        )
-
-
-
-
-
-
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/api-docs/slack_sdk/webhook/webhook_response.html b/docs/api-docs/slack_sdk/webhook/webhook_response.html deleted file mode 100644 index 3e514ee57..000000000 --- a/docs/api-docs/slack_sdk/webhook/webhook_response.html +++ /dev/null @@ -1,106 +0,0 @@ - - - - - - -slack_sdk.webhook.webhook_response API documentation - - - - - - - - - - - -
-
-
-

Module slack_sdk.webhook.webhook_response

-
-
-
- -Expand source code - -
class WebhookResponse:
-    def __init__(
-        self,
-        *,
-        url: str,
-        status_code: int,
-        body: str,
-        headers: dict,
-    ):
-        self.api_url = url
-        self.status_code = status_code
-        self.body = body
-        self.headers = headers
-
-
-
-
-
-
-
-
-
-

Classes

-
-
-class WebhookResponse -(*, url:ย str, status_code:ย int, body:ย str, headers:ย dict) -
-
-
-
- -Expand source code - -
class WebhookResponse:
-    def __init__(
-        self,
-        *,
-        url: str,
-        status_code: int,
-        body: str,
-        headers: dict,
-    ):
-        self.api_url = url
-        self.status_code = status_code
-        self.body = body
-        self.headers = headers
-
-
-
-
-
- -
- - - \ No newline at end of file diff --git a/docs/assets/basic.css b/docs/assets/basic.css deleted file mode 100644 index be19270e4..000000000 --- a/docs/assets/basic.css +++ /dev/null @@ -1,856 +0,0 @@ -/* - * basic.css - * ~~~~~~~~~ - * - * Sphinx stylesheet -- basic theme. - * - * :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * - */ - -/* -- main layout ----------------------------------------------------------- */ - -div.clearer { - clear: both; -} - -div.section::after { - display: block; - content: ''; - clear: left; -} - -/* -- relbar ---------------------------------------------------------------- */ - -div.related { - width: 100%; - font-size: 90%; -} - -div.related h3 { - display: none; -} - -div.related ul { - margin: 0; - padding: 0 0 0 10px; - list-style: none; -} - -div.related li { - display: inline; -} - -div.related li.right { - float: right; - margin-right: 5px; -} - -/* -- sidebar --------------------------------------------------------------- */ - -div.sphinxsidebarwrapper { - padding: 10px 5px 0 10px; -} - -div.sphinxsidebar { - float: left; - width: 230px; - margin-left: -100%; - font-size: 90%; - word-wrap: break-word; - overflow-wrap : break-word; -} - -div.sphinxsidebar ul { - list-style: none; -} - -div.sphinxsidebar ul ul, -div.sphinxsidebar ul.want-points { - margin-left: 20px; - list-style: square; -} - -div.sphinxsidebar ul ul { - margin-top: 0; - margin-bottom: 0; -} - -div.sphinxsidebar form { - margin-top: 10px; -} - -div.sphinxsidebar input { - border: 1px solid #98dbcc; - font-family: sans-serif; - font-size: 1em; -} - -div.sphinxsidebar #searchbox form.search { - overflow: hidden; -} - -div.sphinxsidebar #searchbox input[type="text"] { - float: left; - width: 80%; - padding: 0.25em; - box-sizing: border-box; -} - -div.sphinxsidebar #searchbox input[type="submit"] { - float: left; - width: 20%; - border-left: none; - padding: 0.25em; - box-sizing: border-box; -} - - -img { - border: 0; - max-width: 100%; -} - -/* -- search page ----------------------------------------------------------- */ - -ul.search { - margin: 10px 0 0 20px; - padding: 0; -} - -ul.search li { - padding: 5px 0 5px 20px; - background-image: url(file.png); - background-repeat: no-repeat; - background-position: 0 7px; -} - -ul.search li a { - font-weight: bold; -} - -ul.search li div.context { - color: #888; - margin: 2px 0 0 30px; - text-align: left; -} - -ul.keywordmatches li.goodmatch a { - font-weight: bold; -} - -/* -- index page ------------------------------------------------------------ */ - -table.contentstable { - width: 90%; - margin-left: auto; - margin-right: auto; -} - -table.contentstable p.biglink { - line-height: 150%; -} - -a.biglink { - font-size: 1.3em; -} - -span.linkdescr { - font-style: italic; - padding-top: 5px; - font-size: 90%; -} - -/* -- general index --------------------------------------------------------- */ - -table.indextable { - width: 100%; -} - -table.indextable td { - text-align: left; - vertical-align: top; -} - -table.indextable ul { - margin-top: 0; - margin-bottom: 0; - list-style-type: none; -} - -table.indextable > tbody > tr > td > ul { - padding-left: 0em; -} - -table.indextable tr.pcap { - height: 10px; -} - -table.indextable tr.cap { - margin-top: 10px; - background-color: #f2f2f2; -} - -img.toggler { - margin-right: 3px; - margin-top: 3px; - cursor: pointer; -} - -div.modindex-jumpbox { - border-top: 1px solid #ddd; - border-bottom: 1px solid #ddd; - margin: 1em 0 1em 0; - padding: 0.4em; -} - -div.genindex-jumpbox { - border-top: 1px solid #ddd; - border-bottom: 1px solid #ddd; - margin: 1em 0 1em 0; - padding: 0.4em; -} - -/* -- domain module index --------------------------------------------------- */ - -table.modindextable td { - padding: 2px; - border-collapse: collapse; -} - -/* -- general body styles --------------------------------------------------- */ - -div.body { - min-width: 450px; - max-width: 800px; -} - -div.body p, div.body dd, div.body li, div.body blockquote { - -moz-hyphens: auto; - -ms-hyphens: auto; - -webkit-hyphens: auto; - hyphens: auto; -} - -a.headerlink { - visibility: hidden; -} - -a.brackets:before, -span.brackets > a:before{ - content: "["; -} - -a.brackets:after, -span.brackets > a:after { - content: "]"; -} - -h1:hover > a.headerlink, -h2:hover > a.headerlink, -h3:hover > a.headerlink, -h4:hover > a.headerlink, -h5:hover > a.headerlink, -h6:hover > a.headerlink, -dt:hover > a.headerlink, -caption:hover > a.headerlink, -p.caption:hover > a.headerlink, -div.code-block-caption:hover > a.headerlink { - visibility: visible; -} - -div.body p.caption { - text-align: inherit; -} - -div.body td { - text-align: left; -} - -.first { - margin-top: 0 !important; -} - -p.rubric { - margin-top: 30px; - font-weight: bold; -} - -img.align-left, .figure.align-left, object.align-left { - clear: left; - float: left; - margin-right: 1em; -} - -img.align-right, .figure.align-right, object.align-right { - clear: right; - float: right; - margin-left: 1em; -} - -img.align-center, .figure.align-center, object.align-center { - display: block; - margin-left: auto; - margin-right: auto; -} - -img.align-default, .figure.align-default { - display: block; - margin-left: auto; - margin-right: auto; -} - -.align-left { - text-align: left; -} - -.align-center { - text-align: center; -} - -.align-default { - text-align: center; -} - -.align-right { - text-align: right; -} - -/* -- sidebars -------------------------------------------------------------- */ - -div.sidebar { - margin: 0 0 0.5em 1em; - border: 1px solid #ddb; - padding: 7px; - background-color: #ffe; - width: 40%; - float: right; - clear: right; - overflow-x: auto; -} - -p.sidebar-title { - font-weight: bold; -} - -div.admonition, div.topic, blockquote { - clear: left; -} - -/* -- topics ---------------------------------------------------------------- */ - -div.topic { - border: 1px solid #ccc; - padding: 7px; - margin: 10px 0 10px 0; -} - -p.topic-title { - font-size: 1.1em; - font-weight: bold; - margin-top: 10px; -} - -/* -- admonitions ----------------------------------------------------------- */ - -div.admonition { - margin-top: 10px; - margin-bottom: 10px; - padding: 7px; -} - -div.admonition dt { - font-weight: bold; -} - -p.admonition-title { - margin: 0px 10px 5px 0px; - font-weight: bold; -} - -div.body p.centered { - text-align: center; - margin-top: 25px; -} - -/* -- content of sidebars/topics/admonitions -------------------------------- */ - -div.sidebar > :last-child, -div.topic > :last-child, -div.admonition > :last-child { - margin-bottom: 0; -} - -div.sidebar::after, -div.topic::after, -div.admonition::after, -blockquote::after { - display: block; - content: ''; - clear: both; -} - -/* -- tables ---------------------------------------------------------------- */ - -table.docutils { - margin-top: 10px; - margin-bottom: 10px; - border: 0; - border-collapse: collapse; -} - -table.align-center { - margin-left: auto; - margin-right: auto; -} - -table.align-default { - margin-left: auto; - margin-right: auto; -} - -table caption span.caption-number { - font-style: italic; -} - -table caption span.caption-text { -} - -table.docutils td, table.docutils th { - padding: 1px 8px 1px 5px; - border-top: 0; - border-left: 0; - border-right: 0; - border-bottom: 1px solid #aaa; -} - -table.footnote td, table.footnote th { - border: 0 !important; -} - -th { - text-align: left; - padding-right: 5px; -} - -table.citation { - border-left: solid 1px gray; - margin-left: 1px; -} - -table.citation td { - border-bottom: none; -} - -th > :first-child, -td > :first-child { - margin-top: 0px; -} - -th > :last-child, -td > :last-child { - margin-bottom: 0px; -} - -/* -- figures --------------------------------------------------------------- */ - -div.figure { - margin: 0.5em; - padding: 0.5em; -} - -div.figure p.caption { - padding: 0.3em; -} - -div.figure p.caption span.caption-number { - font-style: italic; -} - -div.figure p.caption span.caption-text { -} - -/* -- field list styles ----------------------------------------------------- */ - -table.field-list td, table.field-list th { - border: 0 !important; -} - -.field-list ul { - margin: 0; - padding-left: 1em; -} - -.field-list p { - margin: 0; -} - -.field-name { - -moz-hyphens: manual; - -ms-hyphens: manual; - -webkit-hyphens: manual; - hyphens: manual; -} - -/* -- hlist styles ---------------------------------------------------------- */ - -table.hlist { - margin: 1em 0; -} - -table.hlist td { - vertical-align: top; -} - - -/* -- other body styles ----------------------------------------------------- */ - -ol.arabic { - list-style: decimal; -} - -ol.loweralpha { - list-style: lower-alpha; -} - -ol.upperalpha { - list-style: upper-alpha; -} - -ol.lowerroman { - list-style: lower-roman; -} - -ol.upperroman { - list-style: upper-roman; -} - -:not(li) > ol > li:first-child > :first-child, -:not(li) > ul > li:first-child > :first-child { - margin-top: 0px; -} - -:not(li) > ol > li:last-child > :last-child, -:not(li) > ul > li:last-child > :last-child { - margin-bottom: 0px; -} - -ol.simple ol p, -ol.simple ul p, -ul.simple ol p, -ul.simple ul p { - margin-top: 0; -} - -ol.simple > li:not(:first-child) > p, -ul.simple > li:not(:first-child) > p { - margin-top: 0; -} - -ol.simple p, -ul.simple p { - margin-bottom: 0; -} - -dl.footnote > dt, -dl.citation > dt { - float: left; - margin-right: 0.5em; -} - -dl.footnote > dd, -dl.citation > dd { - margin-bottom: 0em; -} - -dl.footnote > dd:after, -dl.citation > dd:after { - content: ""; - clear: both; -} - -dl.field-list { - display: grid; - grid-template-columns: fit-content(30%) auto; -} - -dl.field-list > dt { - font-weight: bold; - word-break: break-word; - padding-left: 0.5em; - padding-right: 5px; -} - -dl.field-list > dt:after { - content: ":"; -} - -dl.field-list > dd { - padding-left: 0.5em; - margin-top: 0em; - margin-left: 0em; - margin-bottom: 0em; -} - -dl { - margin-bottom: 15px; -} - -dd > :first-child { - margin-top: 0px; -} - -dd ul, dd table { - margin-bottom: 10px; -} - -dd { - margin-top: 3px; - margin-bottom: 10px; - margin-left: 30px; -} - -dl > dd:last-child, -dl > dd:last-child > :last-child { - margin-bottom: 0; -} - -dt:target, span.highlighted { - background-color: #fbe54e; -} - -rect.highlighted { - fill: #fbe54e; -} - -dl.glossary dt { - font-weight: bold; - font-size: 1.1em; -} - -.optional { - font-size: 1.3em; -} - -.sig-paren { - font-size: larger; -} - -.versionmodified { - font-style: italic; -} - -.system-message { - background-color: #fda; - padding: 5px; - border: 3px solid red; -} - -.footnote:target { - background-color: #ffa; -} - -.line-block { - display: block; - margin-top: 1em; - margin-bottom: 1em; -} - -.line-block .line-block { - margin-top: 0; - margin-bottom: 0; - margin-left: 1.5em; -} - -.guilabel, .menuselection { - font-family: sans-serif; -} - -.accelerator { - text-decoration: underline; -} - -.classifier { - font-style: oblique; -} - -.classifier:before { - font-style: normal; - margin: 0.5em; - content: ":"; -} - -abbr, acronym { - border-bottom: dotted 1px; - cursor: help; -} - -/* -- code displays --------------------------------------------------------- */ - -pre { - overflow: auto; - overflow-y: hidden; /* fixes display issues on Chrome browsers */ -} - -pre, div[class*="highlight-"] { - clear: both; -} - -span.pre { - -moz-hyphens: none; - -ms-hyphens: none; - -webkit-hyphens: none; - hyphens: none; -} - -div[class*="highlight-"] { - margin: 1em 0; -} - -td.linenos pre { - border: 0; - background-color: transparent; - color: #aaa; -} - -table.highlighttable { - display: block; -} - -table.highlighttable tbody { - display: block; -} - -table.highlighttable tr { - display: flex; -} - -table.highlighttable td { - margin: 0; - padding: 0; -} - -table.highlighttable td.linenos { - padding-right: 0.5em; -} - -table.highlighttable td.code { - flex: 1; - overflow: hidden; -} - -.highlight .hll { - display: block; -} - -div.highlight pre, -table.highlighttable pre { - margin: 0; -} - -div.code-block-caption + div { - margin-top: 0; -} - -div.code-block-caption { - margin-top: 1em; - padding: 2px 5px; - font-size: small; -} - -div.code-block-caption code { - background-color: transparent; -} - -table.highlighttable td.linenos, -span.linenos, -div.doctest > div.highlight span.gp { /* gp: Generic.Prompt */ - user-select: none; -} - -div.code-block-caption span.caption-number { - padding: 0.1em 0.3em; - font-style: italic; -} - -div.code-block-caption span.caption-text { -} - -div.literal-block-wrapper { - margin: 1em 0; -} - -code.descname { - background-color: transparent; - font-weight: bold; - font-size: 1.2em; -} - -code.descclassname { - background-color: transparent; -} - -code.xref, a code { - background-color: transparent; - font-weight: bold; -} - -h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { - background-color: transparent; -} - -.viewcode-link { - float: right; -} - -.viewcode-back { - float: right; - font-family: sans-serif; -} - -div.viewcode-block:target { - margin: -1px -10px; - padding: 0 10px; -} - -/* -- math display ---------------------------------------------------------- */ - -img.math { - vertical-align: middle; -} - -div.body div.math p { - text-align: center; -} - -span.eqno { - float: right; -} - -span.eqno a.headerlink { - position: absolute; - z-index: 1; -} - -div.math:hover a.headerlink { - visibility: visible; -} - -/* -- printout stylesheet --------------------------------------------------- */ - -@media print { - div.document, - div.documentwrapper, - div.bodywrapper { - margin: 0 !important; - width: 100%; - } - - div.sphinxsidebar, - div.related, - div.footer, - #top-link { - display: none; - } -} \ No newline at end of file diff --git a/docs/assets/classic.css b/docs/assets/classic.css deleted file mode 100644 index dcae94623..000000000 --- a/docs/assets/classic.css +++ /dev/null @@ -1,266 +0,0 @@ -/* - * classic.css_t - * ~~~~~~~~~~~~~ - * - * Sphinx stylesheet -- classic theme. - * - * :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * - */ - -@import url("basic.css"); - -/* -- page layout ----------------------------------------------------------- */ - -html { - /* CSS hack for macOS's scrollbar (see #1125) */ - background-color: #FFFFFF; -} - -body { - font-family: sans-serif; - font-size: 100%; - background-color: #11303d; - color: #000; - margin: 0; - padding: 0; -} - -div.document { - background-color: #1c4e63; -} - -div.documentwrapper { - float: left; - width: 100%; -} - -div.bodywrapper { - margin: 0 0 0 230px; -} - -div.body { - background-color: #ffffff; - color: #000000; - padding: 0 20px 30px 20px; -} - -div.footer { - color: #ffffff; - width: 100%; - padding: 9px 0 9px 0; - text-align: center; - font-size: 75%; -} - -div.footer a { - color: #ffffff; - text-decoration: underline; -} - -div.related { - background-color: #133f52; - line-height: 30px; - color: #ffffff; -} - -div.related a { - color: #ffffff; -} - -div.sphinxsidebar { -} - -div.sphinxsidebar h3 { - font-family: 'Trebuchet MS', sans-serif; - color: #ffffff; - font-size: 1.4em; - font-weight: normal; - margin: 0; - padding: 0; -} - -div.sphinxsidebar h3 a { - color: #ffffff; -} - -div.sphinxsidebar h4 { - font-family: 'Trebuchet MS', sans-serif; - color: #ffffff; - font-size: 1.3em; - font-weight: normal; - margin: 5px 0 0 0; - padding: 0; -} - -div.sphinxsidebar p { - color: #ffffff; -} - -div.sphinxsidebar p.topless { - margin: 5px 10px 10px 10px; -} - -div.sphinxsidebar ul { - margin: 10px; - padding: 0; - color: #ffffff; -} - -div.sphinxsidebar a { - color: #98dbcc; -} - -div.sphinxsidebar input { - border: 1px solid #98dbcc; - font-family: sans-serif; - font-size: 1em; -} - - - -/* -- hyperlink styles ------------------------------------------------------ */ - -a { - color: #355f7c; - text-decoration: none; -} - -a:visited { - color: #355f7c; - text-decoration: none; -} - -a:hover { - text-decoration: underline; -} - - - -/* -- body styles ----------------------------------------------------------- */ - -div.body h1, -div.body h2, -div.body h3, -div.body h4, -div.body h5, -div.body h6 { - font-family: 'Trebuchet MS', sans-serif; - background-color: #f2f2f2; - font-weight: normal; - color: #20435c; - border-bottom: 1px solid #ccc; - margin: 20px -20px 10px -20px; - padding: 3px 0 3px 10px; -} - -div.body h1 { margin-top: 0; font-size: 200%; } -div.body h2 { font-size: 160%; } -div.body h3 { font-size: 140%; } -div.body h4 { font-size: 120%; } -div.body h5 { font-size: 110%; } -div.body h6 { font-size: 100%; } - -a.headerlink { - color: #c60f0f; - font-size: 0.8em; - padding: 0 4px 0 4px; - text-decoration: none; -} - -a.headerlink:hover { - background-color: #c60f0f; - color: white; -} - -div.body p, div.body dd, div.body li, div.body blockquote { - text-align: justify; - line-height: 130%; -} - -div.admonition p.admonition-title + p { - display: inline; -} - -div.admonition p { - margin-bottom: 5px; -} - -div.admonition pre { - margin-bottom: 5px; -} - -div.admonition ul, div.admonition ol { - margin-bottom: 5px; -} - -div.note { - background-color: #eee; - border: 1px solid #ccc; -} - -div.seealso { - background-color: #ffc; - border: 1px solid #ff6; -} - -div.topic { - background-color: #eee; -} - -div.warning { - background-color: #ffe4e4; - border: 1px solid #f66; -} - -p.admonition-title { - display: inline; -} - -p.admonition-title:after { - content: ":"; -} - -pre { - padding: 5px; - background-color: unset; - color: unset; - line-height: 120%; - border: 1px solid #ac9; - border-left: none; - border-right: none; -} - -code { - background-color: #ecf0f3; - padding: 0 1px 0 1px; - font-size: 0.95em; -} - -th, dl.field-list > dt { - background-color: #ede; -} - -.warning code { - background: #efc2c2; -} - -.note code { - background: #d6d6d6; -} - -.viewcode-back { - font-family: sans-serif; -} - -div.viewcode-block:target { - background-color: #f4debf; - border-top: 1px solid #ac9; - border-bottom: 1px solid #ac9; -} - -div.code-block-caption { - color: #efefef; - background-color: #1c4e63; -} \ No newline at end of file diff --git a/docs/assets/default.css b/docs/assets/default.css deleted file mode 100644 index b7aacd34c..000000000 --- a/docs/assets/default.css +++ /dev/null @@ -1,79 +0,0 @@ -a.headerlink { - display: none !important; -} - -h2 { - margin-top: -120px; - padding-top: 120px; -} - -.section-title { - font-size: 2rem; - line-height: 2.5rem; - letter-spacing: -1px; - font-weight: 700; - margin: 0 0 1rem; -} - -nav#api_nav .toctree-l1 { - margin-bottom: 1.5rem; -} - -nav#api_nav #api_sections ul { - list-style: none; - margin: 0; - padding: 0; -} - -nav#api_nav #api_sections ul li.toctree-l1>a { - color: #1264a3; - letter-spacing: 0; - font-size: .8rem; - font-weight: 800; - text-transform: uppercase; - border: none; - padding: 0; -} - -nav#api_nav #api_sections ul li.toctree-l2 { - margin: 0; - padding: 0; -} - -nav#api_nav #api_sections ul li.toctree-l2 a { - color: #1d1c1d; - text-transform: none; - font-weight: inherit; - padding: 0; - display: block; - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - font-size: 15px!important; - line-height:15px; - padding: 4px 8px; - border: 1px solid transparent; - border-radius: 4px; -} - -nav#api_nav #api_sections ul li.toctree-l2 a:hover { - cursor: pointer; - text-decoration: none; - background-color:#e8f5fa; - border-color:#dcf0fb; -} - -nav#api_nav #footer #footer_nav { - font-size: .9375rem; -} - -nav#api_nav #footer #footer_nav a { - border: none; - padding: 0; - color: #616061; -} - -nav#api_nav #footer #footer_nav a:hover { - text-decoration: none; - color: #1c1c1c; -} \ No newline at end of file diff --git a/docs/assets/docs.css b/docs/assets/docs.css deleted file mode 100644 index 12c715d28..000000000 --- a/docs/assets/docs.css +++ /dev/null @@ -1,34 +0,0 @@ -/* Updates body font */ -body { - font-family: Slack-Lato,appleLogo,sans-serif; -} - -/* Replaces old sidebar styled links */ -.sidebar_menu h5 { - font-size: 0.8rem; - font-weight: 800; - margin-bottom: 3px; -} - -/* Aligns footer navigation to the left of the sidebar */ -.footer_nav { - margin: 0 !important; -} - -/* Styles the signature all nice and pretty <3 */ -#footer_signature { - color:#e01e5a; - font-size:.9rem; - margin-top: 10px; -} - -/* Fixes link hover state */ -a:hover { - text-decoration: underline; -} - -/* Makes footer consistent */ -footer { - background-color: transparent; - border: 0; -} \ No newline at end of file diff --git a/docs/assets/doctools.js b/docs/assets/doctools.js deleted file mode 100644 index 144884ea6..000000000 --- a/docs/assets/doctools.js +++ /dev/null @@ -1,316 +0,0 @@ -/* - * doctools.js - * ~~~~~~~~~~~ - * - * Sphinx JavaScript utilities for all documentation. - * - * :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * - */ - -/** - * select a different prefix for underscore - */ -$u = _.noConflict(); - -/** - * make the code below compatible with browsers without - * an installed firebug like debugger -if (!window.console || !console.firebug) { - var names = ["log", "debug", "info", "warn", "error", "assert", "dir", - "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", - "profile", "profileEnd"]; - window.console = {}; - for (var i = 0; i < names.length; ++i) - window.console[names[i]] = function() {}; -} - */ - -/** - * small helper function to urldecode strings - */ -jQuery.urldecode = function(x) { - return decodeURIComponent(x).replace(/\+/g, ' '); -}; - -/** - * small helper function to urlencode strings - */ -jQuery.urlencode = encodeURIComponent; - -/** - * This function returns the parsed url parameters of the - * current request. Multiple values per key are supported, - * it will always return arrays of strings for the value parts. - */ -jQuery.getQueryParameters = function(s) { - if (typeof s === 'undefined') - s = document.location.search; - var parts = s.substr(s.indexOf('?') + 1).split('&'); - var result = {}; - for (var i = 0; i < parts.length; i++) { - var tmp = parts[i].split('=', 2); - var key = jQuery.urldecode(tmp[0]); - var value = jQuery.urldecode(tmp[1]); - if (key in result) - result[key].push(value); - else - result[key] = [value]; - } - return result; -}; - -/** - * highlight a given string on a jquery object by wrapping it in - * span elements with the given class name. - */ -jQuery.fn.highlightText = function(text, className) { - function highlight(node, addItems) { - if (node.nodeType === 3) { - var val = node.nodeValue; - var pos = val.toLowerCase().indexOf(text); - if (pos >= 0 && - !jQuery(node.parentNode).hasClass(className) && - !jQuery(node.parentNode).hasClass("nohighlight")) { - var span; - var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); - if (isInSVG) { - span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); - } else { - span = document.createElement("span"); - span.className = className; - } - span.appendChild(document.createTextNode(val.substr(pos, text.length))); - node.parentNode.insertBefore(span, node.parentNode.insertBefore( - document.createTextNode(val.substr(pos + text.length)), - node.nextSibling)); - node.nodeValue = val.substr(0, pos); - if (isInSVG) { - var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); - var bbox = node.parentElement.getBBox(); - rect.x.baseVal.value = bbox.x; - rect.y.baseVal.value = bbox.y; - rect.width.baseVal.value = bbox.width; - rect.height.baseVal.value = bbox.height; - rect.setAttribute('class', className); - addItems.push({ - "parent": node.parentNode, - "target": rect}); - } - } - } - else if (!jQuery(node).is("button, select, textarea")) { - jQuery.each(node.childNodes, function() { - highlight(this, addItems); - }); - } - } - var addItems = []; - var result = this.each(function() { - highlight(this, addItems); - }); - for (var i = 0; i < addItems.length; ++i) { - jQuery(addItems[i].parent).before(addItems[i].target); - } - return result; -}; - -/* - * backward compatibility for jQuery.browser - * This will be supported until firefox bug is fixed. - */ -if (!jQuery.browser) { - jQuery.uaMatch = function(ua) { - ua = ua.toLowerCase(); - - var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || - /(webkit)[ \/]([\w.]+)/.exec(ua) || - /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || - /(msie) ([\w.]+)/.exec(ua) || - ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || - []; - - return { - browser: match[ 1 ] || "", - version: match[ 2 ] || "0" - }; - }; - jQuery.browser = {}; - jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; -} - -/** - * Small JavaScript module for the documentation. - */ -var Documentation = { - - init : function() { - this.fixFirefoxAnchorBug(); - this.highlightSearchWords(); - this.initIndexTable(); - if (DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) { - this.initOnKeyListeners(); - } - }, - - /** - * i18n support - */ - TRANSLATIONS : {}, - PLURAL_EXPR : function(n) { return n === 1 ? 0 : 1; }, - LOCALE : 'unknown', - - // gettext and ngettext don't access this so that the functions - // can safely bound to a different name (_ = Documentation.gettext) - gettext : function(string) { - var translated = Documentation.TRANSLATIONS[string]; - if (typeof translated === 'undefined') - return string; - return (typeof translated === 'string') ? translated : translated[0]; - }, - - ngettext : function(singular, plural, n) { - var translated = Documentation.TRANSLATIONS[singular]; - if (typeof translated === 'undefined') - return (n == 1) ? singular : plural; - return translated[Documentation.PLURALEXPR(n)]; - }, - - addTranslations : function(catalog) { - for (var key in catalog.messages) - this.TRANSLATIONS[key] = catalog.messages[key]; - this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); - this.LOCALE = catalog.locale; - }, - - /** - * add context elements like header anchor links - */ - addContextElements : function() { - $('div[id] > :header:first').each(function() { - $('\u00B6'). - attr('href', '#' + this.id). - attr('title', _('Permalink to this headline')). - appendTo(this); - }); - $('dt[id]').each(function() { - $('\u00B6'). - attr('href', '#' + this.id). - attr('title', _('Permalink to this definition')). - appendTo(this); - }); - }, - - /** - * workaround a firefox stupidity - * see: https://bugzilla.mozilla.org/show_bug.cgi?id=645075 - */ - fixFirefoxAnchorBug : function() { - if (document.location.hash && $.browser.mozilla) - window.setTimeout(function() { - document.location.href += ''; - }, 10); - }, - - /** - * highlight the search words provided in the url in the text - */ - highlightSearchWords : function() { - var params = $.getQueryParameters(); - var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; - if (terms.length) { - var body = $('div.body'); - if (!body.length) { - body = $('body'); - } - window.setTimeout(function() { - $.each(terms, function() { - body.highlightText(this.toLowerCase(), 'highlighted'); - }); - }, 10); - $('') - .appendTo($('#searchbox')); - } - }, - - /** - * init the domain index toggle buttons - */ - initIndexTable : function() { - var togglers = $('img.toggler').click(function() { - var src = $(this).attr('src'); - var idnum = $(this).attr('id').substr(7); - $('tr.cg-' + idnum).toggle(); - if (src.substr(-9) === 'minus.png') - $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); - else - $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); - }).css('display', ''); - if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { - togglers.click(); - } - }, - - /** - * helper function to hide the search marks again - */ - hideSearchWords : function() { - $('#searchbox .highlight-link').fadeOut(300); - $('span.highlighted').removeClass('highlighted'); - }, - - /** - * make the url absolute - */ - makeURL : function(relativeURL) { - return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; - }, - - /** - * get the current relative url - */ - getCurrentURL : function() { - var path = document.location.pathname; - var parts = path.split(/\//); - $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { - if (this === '..') - parts.pop(); - }); - var url = parts.join('/'); - return path.substring(url.lastIndexOf('/') + 1, path.length - 1); - }, - - initOnKeyListeners: function() { - $(document).keydown(function(event) { - var activeElementType = document.activeElement.tagName; - // don't navigate when in search box, textarea, dropdown or button - if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT' - && activeElementType !== 'BUTTON' && !event.altKey && !event.ctrlKey && !event.metaKey - && !event.shiftKey) { - switch (event.keyCode) { - case 37: // left - var prevHref = $('link[rel="prev"]').prop('href'); - if (prevHref) { - window.location.href = prevHref; - return false; - } - case 39: // right - var nextHref = $('link[rel="next"]').prop('href'); - if (nextHref) { - window.location.href = nextHref; - return false; - } - } - } - }); - } -}; - -// quick alias for translations -_ = Documentation.gettext; - -$(document).ready(function() { - Documentation.init(); -}); diff --git a/docs/assets/documentation_options.js b/docs/assets/documentation_options.js deleted file mode 100644 index 9fd31e439..000000000 --- a/docs/assets/documentation_options.js +++ /dev/null @@ -1,12 +0,0 @@ -var DOCUMENTATION_OPTIONS = { - URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), - VERSION: '1.0.1', - LANGUAGE: 'None', - COLLAPSE_INDEX: false, - BUILDER: 'html', - FILE_SUFFIX: '.html', - LINK_SUFFIX: '.html', - HAS_SOURCE: true, - SOURCELINK_SUFFIX: '.txt', - NAVIGATION_WITH_KEYS: false -}; \ No newline at end of file diff --git a/docs/assets/file.png b/docs/assets/file.png deleted file mode 100644 index a858a410e..000000000 Binary files a/docs/assets/file.png and /dev/null differ diff --git a/docs/assets/jquery-3.5.1.js b/docs/assets/jquery-3.5.1.js deleted file mode 100644 index 50937333b..000000000 --- a/docs/assets/jquery-3.5.1.js +++ /dev/null @@ -1,10872 +0,0 @@ -/*! - * jQuery JavaScript Library v3.5.1 - * https://jquery.com/ - * - * Includes Sizzle.js - * https://sizzlejs.com/ - * - * Copyright JS Foundation and other contributors - * Released under the MIT license - * https://jquery.org/license - * - * Date: 2020-05-04T22:49Z - */ -( function( global, factory ) { - - "use strict"; - - if ( typeof module === "object" && typeof module.exports === "object" ) { - - // For CommonJS and CommonJS-like environments where a proper `window` - // is present, execute the factory and get jQuery. - // For environments that do not have a `window` with a `document` - // (such as Node.js), expose a factory as module.exports. - // This accentuates the need for the creation of a real `window`. - // e.g. var jQuery = require("jquery")(window); - // See ticket #14549 for more info. - module.exports = global.document ? - factory( global, true ) : - function( w ) { - if ( !w.document ) { - throw new Error( "jQuery requires a window with a document" ); - } - return factory( w ); - }; - } else { - factory( global ); - } - -// Pass this if window is not defined yet -} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { - -// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 -// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode -// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common -// enough that all such attempts are guarded in a try block. -"use strict"; - -var arr = []; - -var getProto = Object.getPrototypeOf; - -var slice = arr.slice; - -var flat = arr.flat ? function( array ) { - return arr.flat.call( array ); -} : function( array ) { - return arr.concat.apply( [], array ); -}; - - -var push = arr.push; - -var indexOf = arr.indexOf; - -var class2type = {}; - -var toString = class2type.toString; - -var hasOwn = class2type.hasOwnProperty; - -var fnToString = hasOwn.toString; - -var ObjectFunctionString = fnToString.call( Object ); - -var support = {}; - -var isFunction = function isFunction( obj ) { - - // Support: Chrome <=57, Firefox <=52 - // In some browsers, typeof returns "function" for HTML elements - // (i.e., `typeof document.createElement( "object" ) === "function"`). - // We don't want to classify *any* DOM node as a function. - return typeof obj === "function" && typeof obj.nodeType !== "number"; - }; - - -var isWindow = function isWindow( obj ) { - return obj != null && obj === obj.window; - }; - - -var document = window.document; - - - - var preservedScriptAttributes = { - type: true, - src: true, - nonce: true, - noModule: true - }; - - function DOMEval( code, node, doc ) { - doc = doc || document; - - var i, val, - script = doc.createElement( "script" ); - - script.text = code; - if ( node ) { - for ( i in preservedScriptAttributes ) { - - // Support: Firefox 64+, Edge 18+ - // Some browsers don't support the "nonce" property on scripts. - // On the other hand, just using `getAttribute` is not enough as - // the `nonce` attribute is reset to an empty string whenever it - // becomes browsing-context connected. - // See https://github.com/whatwg/html/issues/2369 - // See https://html.spec.whatwg.org/#nonce-attributes - // The `node.getAttribute` check was added for the sake of - // `jQuery.globalEval` so that it can fake a nonce-containing node - // via an object. - val = node[ i ] || node.getAttribute && node.getAttribute( i ); - if ( val ) { - script.setAttribute( i, val ); - } - } - } - doc.head.appendChild( script ).parentNode.removeChild( script ); - } - - -function toType( obj ) { - if ( obj == null ) { - return obj + ""; - } - - // Support: Android <=2.3 only (functionish RegExp) - return typeof obj === "object" || typeof obj === "function" ? - class2type[ toString.call( obj ) ] || "object" : - typeof obj; -} -/* global Symbol */ -// Defining this global in .eslintrc.json would create a danger of using the global -// unguarded in another place, it seems safer to define global only for this module - - - -var - version = "3.5.1", - - // Define a local copy of jQuery - jQuery = function( selector, context ) { - - // The jQuery object is actually just the init constructor 'enhanced' - // Need init if jQuery is called (just allow error to be thrown if not included) - return new jQuery.fn.init( selector, context ); - }; - -jQuery.fn = jQuery.prototype = { - - // The current version of jQuery being used - jquery: version, - - constructor: jQuery, - - // The default length of a jQuery object is 0 - length: 0, - - toArray: function() { - return slice.call( this ); - }, - - // Get the Nth element in the matched element set OR - // Get the whole matched element set as a clean array - get: function( num ) { - - // Return all the elements in a clean array - if ( num == null ) { - return slice.call( this ); - } - - // Return just the one element from the set - return num < 0 ? this[ num + this.length ] : this[ num ]; - }, - - // Take an array of elements and push it onto the stack - // (returning the new matched element set) - pushStack: function( elems ) { - - // Build a new jQuery matched element set - var ret = jQuery.merge( this.constructor(), elems ); - - // Add the old object onto the stack (as a reference) - ret.prevObject = this; - - // Return the newly-formed element set - return ret; - }, - - // Execute a callback for every element in the matched set. - each: function( callback ) { - return jQuery.each( this, callback ); - }, - - map: function( callback ) { - return this.pushStack( jQuery.map( this, function( elem, i ) { - return callback.call( elem, i, elem ); - } ) ); - }, - - slice: function() { - return this.pushStack( slice.apply( this, arguments ) ); - }, - - first: function() { - return this.eq( 0 ); - }, - - last: function() { - return this.eq( -1 ); - }, - - even: function() { - return this.pushStack( jQuery.grep( this, function( _elem, i ) { - return ( i + 1 ) % 2; - } ) ); - }, - - odd: function() { - return this.pushStack( jQuery.grep( this, function( _elem, i ) { - return i % 2; - } ) ); - }, - - eq: function( i ) { - var len = this.length, - j = +i + ( i < 0 ? len : 0 ); - return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); - }, - - end: function() { - return this.prevObject || this.constructor(); - }, - - // For internal use only. - // Behaves like an Array's method, not like a jQuery method. - push: push, - sort: arr.sort, - splice: arr.splice -}; - -jQuery.extend = jQuery.fn.extend = function() { - var options, name, src, copy, copyIsArray, clone, - target = arguments[ 0 ] || {}, - i = 1, - length = arguments.length, - deep = false; - - // Handle a deep copy situation - if ( typeof target === "boolean" ) { - deep = target; - - // Skip the boolean and the target - target = arguments[ i ] || {}; - i++; - } - - // Handle case when target is a string or something (possible in deep copy) - if ( typeof target !== "object" && !isFunction( target ) ) { - target = {}; - } - - // Extend jQuery itself if only one argument is passed - if ( i === length ) { - target = this; - i--; - } - - for ( ; i < length; i++ ) { - - // Only deal with non-null/undefined values - if ( ( options = arguments[ i ] ) != null ) { - - // Extend the base object - for ( name in options ) { - copy = options[ name ]; - - // Prevent Object.prototype pollution - // Prevent never-ending loop - if ( name === "__proto__" || target === copy ) { - continue; - } - - // Recurse if we're merging plain objects or arrays - if ( deep && copy && ( jQuery.isPlainObject( copy ) || - ( copyIsArray = Array.isArray( copy ) ) ) ) { - src = target[ name ]; - - // Ensure proper type for the source value - if ( copyIsArray && !Array.isArray( src ) ) { - clone = []; - } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) { - clone = {}; - } else { - clone = src; - } - copyIsArray = false; - - // Never move original objects, clone them - target[ name ] = jQuery.extend( deep, clone, copy ); - - // Don't bring in undefined values - } else if ( copy !== undefined ) { - target[ name ] = copy; - } - } - } - } - - // Return the modified object - return target; -}; - -jQuery.extend( { - - // Unique for each copy of jQuery on the page - expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), - - // Assume jQuery is ready without the ready module - isReady: true, - - error: function( msg ) { - throw new Error( msg ); - }, - - noop: function() {}, - - isPlainObject: function( obj ) { - var proto, Ctor; - - // Detect obvious negatives - // Use toString instead of jQuery.type to catch host objects - if ( !obj || toString.call( obj ) !== "[object Object]" ) { - return false; - } - - proto = getProto( obj ); - - // Objects with no prototype (e.g., `Object.create( null )`) are plain - if ( !proto ) { - return true; - } - - // Objects with prototype are plain iff they were constructed by a global Object function - Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; - return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; - }, - - isEmptyObject: function( obj ) { - var name; - - for ( name in obj ) { - return false; - } - return true; - }, - - // Evaluates a script in a provided context; falls back to the global one - // if not specified. - globalEval: function( code, options, doc ) { - DOMEval( code, { nonce: options && options.nonce }, doc ); - }, - - each: function( obj, callback ) { - var length, i = 0; - - if ( isArrayLike( obj ) ) { - length = obj.length; - for ( ; i < length; i++ ) { - if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { - break; - } - } - } else { - for ( i in obj ) { - if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { - break; - } - } - } - - return obj; - }, - - // results is for internal usage only - makeArray: function( arr, results ) { - var ret = results || []; - - if ( arr != null ) { - if ( isArrayLike( Object( arr ) ) ) { - jQuery.merge( ret, - typeof arr === "string" ? - [ arr ] : arr - ); - } else { - push.call( ret, arr ); - } - } - - return ret; - }, - - inArray: function( elem, arr, i ) { - return arr == null ? -1 : indexOf.call( arr, elem, i ); - }, - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - merge: function( first, second ) { - var len = +second.length, - j = 0, - i = first.length; - - for ( ; j < len; j++ ) { - first[ i++ ] = second[ j ]; - } - - first.length = i; - - return first; - }, - - grep: function( elems, callback, invert ) { - var callbackInverse, - matches = [], - i = 0, - length = elems.length, - callbackExpect = !invert; - - // Go through the array, only saving the items - // that pass the validator function - for ( ; i < length; i++ ) { - callbackInverse = !callback( elems[ i ], i ); - if ( callbackInverse !== callbackExpect ) { - matches.push( elems[ i ] ); - } - } - - return matches; - }, - - // arg is for internal usage only - map: function( elems, callback, arg ) { - var length, value, - i = 0, - ret = []; - - // Go through the array, translating each of the items to their new values - if ( isArrayLike( elems ) ) { - length = elems.length; - for ( ; i < length; i++ ) { - value = callback( elems[ i ], i, arg ); - - if ( value != null ) { - ret.push( value ); - } - } - - // Go through every key on the object, - } else { - for ( i in elems ) { - value = callback( elems[ i ], i, arg ); - - if ( value != null ) { - ret.push( value ); - } - } - } - - // Flatten any nested arrays - return flat( ret ); - }, - - // A global GUID counter for objects - guid: 1, - - // jQuery.support is not used in Core but other projects attach their - // properties to it so it needs to exist. - support: support -} ); - -if ( typeof Symbol === "function" ) { - jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; -} - -// Populate the class2type map -jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), -function( _i, name ) { - class2type[ "[object " + name + "]" ] = name.toLowerCase(); -} ); - -function isArrayLike( obj ) { - - // Support: real iOS 8.2 only (not reproducible in simulator) - // `in` check used to prevent JIT error (gh-2145) - // hasOwn isn't used here due to false negatives - // regarding Nodelist length in IE - var length = !!obj && "length" in obj && obj.length, - type = toType( obj ); - - if ( isFunction( obj ) || isWindow( obj ) ) { - return false; - } - - return type === "array" || length === 0 || - typeof length === "number" && length > 0 && ( length - 1 ) in obj; -} -var Sizzle = -/*! - * Sizzle CSS Selector Engine v2.3.5 - * https://sizzlejs.com/ - * - * Copyright JS Foundation and other contributors - * Released under the MIT license - * https://js.foundation/ - * - * Date: 2020-03-14 - */ -( function( window ) { -var i, - support, - Expr, - getText, - isXML, - tokenize, - compile, - select, - outermostContext, - sortInput, - hasDuplicate, - - // Local document vars - setDocument, - document, - docElem, - documentIsHTML, - rbuggyQSA, - rbuggyMatches, - matches, - contains, - - // Instance-specific data - expando = "sizzle" + 1 * new Date(), - preferredDoc = window.document, - dirruns = 0, - done = 0, - classCache = createCache(), - tokenCache = createCache(), - compilerCache = createCache(), - nonnativeSelectorCache = createCache(), - sortOrder = function( a, b ) { - if ( a === b ) { - hasDuplicate = true; - } - return 0; - }, - - // Instance methods - hasOwn = ( {} ).hasOwnProperty, - arr = [], - pop = arr.pop, - pushNative = arr.push, - push = arr.push, - slice = arr.slice, - - // Use a stripped-down indexOf as it's faster than native - // https://jsperf.com/thor-indexof-vs-for/5 - indexOf = function( list, elem ) { - var i = 0, - len = list.length; - for ( ; i < len; i++ ) { - if ( list[ i ] === elem ) { - return i; - } - } - return -1; - }, - - booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|" + - "ismap|loop|multiple|open|readonly|required|scoped", - - // Regular expressions - - // http://www.w3.org/TR/css3-selectors/#whitespace - whitespace = "[\\x20\\t\\r\\n\\f]", - - // https://www.w3.org/TR/css-syntax-3/#ident-token-diagram - identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace + - "?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+", - - // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors - attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + - - // Operator (capture 2) - "*([*^$|!~]?=)" + whitespace + - - // "Attribute values must be CSS identifiers [capture 5] - // or strings [capture 3 or capture 4]" - "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + - whitespace + "*\\]", - - pseudos = ":(" + identifier + ")(?:\\((" + - - // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: - // 1. quoted (capture 3; capture 4 or capture 5) - "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + - - // 2. simple (capture 6) - "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + - - // 3. anything else (capture 2) - ".*" + - ")\\)|)", - - // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter - rwhitespace = new RegExp( whitespace + "+", "g" ), - rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + - whitespace + "+$", "g" ), - - rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), - rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + - "*" ), - rdescend = new RegExp( whitespace + "|>" ), - - rpseudo = new RegExp( pseudos ), - ridentifier = new RegExp( "^" + identifier + "$" ), - - matchExpr = { - "ID": new RegExp( "^#(" + identifier + ")" ), - "CLASS": new RegExp( "^\\.(" + identifier + ")" ), - "TAG": new RegExp( "^(" + identifier + "|[*])" ), - "ATTR": new RegExp( "^" + attributes ), - "PSEUDO": new RegExp( "^" + pseudos ), - "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + - whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + - whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), - "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), - - // For use in libraries implementing .is() - // We use this for POS matching in `select` - "needsContext": new RegExp( "^" + whitespace + - "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + - "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) - }, - - rhtml = /HTML$/i, - rinputs = /^(?:input|select|textarea|button)$/i, - rheader = /^h\d$/i, - - rnative = /^[^{]+\{\s*\[native \w/, - - // Easily-parseable/retrievable ID or TAG or CLASS selectors - rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, - - rsibling = /[+~]/, - - // CSS escapes - // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters - runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace + "?|\\\\([^\\r\\n\\f])", "g" ), - funescape = function( escape, nonHex ) { - var high = "0x" + escape.slice( 1 ) - 0x10000; - - return nonHex ? - - // Strip the backslash prefix from a non-hex escape sequence - nonHex : - - // Replace a hexadecimal escape sequence with the encoded Unicode code point - // Support: IE <=11+ - // For values outside the Basic Multilingual Plane (BMP), manually construct a - // surrogate pair - high < 0 ? - String.fromCharCode( high + 0x10000 ) : - String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); - }, - - // CSS string/identifier serialization - // https://drafts.csswg.org/cssom/#common-serializing-idioms - rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, - fcssescape = function( ch, asCodePoint ) { - if ( asCodePoint ) { - - // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER - if ( ch === "\0" ) { - return "\uFFFD"; - } - - // Control characters and (dependent upon position) numbers get escaped as code points - return ch.slice( 0, -1 ) + "\\" + - ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; - } - - // Other potentially-special ASCII characters get backslash-escaped - return "\\" + ch; - }, - - // Used for iframes - // See setDocument() - // Removing the function wrapper causes a "Permission Denied" - // error in IE - unloadHandler = function() { - setDocument(); - }, - - inDisabledFieldset = addCombinator( - function( elem ) { - return elem.disabled === true && elem.nodeName.toLowerCase() === "fieldset"; - }, - { dir: "parentNode", next: "legend" } - ); - -// Optimize for push.apply( _, NodeList ) -try { - push.apply( - ( arr = slice.call( preferredDoc.childNodes ) ), - preferredDoc.childNodes - ); - - // Support: Android<4.0 - // Detect silently failing push.apply - // eslint-disable-next-line no-unused-expressions - arr[ preferredDoc.childNodes.length ].nodeType; -} catch ( e ) { - push = { apply: arr.length ? - - // Leverage slice if possible - function( target, els ) { - pushNative.apply( target, slice.call( els ) ); - } : - - // Support: IE<9 - // Otherwise append directly - function( target, els ) { - var j = target.length, - i = 0; - - // Can't trust NodeList.length - while ( ( target[ j++ ] = els[ i++ ] ) ) {} - target.length = j - 1; - } - }; -} - -function Sizzle( selector, context, results, seed ) { - var m, i, elem, nid, match, groups, newSelector, - newContext = context && context.ownerDocument, - - // nodeType defaults to 9, since context defaults to document - nodeType = context ? context.nodeType : 9; - - results = results || []; - - // Return early from calls with invalid selector or context - if ( typeof selector !== "string" || !selector || - nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { - - return results; - } - - // Try to shortcut find operations (as opposed to filters) in HTML documents - if ( !seed ) { - setDocument( context ); - context = context || document; - - if ( documentIsHTML ) { - - // If the selector is sufficiently simple, try using a "get*By*" DOM method - // (excepting DocumentFragment context, where the methods don't exist) - if ( nodeType !== 11 && ( match = rquickExpr.exec( selector ) ) ) { - - // ID selector - if ( ( m = match[ 1 ] ) ) { - - // Document context - if ( nodeType === 9 ) { - if ( ( elem = context.getElementById( m ) ) ) { - - // Support: IE, Opera, Webkit - // TODO: identify versions - // getElementById can match elements by name instead of ID - if ( elem.id === m ) { - results.push( elem ); - return results; - } - } else { - return results; - } - - // Element context - } else { - - // Support: IE, Opera, Webkit - // TODO: identify versions - // getElementById can match elements by name instead of ID - if ( newContext && ( elem = newContext.getElementById( m ) ) && - contains( context, elem ) && - elem.id === m ) { - - results.push( elem ); - return results; - } - } - - // Type selector - } else if ( match[ 2 ] ) { - push.apply( results, context.getElementsByTagName( selector ) ); - return results; - - // Class selector - } else if ( ( m = match[ 3 ] ) && support.getElementsByClassName && - context.getElementsByClassName ) { - - push.apply( results, context.getElementsByClassName( m ) ); - return results; - } - } - - // Take advantage of querySelectorAll - if ( support.qsa && - !nonnativeSelectorCache[ selector + " " ] && - ( !rbuggyQSA || !rbuggyQSA.test( selector ) ) && - - // Support: IE 8 only - // Exclude object elements - ( nodeType !== 1 || context.nodeName.toLowerCase() !== "object" ) ) { - - newSelector = selector; - newContext = context; - - // qSA considers elements outside a scoping root when evaluating child or - // descendant combinators, which is not what we want. - // In such cases, we work around the behavior by prefixing every selector in the - // list with an ID selector referencing the scope context. - // The technique has to be used as well when a leading combinator is used - // as such selectors are not recognized by querySelectorAll. - // Thanks to Andrew Dupont for this technique. - if ( nodeType === 1 && - ( rdescend.test( selector ) || rcombinators.test( selector ) ) ) { - - // Expand context for sibling selectors - newContext = rsibling.test( selector ) && testContext( context.parentNode ) || - context; - - // We can use :scope instead of the ID hack if the browser - // supports it & if we're not changing the context. - if ( newContext !== context || !support.scope ) { - - // Capture the context ID, setting it first if necessary - if ( ( nid = context.getAttribute( "id" ) ) ) { - nid = nid.replace( rcssescape, fcssescape ); - } else { - context.setAttribute( "id", ( nid = expando ) ); - } - } - - // Prefix every selector in the list - groups = tokenize( selector ); - i = groups.length; - while ( i-- ) { - groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " + - toSelector( groups[ i ] ); - } - newSelector = groups.join( "," ); - } - - try { - push.apply( results, - newContext.querySelectorAll( newSelector ) - ); - return results; - } catch ( qsaError ) { - nonnativeSelectorCache( selector, true ); - } finally { - if ( nid === expando ) { - context.removeAttribute( "id" ); - } - } - } - } - } - - // All others - return select( selector.replace( rtrim, "$1" ), context, results, seed ); -} - -/** - * Create key-value caches of limited size - * @returns {function(string, object)} Returns the Object data after storing it on itself with - * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) - * deleting the oldest entry - */ -function createCache() { - var keys = []; - - function cache( key, value ) { - - // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) - if ( keys.push( key + " " ) > Expr.cacheLength ) { - - // Only keep the most recent entries - delete cache[ keys.shift() ]; - } - return ( cache[ key + " " ] = value ); - } - return cache; -} - -/** - * Mark a function for special use by Sizzle - * @param {Function} fn The function to mark - */ -function markFunction( fn ) { - fn[ expando ] = true; - return fn; -} - -/** - * Support testing using an element - * @param {Function} fn Passed the created element and returns a boolean result - */ -function assert( fn ) { - var el = document.createElement( "fieldset" ); - - try { - return !!fn( el ); - } catch ( e ) { - return false; - } finally { - - // Remove from its parent by default - if ( el.parentNode ) { - el.parentNode.removeChild( el ); - } - - // release memory in IE - el = null; - } -} - -/** - * Adds the same handler for all of the specified attrs - * @param {String} attrs Pipe-separated list of attributes - * @param {Function} handler The method that will be applied - */ -function addHandle( attrs, handler ) { - var arr = attrs.split( "|" ), - i = arr.length; - - while ( i-- ) { - Expr.attrHandle[ arr[ i ] ] = handler; - } -} - -/** - * Checks document order of two siblings - * @param {Element} a - * @param {Element} b - * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b - */ -function siblingCheck( a, b ) { - var cur = b && a, - diff = cur && a.nodeType === 1 && b.nodeType === 1 && - a.sourceIndex - b.sourceIndex; - - // Use IE sourceIndex if available on both nodes - if ( diff ) { - return diff; - } - - // Check if b follows a - if ( cur ) { - while ( ( cur = cur.nextSibling ) ) { - if ( cur === b ) { - return -1; - } - } - } - - return a ? 1 : -1; -} - -/** - * Returns a function to use in pseudos for input types - * @param {String} type - */ -function createInputPseudo( type ) { - return function( elem ) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && elem.type === type; - }; -} - -/** - * Returns a function to use in pseudos for buttons - * @param {String} type - */ -function createButtonPseudo( type ) { - return function( elem ) { - var name = elem.nodeName.toLowerCase(); - return ( name === "input" || name === "button" ) && elem.type === type; - }; -} - -/** - * Returns a function to use in pseudos for :enabled/:disabled - * @param {Boolean} disabled true for :disabled; false for :enabled - */ -function createDisabledPseudo( disabled ) { - - // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable - return function( elem ) { - - // Only certain elements can match :enabled or :disabled - // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled - // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled - if ( "form" in elem ) { - - // Check for inherited disabledness on relevant non-disabled elements: - // * listed form-associated elements in a disabled fieldset - // https://html.spec.whatwg.org/multipage/forms.html#category-listed - // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled - // * option elements in a disabled optgroup - // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled - // All such elements have a "form" property. - if ( elem.parentNode && elem.disabled === false ) { - - // Option elements defer to a parent optgroup if present - if ( "label" in elem ) { - if ( "label" in elem.parentNode ) { - return elem.parentNode.disabled === disabled; - } else { - return elem.disabled === disabled; - } - } - - // Support: IE 6 - 11 - // Use the isDisabled shortcut property to check for disabled fieldset ancestors - return elem.isDisabled === disabled || - - // Where there is no isDisabled, check manually - /* jshint -W018 */ - elem.isDisabled !== !disabled && - inDisabledFieldset( elem ) === disabled; - } - - return elem.disabled === disabled; - - // Try to winnow out elements that can't be disabled before trusting the disabled property. - // Some victims get caught in our net (label, legend, menu, track), but it shouldn't - // even exist on them, let alone have a boolean value. - } else if ( "label" in elem ) { - return elem.disabled === disabled; - } - - // Remaining elements are neither :enabled nor :disabled - return false; - }; -} - -/** - * Returns a function to use in pseudos for positionals - * @param {Function} fn - */ -function createPositionalPseudo( fn ) { - return markFunction( function( argument ) { - argument = +argument; - return markFunction( function( seed, matches ) { - var j, - matchIndexes = fn( [], seed.length, argument ), - i = matchIndexes.length; - - // Match elements found at the specified indexes - while ( i-- ) { - if ( seed[ ( j = matchIndexes[ i ] ) ] ) { - seed[ j ] = !( matches[ j ] = seed[ j ] ); - } - } - } ); - } ); -} - -/** - * Checks a node for validity as a Sizzle context - * @param {Element|Object=} context - * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value - */ -function testContext( context ) { - return context && typeof context.getElementsByTagName !== "undefined" && context; -} - -// Expose support vars for convenience -support = Sizzle.support = {}; - -/** - * Detects XML nodes - * @param {Element|Object} elem An element or a document - * @returns {Boolean} True iff elem is a non-HTML XML node - */ -isXML = Sizzle.isXML = function( elem ) { - var namespace = elem.namespaceURI, - docElem = ( elem.ownerDocument || elem ).documentElement; - - // Support: IE <=8 - // Assume HTML when documentElement doesn't yet exist, such as inside loading iframes - // https://bugs.jquery.com/ticket/4833 - return !rhtml.test( namespace || docElem && docElem.nodeName || "HTML" ); -}; - -/** - * Sets document-related variables once based on the current document - * @param {Element|Object} [doc] An element or document object to use to set the document - * @returns {Object} Returns the current document - */ -setDocument = Sizzle.setDocument = function( node ) { - var hasCompare, subWindow, - doc = node ? node.ownerDocument || node : preferredDoc; - - // Return early if doc is invalid or already selected - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( doc == document || doc.nodeType !== 9 || !doc.documentElement ) { - return document; - } - - // Update global variables - document = doc; - docElem = document.documentElement; - documentIsHTML = !isXML( document ); - - // Support: IE 9 - 11+, Edge 12 - 18+ - // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( preferredDoc != document && - ( subWindow = document.defaultView ) && subWindow.top !== subWindow ) { - - // Support: IE 11, Edge - if ( subWindow.addEventListener ) { - subWindow.addEventListener( "unload", unloadHandler, false ); - - // Support: IE 9 - 10 only - } else if ( subWindow.attachEvent ) { - subWindow.attachEvent( "onunload", unloadHandler ); - } - } - - // Support: IE 8 - 11+, Edge 12 - 18+, Chrome <=16 - 25 only, Firefox <=3.6 - 31 only, - // Safari 4 - 5 only, Opera <=11.6 - 12.x only - // IE/Edge & older browsers don't support the :scope pseudo-class. - // Support: Safari 6.0 only - // Safari 6.0 supports :scope but it's an alias of :root there. - support.scope = assert( function( el ) { - docElem.appendChild( el ).appendChild( document.createElement( "div" ) ); - return typeof el.querySelectorAll !== "undefined" && - !el.querySelectorAll( ":scope fieldset div" ).length; - } ); - - /* Attributes - ---------------------------------------------------------------------- */ - - // Support: IE<8 - // Verify that getAttribute really returns attributes and not properties - // (excepting IE8 booleans) - support.attributes = assert( function( el ) { - el.className = "i"; - return !el.getAttribute( "className" ); - } ); - - /* getElement(s)By* - ---------------------------------------------------------------------- */ - - // Check if getElementsByTagName("*") returns only elements - support.getElementsByTagName = assert( function( el ) { - el.appendChild( document.createComment( "" ) ); - return !el.getElementsByTagName( "*" ).length; - } ); - - // Support: IE<9 - support.getElementsByClassName = rnative.test( document.getElementsByClassName ); - - // Support: IE<10 - // Check if getElementById returns elements by name - // The broken getElementById methods don't pick up programmatically-set names, - // so use a roundabout getElementsByName test - support.getById = assert( function( el ) { - docElem.appendChild( el ).id = expando; - return !document.getElementsByName || !document.getElementsByName( expando ).length; - } ); - - // ID filter and find - if ( support.getById ) { - Expr.filter[ "ID" ] = function( id ) { - var attrId = id.replace( runescape, funescape ); - return function( elem ) { - return elem.getAttribute( "id" ) === attrId; - }; - }; - Expr.find[ "ID" ] = function( id, context ) { - if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { - var elem = context.getElementById( id ); - return elem ? [ elem ] : []; - } - }; - } else { - Expr.filter[ "ID" ] = function( id ) { - var attrId = id.replace( runescape, funescape ); - return function( elem ) { - var node = typeof elem.getAttributeNode !== "undefined" && - elem.getAttributeNode( "id" ); - return node && node.value === attrId; - }; - }; - - // Support: IE 6 - 7 only - // getElementById is not reliable as a find shortcut - Expr.find[ "ID" ] = function( id, context ) { - if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { - var node, i, elems, - elem = context.getElementById( id ); - - if ( elem ) { - - // Verify the id attribute - node = elem.getAttributeNode( "id" ); - if ( node && node.value === id ) { - return [ elem ]; - } - - // Fall back on getElementsByName - elems = context.getElementsByName( id ); - i = 0; - while ( ( elem = elems[ i++ ] ) ) { - node = elem.getAttributeNode( "id" ); - if ( node && node.value === id ) { - return [ elem ]; - } - } - } - - return []; - } - }; - } - - // Tag - Expr.find[ "TAG" ] = support.getElementsByTagName ? - function( tag, context ) { - if ( typeof context.getElementsByTagName !== "undefined" ) { - return context.getElementsByTagName( tag ); - - // DocumentFragment nodes don't have gEBTN - } else if ( support.qsa ) { - return context.querySelectorAll( tag ); - } - } : - - function( tag, context ) { - var elem, - tmp = [], - i = 0, - - // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too - results = context.getElementsByTagName( tag ); - - // Filter out possible comments - if ( tag === "*" ) { - while ( ( elem = results[ i++ ] ) ) { - if ( elem.nodeType === 1 ) { - tmp.push( elem ); - } - } - - return tmp; - } - return results; - }; - - // Class - Expr.find[ "CLASS" ] = support.getElementsByClassName && function( className, context ) { - if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { - return context.getElementsByClassName( className ); - } - }; - - /* QSA/matchesSelector - ---------------------------------------------------------------------- */ - - // QSA and matchesSelector support - - // matchesSelector(:active) reports false when true (IE9/Opera 11.5) - rbuggyMatches = []; - - // qSa(:focus) reports false when true (Chrome 21) - // We allow this because of a bug in IE8/9 that throws an error - // whenever `document.activeElement` is accessed on an iframe - // So, we allow :focus to pass through QSA all the time to avoid the IE error - // See https://bugs.jquery.com/ticket/13378 - rbuggyQSA = []; - - if ( ( support.qsa = rnative.test( document.querySelectorAll ) ) ) { - - // Build QSA regex - // Regex strategy adopted from Diego Perini - assert( function( el ) { - - var input; - - // Select is set to empty string on purpose - // This is to test IE's treatment of not explicitly - // setting a boolean content attribute, - // since its presence should be enough - // https://bugs.jquery.com/ticket/12359 - docElem.appendChild( el ).innerHTML = "" + - ""; - - // Support: IE8, Opera 11-12.16 - // Nothing should be selected when empty strings follow ^= or $= or *= - // The test attribute must be unknown in Opera but "safe" for WinRT - // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section - if ( el.querySelectorAll( "[msallowcapture^='']" ).length ) { - rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); - } - - // Support: IE8 - // Boolean attributes and "value" are not treated correctly - if ( !el.querySelectorAll( "[selected]" ).length ) { - rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); - } - - // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ - if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { - rbuggyQSA.push( "~=" ); - } - - // Support: IE 11+, Edge 15 - 18+ - // IE 11/Edge don't find elements on a `[name='']` query in some cases. - // Adding a temporary attribute to the document before the selection works - // around the issue. - // Interestingly, IE 10 & older don't seem to have the issue. - input = document.createElement( "input" ); - input.setAttribute( "name", "" ); - el.appendChild( input ); - if ( !el.querySelectorAll( "[name='']" ).length ) { - rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" + - whitespace + "*(?:''|\"\")" ); - } - - // Webkit/Opera - :checked should return selected option elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - // IE8 throws error here and will not see later tests - if ( !el.querySelectorAll( ":checked" ).length ) { - rbuggyQSA.push( ":checked" ); - } - - // Support: Safari 8+, iOS 8+ - // https://bugs.webkit.org/show_bug.cgi?id=136851 - // In-page `selector#id sibling-combinator selector` fails - if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { - rbuggyQSA.push( ".#.+[+~]" ); - } - - // Support: Firefox <=3.6 - 5 only - // Old Firefox doesn't throw on a badly-escaped identifier. - el.querySelectorAll( "\\\f" ); - rbuggyQSA.push( "[\\r\\n\\f]" ); - } ); - - assert( function( el ) { - el.innerHTML = "" + - ""; - - // Support: Windows 8 Native Apps - // The type and name attributes are restricted during .innerHTML assignment - var input = document.createElement( "input" ); - input.setAttribute( "type", "hidden" ); - el.appendChild( input ).setAttribute( "name", "D" ); - - // Support: IE8 - // Enforce case-sensitivity of name attribute - if ( el.querySelectorAll( "[name=d]" ).length ) { - rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); - } - - // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) - // IE8 throws error here and will not see later tests - if ( el.querySelectorAll( ":enabled" ).length !== 2 ) { - rbuggyQSA.push( ":enabled", ":disabled" ); - } - - // Support: IE9-11+ - // IE's :disabled selector does not pick up the children of disabled fieldsets - docElem.appendChild( el ).disabled = true; - if ( el.querySelectorAll( ":disabled" ).length !== 2 ) { - rbuggyQSA.push( ":enabled", ":disabled" ); - } - - // Support: Opera 10 - 11 only - // Opera 10-11 does not throw on post-comma invalid pseudos - el.querySelectorAll( "*,:x" ); - rbuggyQSA.push( ",.*:" ); - } ); - } - - if ( ( support.matchesSelector = rnative.test( ( matches = docElem.matches || - docElem.webkitMatchesSelector || - docElem.mozMatchesSelector || - docElem.oMatchesSelector || - docElem.msMatchesSelector ) ) ) ) { - - assert( function( el ) { - - // Check to see if it's possible to do matchesSelector - // on a disconnected node (IE 9) - support.disconnectedMatch = matches.call( el, "*" ); - - // This should fail with an exception - // Gecko does not error, returns false instead - matches.call( el, "[s!='']:x" ); - rbuggyMatches.push( "!=", pseudos ); - } ); - } - - rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) ); - rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join( "|" ) ); - - /* Contains - ---------------------------------------------------------------------- */ - hasCompare = rnative.test( docElem.compareDocumentPosition ); - - // Element contains another - // Purposefully self-exclusive - // As in, an element does not contain itself - contains = hasCompare || rnative.test( docElem.contains ) ? - function( a, b ) { - var adown = a.nodeType === 9 ? a.documentElement : a, - bup = b && b.parentNode; - return a === bup || !!( bup && bup.nodeType === 1 && ( - adown.contains ? - adown.contains( bup ) : - a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 - ) ); - } : - function( a, b ) { - if ( b ) { - while ( ( b = b.parentNode ) ) { - if ( b === a ) { - return true; - } - } - } - return false; - }; - - /* Sorting - ---------------------------------------------------------------------- */ - - // Document order sorting - sortOrder = hasCompare ? - function( a, b ) { - - // Flag for duplicate removal - if ( a === b ) { - hasDuplicate = true; - return 0; - } - - // Sort on method existence if only one input has compareDocumentPosition - var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; - if ( compare ) { - return compare; - } - - // Calculate position if both inputs belong to the same document - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - compare = ( a.ownerDocument || a ) == ( b.ownerDocument || b ) ? - a.compareDocumentPosition( b ) : - - // Otherwise we know they are disconnected - 1; - - // Disconnected nodes - if ( compare & 1 || - ( !support.sortDetached && b.compareDocumentPosition( a ) === compare ) ) { - - // Choose the first element that is related to our preferred document - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( a == document || a.ownerDocument == preferredDoc && - contains( preferredDoc, a ) ) { - return -1; - } - - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( b == document || b.ownerDocument == preferredDoc && - contains( preferredDoc, b ) ) { - return 1; - } - - // Maintain original order - return sortInput ? - ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : - 0; - } - - return compare & 4 ? -1 : 1; - } : - function( a, b ) { - - // Exit early if the nodes are identical - if ( a === b ) { - hasDuplicate = true; - return 0; - } - - var cur, - i = 0, - aup = a.parentNode, - bup = b.parentNode, - ap = [ a ], - bp = [ b ]; - - // Parentless nodes are either documents or disconnected - if ( !aup || !bup ) { - - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - /* eslint-disable eqeqeq */ - return a == document ? -1 : - b == document ? 1 : - /* eslint-enable eqeqeq */ - aup ? -1 : - bup ? 1 : - sortInput ? - ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : - 0; - - // If the nodes are siblings, we can do a quick check - } else if ( aup === bup ) { - return siblingCheck( a, b ); - } - - // Otherwise we need full lists of their ancestors for comparison - cur = a; - while ( ( cur = cur.parentNode ) ) { - ap.unshift( cur ); - } - cur = b; - while ( ( cur = cur.parentNode ) ) { - bp.unshift( cur ); - } - - // Walk down the tree looking for a discrepancy - while ( ap[ i ] === bp[ i ] ) { - i++; - } - - return i ? - - // Do a sibling check if the nodes have a common ancestor - siblingCheck( ap[ i ], bp[ i ] ) : - - // Otherwise nodes in our document sort first - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - /* eslint-disable eqeqeq */ - ap[ i ] == preferredDoc ? -1 : - bp[ i ] == preferredDoc ? 1 : - /* eslint-enable eqeqeq */ - 0; - }; - - return document; -}; - -Sizzle.matches = function( expr, elements ) { - return Sizzle( expr, null, null, elements ); -}; - -Sizzle.matchesSelector = function( elem, expr ) { - setDocument( elem ); - - if ( support.matchesSelector && documentIsHTML && - !nonnativeSelectorCache[ expr + " " ] && - ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && - ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { - - try { - var ret = matches.call( elem, expr ); - - // IE 9's matchesSelector returns false on disconnected nodes - if ( ret || support.disconnectedMatch || - - // As well, disconnected nodes are said to be in a document - // fragment in IE 9 - elem.document && elem.document.nodeType !== 11 ) { - return ret; - } - } catch ( e ) { - nonnativeSelectorCache( expr, true ); - } - } - - return Sizzle( expr, document, null, [ elem ] ).length > 0; -}; - -Sizzle.contains = function( context, elem ) { - - // Set document vars if needed - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( ( context.ownerDocument || context ) != document ) { - setDocument( context ); - } - return contains( context, elem ); -}; - -Sizzle.attr = function( elem, name ) { - - // Set document vars if needed - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( ( elem.ownerDocument || elem ) != document ) { - setDocument( elem ); - } - - var fn = Expr.attrHandle[ name.toLowerCase() ], - - // Don't get fooled by Object.prototype properties (jQuery #13807) - val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? - fn( elem, name, !documentIsHTML ) : - undefined; - - return val !== undefined ? - val : - support.attributes || !documentIsHTML ? - elem.getAttribute( name ) : - ( val = elem.getAttributeNode( name ) ) && val.specified ? - val.value : - null; -}; - -Sizzle.escape = function( sel ) { - return ( sel + "" ).replace( rcssescape, fcssescape ); -}; - -Sizzle.error = function( msg ) { - throw new Error( "Syntax error, unrecognized expression: " + msg ); -}; - -/** - * Document sorting and removing duplicates - * @param {ArrayLike} results - */ -Sizzle.uniqueSort = function( results ) { - var elem, - duplicates = [], - j = 0, - i = 0; - - // Unless we *know* we can detect duplicates, assume their presence - hasDuplicate = !support.detectDuplicates; - sortInput = !support.sortStable && results.slice( 0 ); - results.sort( sortOrder ); - - if ( hasDuplicate ) { - while ( ( elem = results[ i++ ] ) ) { - if ( elem === results[ i ] ) { - j = duplicates.push( i ); - } - } - while ( j-- ) { - results.splice( duplicates[ j ], 1 ); - } - } - - // Clear input after sorting to release objects - // See https://github.com/jquery/sizzle/pull/225 - sortInput = null; - - return results; -}; - -/** - * Utility function for retrieving the text value of an array of DOM nodes - * @param {Array|Element} elem - */ -getText = Sizzle.getText = function( elem ) { - var node, - ret = "", - i = 0, - nodeType = elem.nodeType; - - if ( !nodeType ) { - - // If no nodeType, this is expected to be an array - while ( ( node = elem[ i++ ] ) ) { - - // Do not traverse comment nodes - ret += getText( node ); - } - } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { - - // Use textContent for elements - // innerText usage removed for consistency of new lines (jQuery #11153) - if ( typeof elem.textContent === "string" ) { - return elem.textContent; - } else { - - // Traverse its children - for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { - ret += getText( elem ); - } - } - } else if ( nodeType === 3 || nodeType === 4 ) { - return elem.nodeValue; - } - - // Do not include comment or processing instruction nodes - - return ret; -}; - -Expr = Sizzle.selectors = { - - // Can be adjusted by the user - cacheLength: 50, - - createPseudo: markFunction, - - match: matchExpr, - - attrHandle: {}, - - find: {}, - - relative: { - ">": { dir: "parentNode", first: true }, - " ": { dir: "parentNode" }, - "+": { dir: "previousSibling", first: true }, - "~": { dir: "previousSibling" } - }, - - preFilter: { - "ATTR": function( match ) { - match[ 1 ] = match[ 1 ].replace( runescape, funescape ); - - // Move the given value to match[3] whether quoted or unquoted - match[ 3 ] = ( match[ 3 ] || match[ 4 ] || - match[ 5 ] || "" ).replace( runescape, funescape ); - - if ( match[ 2 ] === "~=" ) { - match[ 3 ] = " " + match[ 3 ] + " "; - } - - return match.slice( 0, 4 ); - }, - - "CHILD": function( match ) { - - /* matches from matchExpr["CHILD"] - 1 type (only|nth|...) - 2 what (child|of-type) - 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) - 4 xn-component of xn+y argument ([+-]?\d*n|) - 5 sign of xn-component - 6 x of xn-component - 7 sign of y-component - 8 y of y-component - */ - match[ 1 ] = match[ 1 ].toLowerCase(); - - if ( match[ 1 ].slice( 0, 3 ) === "nth" ) { - - // nth-* requires argument - if ( !match[ 3 ] ) { - Sizzle.error( match[ 0 ] ); - } - - // numeric x and y parameters for Expr.filter.CHILD - // remember that false/true cast respectively to 0/1 - match[ 4 ] = +( match[ 4 ] ? - match[ 5 ] + ( match[ 6 ] || 1 ) : - 2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" ) ); - match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" ); - - // other types prohibit arguments - } else if ( match[ 3 ] ) { - Sizzle.error( match[ 0 ] ); - } - - return match; - }, - - "PSEUDO": function( match ) { - var excess, - unquoted = !match[ 6 ] && match[ 2 ]; - - if ( matchExpr[ "CHILD" ].test( match[ 0 ] ) ) { - return null; - } - - // Accept quoted arguments as-is - if ( match[ 3 ] ) { - match[ 2 ] = match[ 4 ] || match[ 5 ] || ""; - - // Strip excess characters from unquoted arguments - } else if ( unquoted && rpseudo.test( unquoted ) && - - // Get excess from tokenize (recursively) - ( excess = tokenize( unquoted, true ) ) && - - // advance to the next closing parenthesis - ( excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length ) ) { - - // excess is a negative index - match[ 0 ] = match[ 0 ].slice( 0, excess ); - match[ 2 ] = unquoted.slice( 0, excess ); - } - - // Return only captures needed by the pseudo filter method (type and argument) - return match.slice( 0, 3 ); - } - }, - - filter: { - - "TAG": function( nodeNameSelector ) { - var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); - return nodeNameSelector === "*" ? - function() { - return true; - } : - function( elem ) { - return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; - }; - }, - - "CLASS": function( className ) { - var pattern = classCache[ className + " " ]; - - return pattern || - ( pattern = new RegExp( "(^|" + whitespace + - ")" + className + "(" + whitespace + "|$)" ) ) && classCache( - className, function( elem ) { - return pattern.test( - typeof elem.className === "string" && elem.className || - typeof elem.getAttribute !== "undefined" && - elem.getAttribute( "class" ) || - "" - ); - } ); - }, - - "ATTR": function( name, operator, check ) { - return function( elem ) { - var result = Sizzle.attr( elem, name ); - - if ( result == null ) { - return operator === "!="; - } - if ( !operator ) { - return true; - } - - result += ""; - - /* eslint-disable max-len */ - - return operator === "=" ? result === check : - operator === "!=" ? result !== check : - operator === "^=" ? check && result.indexOf( check ) === 0 : - operator === "*=" ? check && result.indexOf( check ) > -1 : - operator === "$=" ? check && result.slice( -check.length ) === check : - operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : - operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : - false; - /* eslint-enable max-len */ - - }; - }, - - "CHILD": function( type, what, _argument, first, last ) { - var simple = type.slice( 0, 3 ) !== "nth", - forward = type.slice( -4 ) !== "last", - ofType = what === "of-type"; - - return first === 1 && last === 0 ? - - // Shortcut for :nth-*(n) - function( elem ) { - return !!elem.parentNode; - } : - - function( elem, _context, xml ) { - var cache, uniqueCache, outerCache, node, nodeIndex, start, - dir = simple !== forward ? "nextSibling" : "previousSibling", - parent = elem.parentNode, - name = ofType && elem.nodeName.toLowerCase(), - useCache = !xml && !ofType, - diff = false; - - if ( parent ) { - - // :(first|last|only)-(child|of-type) - if ( simple ) { - while ( dir ) { - node = elem; - while ( ( node = node[ dir ] ) ) { - if ( ofType ? - node.nodeName.toLowerCase() === name : - node.nodeType === 1 ) { - - return false; - } - } - - // Reverse direction for :only-* (if we haven't yet done so) - start = dir = type === "only" && !start && "nextSibling"; - } - return true; - } - - start = [ forward ? parent.firstChild : parent.lastChild ]; - - // non-xml :nth-child(...) stores cache data on `parent` - if ( forward && useCache ) { - - // Seek `elem` from a previously-cached index - - // ...in a gzip-friendly way - node = parent; - outerCache = node[ expando ] || ( node[ expando ] = {} ); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ node.uniqueID ] || - ( outerCache[ node.uniqueID ] = {} ); - - cache = uniqueCache[ type ] || []; - nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; - diff = nodeIndex && cache[ 2 ]; - node = nodeIndex && parent.childNodes[ nodeIndex ]; - - while ( ( node = ++nodeIndex && node && node[ dir ] || - - // Fallback to seeking `elem` from the start - ( diff = nodeIndex = 0 ) || start.pop() ) ) { - - // When found, cache indexes on `parent` and break - if ( node.nodeType === 1 && ++diff && node === elem ) { - uniqueCache[ type ] = [ dirruns, nodeIndex, diff ]; - break; - } - } - - } else { - - // Use previously-cached element index if available - if ( useCache ) { - - // ...in a gzip-friendly way - node = elem; - outerCache = node[ expando ] || ( node[ expando ] = {} ); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ node.uniqueID ] || - ( outerCache[ node.uniqueID ] = {} ); - - cache = uniqueCache[ type ] || []; - nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; - diff = nodeIndex; - } - - // xml :nth-child(...) - // or :nth-last-child(...) or :nth(-last)?-of-type(...) - if ( diff === false ) { - - // Use the same loop as above to seek `elem` from the start - while ( ( node = ++nodeIndex && node && node[ dir ] || - ( diff = nodeIndex = 0 ) || start.pop() ) ) { - - if ( ( ofType ? - node.nodeName.toLowerCase() === name : - node.nodeType === 1 ) && - ++diff ) { - - // Cache the index of each encountered element - if ( useCache ) { - outerCache = node[ expando ] || - ( node[ expando ] = {} ); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ node.uniqueID ] || - ( outerCache[ node.uniqueID ] = {} ); - - uniqueCache[ type ] = [ dirruns, diff ]; - } - - if ( node === elem ) { - break; - } - } - } - } - } - - // Incorporate the offset, then check against cycle size - diff -= last; - return diff === first || ( diff % first === 0 && diff / first >= 0 ); - } - }; - }, - - "PSEUDO": function( pseudo, argument ) { - - // pseudo-class names are case-insensitive - // http://www.w3.org/TR/selectors/#pseudo-classes - // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters - // Remember that setFilters inherits from pseudos - var args, - fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || - Sizzle.error( "unsupported pseudo: " + pseudo ); - - // The user may use createPseudo to indicate that - // arguments are needed to create the filter function - // just as Sizzle does - if ( fn[ expando ] ) { - return fn( argument ); - } - - // But maintain support for old signatures - if ( fn.length > 1 ) { - args = [ pseudo, pseudo, "", argument ]; - return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? - markFunction( function( seed, matches ) { - var idx, - matched = fn( seed, argument ), - i = matched.length; - while ( i-- ) { - idx = indexOf( seed, matched[ i ] ); - seed[ idx ] = !( matches[ idx ] = matched[ i ] ); - } - } ) : - function( elem ) { - return fn( elem, 0, args ); - }; - } - - return fn; - } - }, - - pseudos: { - - // Potentially complex pseudos - "not": markFunction( function( selector ) { - - // Trim the selector passed to compile - // to avoid treating leading and trailing - // spaces as combinators - var input = [], - results = [], - matcher = compile( selector.replace( rtrim, "$1" ) ); - - return matcher[ expando ] ? - markFunction( function( seed, matches, _context, xml ) { - var elem, - unmatched = matcher( seed, null, xml, [] ), - i = seed.length; - - // Match elements unmatched by `matcher` - while ( i-- ) { - if ( ( elem = unmatched[ i ] ) ) { - seed[ i ] = !( matches[ i ] = elem ); - } - } - } ) : - function( elem, _context, xml ) { - input[ 0 ] = elem; - matcher( input, null, xml, results ); - - // Don't keep the element (issue #299) - input[ 0 ] = null; - return !results.pop(); - }; - } ), - - "has": markFunction( function( selector ) { - return function( elem ) { - return Sizzle( selector, elem ).length > 0; - }; - } ), - - "contains": markFunction( function( text ) { - text = text.replace( runescape, funescape ); - return function( elem ) { - return ( elem.textContent || getText( elem ) ).indexOf( text ) > -1; - }; - } ), - - // "Whether an element is represented by a :lang() selector - // is based solely on the element's language value - // being equal to the identifier C, - // or beginning with the identifier C immediately followed by "-". - // The matching of C against the element's language value is performed case-insensitively. - // The identifier C does not have to be a valid language name." - // http://www.w3.org/TR/selectors/#lang-pseudo - "lang": markFunction( function( lang ) { - - // lang value must be a valid identifier - if ( !ridentifier.test( lang || "" ) ) { - Sizzle.error( "unsupported lang: " + lang ); - } - lang = lang.replace( runescape, funescape ).toLowerCase(); - return function( elem ) { - var elemLang; - do { - if ( ( elemLang = documentIsHTML ? - elem.lang : - elem.getAttribute( "xml:lang" ) || elem.getAttribute( "lang" ) ) ) { - - elemLang = elemLang.toLowerCase(); - return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; - } - } while ( ( elem = elem.parentNode ) && elem.nodeType === 1 ); - return false; - }; - } ), - - // Miscellaneous - "target": function( elem ) { - var hash = window.location && window.location.hash; - return hash && hash.slice( 1 ) === elem.id; - }, - - "root": function( elem ) { - return elem === docElem; - }, - - "focus": function( elem ) { - return elem === document.activeElement && - ( !document.hasFocus || document.hasFocus() ) && - !!( elem.type || elem.href || ~elem.tabIndex ); - }, - - // Boolean properties - "enabled": createDisabledPseudo( false ), - "disabled": createDisabledPseudo( true ), - - "checked": function( elem ) { - - // In CSS3, :checked should return both checked and selected elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - var nodeName = elem.nodeName.toLowerCase(); - return ( nodeName === "input" && !!elem.checked ) || - ( nodeName === "option" && !!elem.selected ); - }, - - "selected": function( elem ) { - - // Accessing this property makes selected-by-default - // options in Safari work properly - if ( elem.parentNode ) { - // eslint-disable-next-line no-unused-expressions - elem.parentNode.selectedIndex; - } - - return elem.selected === true; - }, - - // Contents - "empty": function( elem ) { - - // http://www.w3.org/TR/selectors/#empty-pseudo - // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), - // but not by others (comment: 8; processing instruction: 7; etc.) - // nodeType < 6 works because attributes (2) do not appear as children - for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { - if ( elem.nodeType < 6 ) { - return false; - } - } - return true; - }, - - "parent": function( elem ) { - return !Expr.pseudos[ "empty" ]( elem ); - }, - - // Element/input types - "header": function( elem ) { - return rheader.test( elem.nodeName ); - }, - - "input": function( elem ) { - return rinputs.test( elem.nodeName ); - }, - - "button": function( elem ) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && elem.type === "button" || name === "button"; - }, - - "text": function( elem ) { - var attr; - return elem.nodeName.toLowerCase() === "input" && - elem.type === "text" && - - // Support: IE<8 - // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" - ( ( attr = elem.getAttribute( "type" ) ) == null || - attr.toLowerCase() === "text" ); - }, - - // Position-in-collection - "first": createPositionalPseudo( function() { - return [ 0 ]; - } ), - - "last": createPositionalPseudo( function( _matchIndexes, length ) { - return [ length - 1 ]; - } ), - - "eq": createPositionalPseudo( function( _matchIndexes, length, argument ) { - return [ argument < 0 ? argument + length : argument ]; - } ), - - "even": createPositionalPseudo( function( matchIndexes, length ) { - var i = 0; - for ( ; i < length; i += 2 ) { - matchIndexes.push( i ); - } - return matchIndexes; - } ), - - "odd": createPositionalPseudo( function( matchIndexes, length ) { - var i = 1; - for ( ; i < length; i += 2 ) { - matchIndexes.push( i ); - } - return matchIndexes; - } ), - - "lt": createPositionalPseudo( function( matchIndexes, length, argument ) { - var i = argument < 0 ? - argument + length : - argument > length ? - length : - argument; - for ( ; --i >= 0; ) { - matchIndexes.push( i ); - } - return matchIndexes; - } ), - - "gt": createPositionalPseudo( function( matchIndexes, length, argument ) { - var i = argument < 0 ? argument + length : argument; - for ( ; ++i < length; ) { - matchIndexes.push( i ); - } - return matchIndexes; - } ) - } -}; - -Expr.pseudos[ "nth" ] = Expr.pseudos[ "eq" ]; - -// Add button/input type pseudos -for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { - Expr.pseudos[ i ] = createInputPseudo( i ); -} -for ( i in { submit: true, reset: true } ) { - Expr.pseudos[ i ] = createButtonPseudo( i ); -} - -// Easy API for creating new setFilters -function setFilters() {} -setFilters.prototype = Expr.filters = Expr.pseudos; -Expr.setFilters = new setFilters(); - -tokenize = Sizzle.tokenize = function( selector, parseOnly ) { - var matched, match, tokens, type, - soFar, groups, preFilters, - cached = tokenCache[ selector + " " ]; - - if ( cached ) { - return parseOnly ? 0 : cached.slice( 0 ); - } - - soFar = selector; - groups = []; - preFilters = Expr.preFilter; - - while ( soFar ) { - - // Comma and first run - if ( !matched || ( match = rcomma.exec( soFar ) ) ) { - if ( match ) { - - // Don't consume trailing commas as valid - soFar = soFar.slice( match[ 0 ].length ) || soFar; - } - groups.push( ( tokens = [] ) ); - } - - matched = false; - - // Combinators - if ( ( match = rcombinators.exec( soFar ) ) ) { - matched = match.shift(); - tokens.push( { - value: matched, - - // Cast descendant combinators to space - type: match[ 0 ].replace( rtrim, " " ) - } ); - soFar = soFar.slice( matched.length ); - } - - // Filters - for ( type in Expr.filter ) { - if ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] || - ( match = preFilters[ type ]( match ) ) ) ) { - matched = match.shift(); - tokens.push( { - value: matched, - type: type, - matches: match - } ); - soFar = soFar.slice( matched.length ); - } - } - - if ( !matched ) { - break; - } - } - - // Return the length of the invalid excess - // if we're just parsing - // Otherwise, throw an error or return tokens - return parseOnly ? - soFar.length : - soFar ? - Sizzle.error( selector ) : - - // Cache the tokens - tokenCache( selector, groups ).slice( 0 ); -}; - -function toSelector( tokens ) { - var i = 0, - len = tokens.length, - selector = ""; - for ( ; i < len; i++ ) { - selector += tokens[ i ].value; - } - return selector; -} - -function addCombinator( matcher, combinator, base ) { - var dir = combinator.dir, - skip = combinator.next, - key = skip || dir, - checkNonElements = base && key === "parentNode", - doneName = done++; - - return combinator.first ? - - // Check against closest ancestor/preceding element - function( elem, context, xml ) { - while ( ( elem = elem[ dir ] ) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - return matcher( elem, context, xml ); - } - } - return false; - } : - - // Check against all ancestor/preceding elements - function( elem, context, xml ) { - var oldCache, uniqueCache, outerCache, - newCache = [ dirruns, doneName ]; - - // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching - if ( xml ) { - while ( ( elem = elem[ dir ] ) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - if ( matcher( elem, context, xml ) ) { - return true; - } - } - } - } else { - while ( ( elem = elem[ dir ] ) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - outerCache = elem[ expando ] || ( elem[ expando ] = {} ); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ elem.uniqueID ] || - ( outerCache[ elem.uniqueID ] = {} ); - - if ( skip && skip === elem.nodeName.toLowerCase() ) { - elem = elem[ dir ] || elem; - } else if ( ( oldCache = uniqueCache[ key ] ) && - oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { - - // Assign to newCache so results back-propagate to previous elements - return ( newCache[ 2 ] = oldCache[ 2 ] ); - } else { - - // Reuse newcache so results back-propagate to previous elements - uniqueCache[ key ] = newCache; - - // A match means we're done; a fail means we have to keep checking - if ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) { - return true; - } - } - } - } - } - return false; - }; -} - -function elementMatcher( matchers ) { - return matchers.length > 1 ? - function( elem, context, xml ) { - var i = matchers.length; - while ( i-- ) { - if ( !matchers[ i ]( elem, context, xml ) ) { - return false; - } - } - return true; - } : - matchers[ 0 ]; -} - -function multipleContexts( selector, contexts, results ) { - var i = 0, - len = contexts.length; - for ( ; i < len; i++ ) { - Sizzle( selector, contexts[ i ], results ); - } - return results; -} - -function condense( unmatched, map, filter, context, xml ) { - var elem, - newUnmatched = [], - i = 0, - len = unmatched.length, - mapped = map != null; - - for ( ; i < len; i++ ) { - if ( ( elem = unmatched[ i ] ) ) { - if ( !filter || filter( elem, context, xml ) ) { - newUnmatched.push( elem ); - if ( mapped ) { - map.push( i ); - } - } - } - } - - return newUnmatched; -} - -function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { - if ( postFilter && !postFilter[ expando ] ) { - postFilter = setMatcher( postFilter ); - } - if ( postFinder && !postFinder[ expando ] ) { - postFinder = setMatcher( postFinder, postSelector ); - } - return markFunction( function( seed, results, context, xml ) { - var temp, i, elem, - preMap = [], - postMap = [], - preexisting = results.length, - - // Get initial elements from seed or context - elems = seed || multipleContexts( - selector || "*", - context.nodeType ? [ context ] : context, - [] - ), - - // Prefilter to get matcher input, preserving a map for seed-results synchronization - matcherIn = preFilter && ( seed || !selector ) ? - condense( elems, preMap, preFilter, context, xml ) : - elems, - - matcherOut = matcher ? - - // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, - postFinder || ( seed ? preFilter : preexisting || postFilter ) ? - - // ...intermediate processing is necessary - [] : - - // ...otherwise use results directly - results : - matcherIn; - - // Find primary matches - if ( matcher ) { - matcher( matcherIn, matcherOut, context, xml ); - } - - // Apply postFilter - if ( postFilter ) { - temp = condense( matcherOut, postMap ); - postFilter( temp, [], context, xml ); - - // Un-match failing elements by moving them back to matcherIn - i = temp.length; - while ( i-- ) { - if ( ( elem = temp[ i ] ) ) { - matcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem ); - } - } - } - - if ( seed ) { - if ( postFinder || preFilter ) { - if ( postFinder ) { - - // Get the final matcherOut by condensing this intermediate into postFinder contexts - temp = []; - i = matcherOut.length; - while ( i-- ) { - if ( ( elem = matcherOut[ i ] ) ) { - - // Restore matcherIn since elem is not yet a final match - temp.push( ( matcherIn[ i ] = elem ) ); - } - } - postFinder( null, ( matcherOut = [] ), temp, xml ); - } - - // Move matched elements from seed to results to keep them synchronized - i = matcherOut.length; - while ( i-- ) { - if ( ( elem = matcherOut[ i ] ) && - ( temp = postFinder ? indexOf( seed, elem ) : preMap[ i ] ) > -1 ) { - - seed[ temp ] = !( results[ temp ] = elem ); - } - } - } - - // Add elements to results, through postFinder if defined - } else { - matcherOut = condense( - matcherOut === results ? - matcherOut.splice( preexisting, matcherOut.length ) : - matcherOut - ); - if ( postFinder ) { - postFinder( null, results, matcherOut, xml ); - } else { - push.apply( results, matcherOut ); - } - } - } ); -} - -function matcherFromTokens( tokens ) { - var checkContext, matcher, j, - len = tokens.length, - leadingRelative = Expr.relative[ tokens[ 0 ].type ], - implicitRelative = leadingRelative || Expr.relative[ " " ], - i = leadingRelative ? 1 : 0, - - // The foundational matcher ensures that elements are reachable from top-level context(s) - matchContext = addCombinator( function( elem ) { - return elem === checkContext; - }, implicitRelative, true ), - matchAnyContext = addCombinator( function( elem ) { - return indexOf( checkContext, elem ) > -1; - }, implicitRelative, true ), - matchers = [ function( elem, context, xml ) { - var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( - ( checkContext = context ).nodeType ? - matchContext( elem, context, xml ) : - matchAnyContext( elem, context, xml ) ); - - // Avoid hanging onto element (issue #299) - checkContext = null; - return ret; - } ]; - - for ( ; i < len; i++ ) { - if ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) { - matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ]; - } else { - matcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches ); - - // Return special upon seeing a positional matcher - if ( matcher[ expando ] ) { - - // Find the next relative operator (if any) for proper handling - j = ++i; - for ( ; j < len; j++ ) { - if ( Expr.relative[ tokens[ j ].type ] ) { - break; - } - } - return setMatcher( - i > 1 && elementMatcher( matchers ), - i > 1 && toSelector( - - // If the preceding token was a descendant combinator, insert an implicit any-element `*` - tokens - .slice( 0, i - 1 ) - .concat( { value: tokens[ i - 2 ].type === " " ? "*" : "" } ) - ).replace( rtrim, "$1" ), - matcher, - i < j && matcherFromTokens( tokens.slice( i, j ) ), - j < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ), - j < len && toSelector( tokens ) - ); - } - matchers.push( matcher ); - } - } - - return elementMatcher( matchers ); -} - -function matcherFromGroupMatchers( elementMatchers, setMatchers ) { - var bySet = setMatchers.length > 0, - byElement = elementMatchers.length > 0, - superMatcher = function( seed, context, xml, results, outermost ) { - var elem, j, matcher, - matchedCount = 0, - i = "0", - unmatched = seed && [], - setMatched = [], - contextBackup = outermostContext, - - // We must always have either seed elements or outermost context - elems = seed || byElement && Expr.find[ "TAG" ]( "*", outermost ), - - // Use integer dirruns iff this is the outermost matcher - dirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ), - len = elems.length; - - if ( outermost ) { - - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - outermostContext = context == document || context || outermost; - } - - // Add elements passing elementMatchers directly to results - // Support: IE<9, Safari - // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id - for ( ; i !== len && ( elem = elems[ i ] ) != null; i++ ) { - if ( byElement && elem ) { - j = 0; - - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( !context && elem.ownerDocument != document ) { - setDocument( elem ); - xml = !documentIsHTML; - } - while ( ( matcher = elementMatchers[ j++ ] ) ) { - if ( matcher( elem, context || document, xml ) ) { - results.push( elem ); - break; - } - } - if ( outermost ) { - dirruns = dirrunsUnique; - } - } - - // Track unmatched elements for set filters - if ( bySet ) { - - // They will have gone through all possible matchers - if ( ( elem = !matcher && elem ) ) { - matchedCount--; - } - - // Lengthen the array for every element, matched or not - if ( seed ) { - unmatched.push( elem ); - } - } - } - - // `i` is now the count of elements visited above, and adding it to `matchedCount` - // makes the latter nonnegative. - matchedCount += i; - - // Apply set filters to unmatched elements - // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` - // equals `i`), unless we didn't visit _any_ elements in the above loop because we have - // no element matchers and no seed. - // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that - // case, which will result in a "00" `matchedCount` that differs from `i` but is also - // numerically zero. - if ( bySet && i !== matchedCount ) { - j = 0; - while ( ( matcher = setMatchers[ j++ ] ) ) { - matcher( unmatched, setMatched, context, xml ); - } - - if ( seed ) { - - // Reintegrate element matches to eliminate the need for sorting - if ( matchedCount > 0 ) { - while ( i-- ) { - if ( !( unmatched[ i ] || setMatched[ i ] ) ) { - setMatched[ i ] = pop.call( results ); - } - } - } - - // Discard index placeholder values to get only actual matches - setMatched = condense( setMatched ); - } - - // Add matches to results - push.apply( results, setMatched ); - - // Seedless set matches succeeding multiple successful matchers stipulate sorting - if ( outermost && !seed && setMatched.length > 0 && - ( matchedCount + setMatchers.length ) > 1 ) { - - Sizzle.uniqueSort( results ); - } - } - - // Override manipulation of globals by nested matchers - if ( outermost ) { - dirruns = dirrunsUnique; - outermostContext = contextBackup; - } - - return unmatched; - }; - - return bySet ? - markFunction( superMatcher ) : - superMatcher; -} - -compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { - var i, - setMatchers = [], - elementMatchers = [], - cached = compilerCache[ selector + " " ]; - - if ( !cached ) { - - // Generate a function of recursive functions that can be used to check each element - if ( !match ) { - match = tokenize( selector ); - } - i = match.length; - while ( i-- ) { - cached = matcherFromTokens( match[ i ] ); - if ( cached[ expando ] ) { - setMatchers.push( cached ); - } else { - elementMatchers.push( cached ); - } - } - - // Cache the compiled function - cached = compilerCache( - selector, - matcherFromGroupMatchers( elementMatchers, setMatchers ) - ); - - // Save selector and tokenization - cached.selector = selector; - } - return cached; -}; - -/** - * A low-level selection function that works with Sizzle's compiled - * selector functions - * @param {String|Function} selector A selector or a pre-compiled - * selector function built with Sizzle.compile - * @param {Element} context - * @param {Array} [results] - * @param {Array} [seed] A set of elements to match against - */ -select = Sizzle.select = function( selector, context, results, seed ) { - var i, tokens, token, type, find, - compiled = typeof selector === "function" && selector, - match = !seed && tokenize( ( selector = compiled.selector || selector ) ); - - results = results || []; - - // Try to minimize operations if there is only one selector in the list and no seed - // (the latter of which guarantees us context) - if ( match.length === 1 ) { - - // Reduce context if the leading compound selector is an ID - tokens = match[ 0 ] = match[ 0 ].slice( 0 ); - if ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === "ID" && - context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) { - - context = ( Expr.find[ "ID" ]( token.matches[ 0 ] - .replace( runescape, funescape ), context ) || [] )[ 0 ]; - if ( !context ) { - return results; - - // Precompiled matchers will still verify ancestry, so step up a level - } else if ( compiled ) { - context = context.parentNode; - } - - selector = selector.slice( tokens.shift().value.length ); - } - - // Fetch a seed set for right-to-left matching - i = matchExpr[ "needsContext" ].test( selector ) ? 0 : tokens.length; - while ( i-- ) { - token = tokens[ i ]; - - // Abort if we hit a combinator - if ( Expr.relative[ ( type = token.type ) ] ) { - break; - } - if ( ( find = Expr.find[ type ] ) ) { - - // Search, expanding context for leading sibling combinators - if ( ( seed = find( - token.matches[ 0 ].replace( runescape, funescape ), - rsibling.test( tokens[ 0 ].type ) && testContext( context.parentNode ) || - context - ) ) ) { - - // If seed is empty or no tokens remain, we can return early - tokens.splice( i, 1 ); - selector = seed.length && toSelector( tokens ); - if ( !selector ) { - push.apply( results, seed ); - return results; - } - - break; - } - } - } - } - - // Compile and execute a filtering function if one is not provided - // Provide `match` to avoid retokenization if we modified the selector above - ( compiled || compile( selector, match ) )( - seed, - context, - !documentIsHTML, - results, - !context || rsibling.test( selector ) && testContext( context.parentNode ) || context - ); - return results; -}; - -// One-time assignments - -// Sort stability -support.sortStable = expando.split( "" ).sort( sortOrder ).join( "" ) === expando; - -// Support: Chrome 14-35+ -// Always assume duplicates if they aren't passed to the comparison function -support.detectDuplicates = !!hasDuplicate; - -// Initialize against the default document -setDocument(); - -// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) -// Detached nodes confoundingly follow *each other* -support.sortDetached = assert( function( el ) { - - // Should return 1, but returns 4 (following) - return el.compareDocumentPosition( document.createElement( "fieldset" ) ) & 1; -} ); - -// Support: IE<8 -// Prevent attribute/property "interpolation" -// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx -if ( !assert( function( el ) { - el.innerHTML = ""; - return el.firstChild.getAttribute( "href" ) === "#"; -} ) ) { - addHandle( "type|href|height|width", function( elem, name, isXML ) { - if ( !isXML ) { - return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); - } - } ); -} - -// Support: IE<9 -// Use defaultValue in place of getAttribute("value") -if ( !support.attributes || !assert( function( el ) { - el.innerHTML = ""; - el.firstChild.setAttribute( "value", "" ); - return el.firstChild.getAttribute( "value" ) === ""; -} ) ) { - addHandle( "value", function( elem, _name, isXML ) { - if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { - return elem.defaultValue; - } - } ); -} - -// Support: IE<9 -// Use getAttributeNode to fetch booleans when getAttribute lies -if ( !assert( function( el ) { - return el.getAttribute( "disabled" ) == null; -} ) ) { - addHandle( booleans, function( elem, name, isXML ) { - var val; - if ( !isXML ) { - return elem[ name ] === true ? name.toLowerCase() : - ( val = elem.getAttributeNode( name ) ) && val.specified ? - val.value : - null; - } - } ); -} - -return Sizzle; - -} )( window ); - - - -jQuery.find = Sizzle; -jQuery.expr = Sizzle.selectors; - -// Deprecated -jQuery.expr[ ":" ] = jQuery.expr.pseudos; -jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort; -jQuery.text = Sizzle.getText; -jQuery.isXMLDoc = Sizzle.isXML; -jQuery.contains = Sizzle.contains; -jQuery.escapeSelector = Sizzle.escape; - - - - -var dir = function( elem, dir, until ) { - var matched = [], - truncate = until !== undefined; - - while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { - if ( elem.nodeType === 1 ) { - if ( truncate && jQuery( elem ).is( until ) ) { - break; - } - matched.push( elem ); - } - } - return matched; -}; - - -var siblings = function( n, elem ) { - var matched = []; - - for ( ; n; n = n.nextSibling ) { - if ( n.nodeType === 1 && n !== elem ) { - matched.push( n ); - } - } - - return matched; -}; - - -var rneedsContext = jQuery.expr.match.needsContext; - - - -function nodeName( elem, name ) { - - return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); - -}; -var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); - - - -// Implement the identical functionality for filter and not -function winnow( elements, qualifier, not ) { - if ( isFunction( qualifier ) ) { - return jQuery.grep( elements, function( elem, i ) { - return !!qualifier.call( elem, i, elem ) !== not; - } ); - } - - // Single element - if ( qualifier.nodeType ) { - return jQuery.grep( elements, function( elem ) { - return ( elem === qualifier ) !== not; - } ); - } - - // Arraylike of elements (jQuery, arguments, Array) - if ( typeof qualifier !== "string" ) { - return jQuery.grep( elements, function( elem ) { - return ( indexOf.call( qualifier, elem ) > -1 ) !== not; - } ); - } - - // Filtered directly for both simple and complex selectors - return jQuery.filter( qualifier, elements, not ); -} - -jQuery.filter = function( expr, elems, not ) { - var elem = elems[ 0 ]; - - if ( not ) { - expr = ":not(" + expr + ")"; - } - - if ( elems.length === 1 && elem.nodeType === 1 ) { - return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : []; - } - - return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { - return elem.nodeType === 1; - } ) ); -}; - -jQuery.fn.extend( { - find: function( selector ) { - var i, ret, - len = this.length, - self = this; - - if ( typeof selector !== "string" ) { - return this.pushStack( jQuery( selector ).filter( function() { - for ( i = 0; i < len; i++ ) { - if ( jQuery.contains( self[ i ], this ) ) { - return true; - } - } - } ) ); - } - - ret = this.pushStack( [] ); - - for ( i = 0; i < len; i++ ) { - jQuery.find( selector, self[ i ], ret ); - } - - return len > 1 ? jQuery.uniqueSort( ret ) : ret; - }, - filter: function( selector ) { - return this.pushStack( winnow( this, selector || [], false ) ); - }, - not: function( selector ) { - return this.pushStack( winnow( this, selector || [], true ) ); - }, - is: function( selector ) { - return !!winnow( - this, - - // If this is a positional/relative selector, check membership in the returned set - // so $("p:first").is("p:last") won't return true for a doc with two "p". - typeof selector === "string" && rneedsContext.test( selector ) ? - jQuery( selector ) : - selector || [], - false - ).length; - } -} ); - - -// Initialize a jQuery object - - -// A central reference to the root jQuery(document) -var rootjQuery, - - // A simple way to check for HTML strings - // Prioritize #id over to avoid XSS via location.hash (#9521) - // Strict HTML recognition (#11290: must start with <) - // Shortcut simple #id case for speed - rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, - - init = jQuery.fn.init = function( selector, context, root ) { - var match, elem; - - // HANDLE: $(""), $(null), $(undefined), $(false) - if ( !selector ) { - return this; - } - - // Method init() accepts an alternate rootjQuery - // so migrate can support jQuery.sub (gh-2101) - root = root || rootjQuery; - - // Handle HTML strings - if ( typeof selector === "string" ) { - if ( selector[ 0 ] === "<" && - selector[ selector.length - 1 ] === ">" && - selector.length >= 3 ) { - - // Assume that strings that start and end with <> are HTML and skip the regex check - match = [ null, selector, null ]; - - } else { - match = rquickExpr.exec( selector ); - } - - // Match html or make sure no context is specified for #id - if ( match && ( match[ 1 ] || !context ) ) { - - // HANDLE: $(html) -> $(array) - if ( match[ 1 ] ) { - context = context instanceof jQuery ? context[ 0 ] : context; - - // Option to run scripts is true for back-compat - // Intentionally let the error be thrown if parseHTML is not present - jQuery.merge( this, jQuery.parseHTML( - match[ 1 ], - context && context.nodeType ? context.ownerDocument || context : document, - true - ) ); - - // HANDLE: $(html, props) - if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { - for ( match in context ) { - - // Properties of context are called as methods if possible - if ( isFunction( this[ match ] ) ) { - this[ match ]( context[ match ] ); - - // ...and otherwise set as attributes - } else { - this.attr( match, context[ match ] ); - } - } - } - - return this; - - // HANDLE: $(#id) - } else { - elem = document.getElementById( match[ 2 ] ); - - if ( elem ) { - - // Inject the element directly into the jQuery object - this[ 0 ] = elem; - this.length = 1; - } - return this; - } - - // HANDLE: $(expr, $(...)) - } else if ( !context || context.jquery ) { - return ( context || root ).find( selector ); - - // HANDLE: $(expr, context) - // (which is just equivalent to: $(context).find(expr) - } else { - return this.constructor( context ).find( selector ); - } - - // HANDLE: $(DOMElement) - } else if ( selector.nodeType ) { - this[ 0 ] = selector; - this.length = 1; - return this; - - // HANDLE: $(function) - // Shortcut for document ready - } else if ( isFunction( selector ) ) { - return root.ready !== undefined ? - root.ready( selector ) : - - // Execute immediately if ready is not present - selector( jQuery ); - } - - return jQuery.makeArray( selector, this ); - }; - -// Give the init function the jQuery prototype for later instantiation -init.prototype = jQuery.fn; - -// Initialize central reference -rootjQuery = jQuery( document ); - - -var rparentsprev = /^(?:parents|prev(?:Until|All))/, - - // Methods guaranteed to produce a unique set when starting from a unique set - guaranteedUnique = { - children: true, - contents: true, - next: true, - prev: true - }; - -jQuery.fn.extend( { - has: function( target ) { - var targets = jQuery( target, this ), - l = targets.length; - - return this.filter( function() { - var i = 0; - for ( ; i < l; i++ ) { - if ( jQuery.contains( this, targets[ i ] ) ) { - return true; - } - } - } ); - }, - - closest: function( selectors, context ) { - var cur, - i = 0, - l = this.length, - matched = [], - targets = typeof selectors !== "string" && jQuery( selectors ); - - // Positional selectors never match, since there's no _selection_ context - if ( !rneedsContext.test( selectors ) ) { - for ( ; i < l; i++ ) { - for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { - - // Always skip document fragments - if ( cur.nodeType < 11 && ( targets ? - targets.index( cur ) > -1 : - - // Don't pass non-elements to Sizzle - cur.nodeType === 1 && - jQuery.find.matchesSelector( cur, selectors ) ) ) { - - matched.push( cur ); - break; - } - } - } - } - - return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); - }, - - // Determine the position of an element within the set - index: function( elem ) { - - // No argument, return index in parent - if ( !elem ) { - return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; - } - - // Index in selector - if ( typeof elem === "string" ) { - return indexOf.call( jQuery( elem ), this[ 0 ] ); - } - - // Locate the position of the desired element - return indexOf.call( this, - - // If it receives a jQuery object, the first element is used - elem.jquery ? elem[ 0 ] : elem - ); - }, - - add: function( selector, context ) { - return this.pushStack( - jQuery.uniqueSort( - jQuery.merge( this.get(), jQuery( selector, context ) ) - ) - ); - }, - - addBack: function( selector ) { - return this.add( selector == null ? - this.prevObject : this.prevObject.filter( selector ) - ); - } -} ); - -function sibling( cur, dir ) { - while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {} - return cur; -} - -jQuery.each( { - parent: function( elem ) { - var parent = elem.parentNode; - return parent && parent.nodeType !== 11 ? parent : null; - }, - parents: function( elem ) { - return dir( elem, "parentNode" ); - }, - parentsUntil: function( elem, _i, until ) { - return dir( elem, "parentNode", until ); - }, - next: function( elem ) { - return sibling( elem, "nextSibling" ); - }, - prev: function( elem ) { - return sibling( elem, "previousSibling" ); - }, - nextAll: function( elem ) { - return dir( elem, "nextSibling" ); - }, - prevAll: function( elem ) { - return dir( elem, "previousSibling" ); - }, - nextUntil: function( elem, _i, until ) { - return dir( elem, "nextSibling", until ); - }, - prevUntil: function( elem, _i, until ) { - return dir( elem, "previousSibling", until ); - }, - siblings: function( elem ) { - return siblings( ( elem.parentNode || {} ).firstChild, elem ); - }, - children: function( elem ) { - return siblings( elem.firstChild ); - }, - contents: function( elem ) { - if ( elem.contentDocument != null && - - // Support: IE 11+ - // elements with no `data` attribute has an object - // `contentDocument` with a `null` prototype. - getProto( elem.contentDocument ) ) { - - return elem.contentDocument; - } - - // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only - // Treat the template element as a regular one in browsers that - // don't support it. - if ( nodeName( elem, "template" ) ) { - elem = elem.content || elem; - } - - return jQuery.merge( [], elem.childNodes ); - } -}, function( name, fn ) { - jQuery.fn[ name ] = function( until, selector ) { - var matched = jQuery.map( this, fn, until ); - - if ( name.slice( -5 ) !== "Until" ) { - selector = until; - } - - if ( selector && typeof selector === "string" ) { - matched = jQuery.filter( selector, matched ); - } - - if ( this.length > 1 ) { - - // Remove duplicates - if ( !guaranteedUnique[ name ] ) { - jQuery.uniqueSort( matched ); - } - - // Reverse order for parents* and prev-derivatives - if ( rparentsprev.test( name ) ) { - matched.reverse(); - } - } - - return this.pushStack( matched ); - }; -} ); -var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); - - - -// Convert String-formatted options into Object-formatted ones -function createOptions( options ) { - var object = {}; - jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { - object[ flag ] = true; - } ); - return object; -} - -/* - * Create a callback list using the following parameters: - * - * options: an optional list of space-separated options that will change how - * the callback list behaves or a more traditional option object - * - * By default a callback list will act like an event callback list and can be - * "fired" multiple times. - * - * Possible options: - * - * once: will ensure the callback list can only be fired once (like a Deferred) - * - * memory: will keep track of previous values and will call any callback added - * after the list has been fired right away with the latest "memorized" - * values (like a Deferred) - * - * unique: will ensure a callback can only be added once (no duplicate in the list) - * - * stopOnFalse: interrupt callings when a callback returns false - * - */ -jQuery.Callbacks = function( options ) { - - // Convert options from String-formatted to Object-formatted if needed - // (we check in cache first) - options = typeof options === "string" ? - createOptions( options ) : - jQuery.extend( {}, options ); - - var // Flag to know if list is currently firing - firing, - - // Last fire value for non-forgettable lists - memory, - - // Flag to know if list was already fired - fired, - - // Flag to prevent firing - locked, - - // Actual callback list - list = [], - - // Queue of execution data for repeatable lists - queue = [], - - // Index of currently firing callback (modified by add/remove as needed) - firingIndex = -1, - - // Fire callbacks - fire = function() { - - // Enforce single-firing - locked = locked || options.once; - - // Execute callbacks for all pending executions, - // respecting firingIndex overrides and runtime changes - fired = firing = true; - for ( ; queue.length; firingIndex = -1 ) { - memory = queue.shift(); - while ( ++firingIndex < list.length ) { - - // Run callback and check for early termination - if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && - options.stopOnFalse ) { - - // Jump to end and forget the data so .add doesn't re-fire - firingIndex = list.length; - memory = false; - } - } - } - - // Forget the data if we're done with it - if ( !options.memory ) { - memory = false; - } - - firing = false; - - // Clean up if we're done firing for good - if ( locked ) { - - // Keep an empty list if we have data for future add calls - if ( memory ) { - list = []; - - // Otherwise, this object is spent - } else { - list = ""; - } - } - }, - - // Actual Callbacks object - self = { - - // Add a callback or a collection of callbacks to the list - add: function() { - if ( list ) { - - // If we have memory from a past run, we should fire after adding - if ( memory && !firing ) { - firingIndex = list.length - 1; - queue.push( memory ); - } - - ( function add( args ) { - jQuery.each( args, function( _, arg ) { - if ( isFunction( arg ) ) { - if ( !options.unique || !self.has( arg ) ) { - list.push( arg ); - } - } else if ( arg && arg.length && toType( arg ) !== "string" ) { - - // Inspect recursively - add( arg ); - } - } ); - } )( arguments ); - - if ( memory && !firing ) { - fire(); - } - } - return this; - }, - - // Remove a callback from the list - remove: function() { - jQuery.each( arguments, function( _, arg ) { - var index; - while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { - list.splice( index, 1 ); - - // Handle firing indexes - if ( index <= firingIndex ) { - firingIndex--; - } - } - } ); - return this; - }, - - // Check if a given callback is in the list. - // If no argument is given, return whether or not list has callbacks attached. - has: function( fn ) { - return fn ? - jQuery.inArray( fn, list ) > -1 : - list.length > 0; - }, - - // Remove all callbacks from the list - empty: function() { - if ( list ) { - list = []; - } - return this; - }, - - // Disable .fire and .add - // Abort any current/pending executions - // Clear all callbacks and values - disable: function() { - locked = queue = []; - list = memory = ""; - return this; - }, - disabled: function() { - return !list; - }, - - // Disable .fire - // Also disable .add unless we have memory (since it would have no effect) - // Abort any pending executions - lock: function() { - locked = queue = []; - if ( !memory && !firing ) { - list = memory = ""; - } - return this; - }, - locked: function() { - return !!locked; - }, - - // Call all callbacks with the given context and arguments - fireWith: function( context, args ) { - if ( !locked ) { - args = args || []; - args = [ context, args.slice ? args.slice() : args ]; - queue.push( args ); - if ( !firing ) { - fire(); - } - } - return this; - }, - - // Call all the callbacks with the given arguments - fire: function() { - self.fireWith( this, arguments ); - return this; - }, - - // To know if the callbacks have already been called at least once - fired: function() { - return !!fired; - } - }; - - return self; -}; - - -function Identity( v ) { - return v; -} -function Thrower( ex ) { - throw ex; -} - -function adoptValue( value, resolve, reject, noValue ) { - var method; - - try { - - // Check for promise aspect first to privilege synchronous behavior - if ( value && isFunction( ( method = value.promise ) ) ) { - method.call( value ).done( resolve ).fail( reject ); - - // Other thenables - } else if ( value && isFunction( ( method = value.then ) ) ) { - method.call( value, resolve, reject ); - - // Other non-thenables - } else { - - // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer: - // * false: [ value ].slice( 0 ) => resolve( value ) - // * true: [ value ].slice( 1 ) => resolve() - resolve.apply( undefined, [ value ].slice( noValue ) ); - } - - // For Promises/A+, convert exceptions into rejections - // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in - // Deferred#then to conditionally suppress rejection. - } catch ( value ) { - - // Support: Android 4.0 only - // Strict mode functions invoked without .call/.apply get global-object context - reject.apply( undefined, [ value ] ); - } -} - -jQuery.extend( { - - Deferred: function( func ) { - var tuples = [ - - // action, add listener, callbacks, - // ... .then handlers, argument index, [final state] - [ "notify", "progress", jQuery.Callbacks( "memory" ), - jQuery.Callbacks( "memory" ), 2 ], - [ "resolve", "done", jQuery.Callbacks( "once memory" ), - jQuery.Callbacks( "once memory" ), 0, "resolved" ], - [ "reject", "fail", jQuery.Callbacks( "once memory" ), - jQuery.Callbacks( "once memory" ), 1, "rejected" ] - ], - state = "pending", - promise = { - state: function() { - return state; - }, - always: function() { - deferred.done( arguments ).fail( arguments ); - return this; - }, - "catch": function( fn ) { - return promise.then( null, fn ); - }, - - // Keep pipe for back-compat - pipe: function( /* fnDone, fnFail, fnProgress */ ) { - var fns = arguments; - - return jQuery.Deferred( function( newDefer ) { - jQuery.each( tuples, function( _i, tuple ) { - - // Map tuples (progress, done, fail) to arguments (done, fail, progress) - var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; - - // deferred.progress(function() { bind to newDefer or newDefer.notify }) - // deferred.done(function() { bind to newDefer or newDefer.resolve }) - // deferred.fail(function() { bind to newDefer or newDefer.reject }) - deferred[ tuple[ 1 ] ]( function() { - var returned = fn && fn.apply( this, arguments ); - if ( returned && isFunction( returned.promise ) ) { - returned.promise() - .progress( newDefer.notify ) - .done( newDefer.resolve ) - .fail( newDefer.reject ); - } else { - newDefer[ tuple[ 0 ] + "With" ]( - this, - fn ? [ returned ] : arguments - ); - } - } ); - } ); - fns = null; - } ).promise(); - }, - then: function( onFulfilled, onRejected, onProgress ) { - var maxDepth = 0; - function resolve( depth, deferred, handler, special ) { - return function() { - var that = this, - args = arguments, - mightThrow = function() { - var returned, then; - - // Support: Promises/A+ section 2.3.3.3.3 - // https://promisesaplus.com/#point-59 - // Ignore double-resolution attempts - if ( depth < maxDepth ) { - return; - } - - returned = handler.apply( that, args ); - - // Support: Promises/A+ section 2.3.1 - // https://promisesaplus.com/#point-48 - if ( returned === deferred.promise() ) { - throw new TypeError( "Thenable self-resolution" ); - } - - // Support: Promises/A+ sections 2.3.3.1, 3.5 - // https://promisesaplus.com/#point-54 - // https://promisesaplus.com/#point-75 - // Retrieve `then` only once - then = returned && - - // Support: Promises/A+ section 2.3.4 - // https://promisesaplus.com/#point-64 - // Only check objects and functions for thenability - ( typeof returned === "object" || - typeof returned === "function" ) && - returned.then; - - // Handle a returned thenable - if ( isFunction( then ) ) { - - // Special processors (notify) just wait for resolution - if ( special ) { - then.call( - returned, - resolve( maxDepth, deferred, Identity, special ), - resolve( maxDepth, deferred, Thrower, special ) - ); - - // Normal processors (resolve) also hook into progress - } else { - - // ...and disregard older resolution values - maxDepth++; - - then.call( - returned, - resolve( maxDepth, deferred, Identity, special ), - resolve( maxDepth, deferred, Thrower, special ), - resolve( maxDepth, deferred, Identity, - deferred.notifyWith ) - ); - } - - // Handle all other returned values - } else { - - // Only substitute handlers pass on context - // and multiple values (non-spec behavior) - if ( handler !== Identity ) { - that = undefined; - args = [ returned ]; - } - - // Process the value(s) - // Default process is resolve - ( special || deferred.resolveWith )( that, args ); - } - }, - - // Only normal processors (resolve) catch and reject exceptions - process = special ? - mightThrow : - function() { - try { - mightThrow(); - } catch ( e ) { - - if ( jQuery.Deferred.exceptionHook ) { - jQuery.Deferred.exceptionHook( e, - process.stackTrace ); - } - - // Support: Promises/A+ section 2.3.3.3.4.1 - // https://promisesaplus.com/#point-61 - // Ignore post-resolution exceptions - if ( depth + 1 >= maxDepth ) { - - // Only substitute handlers pass on context - // and multiple values (non-spec behavior) - if ( handler !== Thrower ) { - that = undefined; - args = [ e ]; - } - - deferred.rejectWith( that, args ); - } - } - }; - - // Support: Promises/A+ section 2.3.3.3.1 - // https://promisesaplus.com/#point-57 - // Re-resolve promises immediately to dodge false rejection from - // subsequent errors - if ( depth ) { - process(); - } else { - - // Call an optional hook to record the stack, in case of exception - // since it's otherwise lost when execution goes async - if ( jQuery.Deferred.getStackHook ) { - process.stackTrace = jQuery.Deferred.getStackHook(); - } - window.setTimeout( process ); - } - }; - } - - return jQuery.Deferred( function( newDefer ) { - - // progress_handlers.add( ... ) - tuples[ 0 ][ 3 ].add( - resolve( - 0, - newDefer, - isFunction( onProgress ) ? - onProgress : - Identity, - newDefer.notifyWith - ) - ); - - // fulfilled_handlers.add( ... ) - tuples[ 1 ][ 3 ].add( - resolve( - 0, - newDefer, - isFunction( onFulfilled ) ? - onFulfilled : - Identity - ) - ); - - // rejected_handlers.add( ... ) - tuples[ 2 ][ 3 ].add( - resolve( - 0, - newDefer, - isFunction( onRejected ) ? - onRejected : - Thrower - ) - ); - } ).promise(); - }, - - // Get a promise for this deferred - // If obj is provided, the promise aspect is added to the object - promise: function( obj ) { - return obj != null ? jQuery.extend( obj, promise ) : promise; - } - }, - deferred = {}; - - // Add list-specific methods - jQuery.each( tuples, function( i, tuple ) { - var list = tuple[ 2 ], - stateString = tuple[ 5 ]; - - // promise.progress = list.add - // promise.done = list.add - // promise.fail = list.add - promise[ tuple[ 1 ] ] = list.add; - - // Handle state - if ( stateString ) { - list.add( - function() { - - // state = "resolved" (i.e., fulfilled) - // state = "rejected" - state = stateString; - }, - - // rejected_callbacks.disable - // fulfilled_callbacks.disable - tuples[ 3 - i ][ 2 ].disable, - - // rejected_handlers.disable - // fulfilled_handlers.disable - tuples[ 3 - i ][ 3 ].disable, - - // progress_callbacks.lock - tuples[ 0 ][ 2 ].lock, - - // progress_handlers.lock - tuples[ 0 ][ 3 ].lock - ); - } - - // progress_handlers.fire - // fulfilled_handlers.fire - // rejected_handlers.fire - list.add( tuple[ 3 ].fire ); - - // deferred.notify = function() { deferred.notifyWith(...) } - // deferred.resolve = function() { deferred.resolveWith(...) } - // deferred.reject = function() { deferred.rejectWith(...) } - deferred[ tuple[ 0 ] ] = function() { - deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); - return this; - }; - - // deferred.notifyWith = list.fireWith - // deferred.resolveWith = list.fireWith - // deferred.rejectWith = list.fireWith - deferred[ tuple[ 0 ] + "With" ] = list.fireWith; - } ); - - // Make the deferred a promise - promise.promise( deferred ); - - // Call given func if any - if ( func ) { - func.call( deferred, deferred ); - } - - // All done! - return deferred; - }, - - // Deferred helper - when: function( singleValue ) { - var - - // count of uncompleted subordinates - remaining = arguments.length, - - // count of unprocessed arguments - i = remaining, - - // subordinate fulfillment data - resolveContexts = Array( i ), - resolveValues = slice.call( arguments ), - - // the master Deferred - master = jQuery.Deferred(), - - // subordinate callback factory - updateFunc = function( i ) { - return function( value ) { - resolveContexts[ i ] = this; - resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; - if ( !( --remaining ) ) { - master.resolveWith( resolveContexts, resolveValues ); - } - }; - }; - - // Single- and empty arguments are adopted like Promise.resolve - if ( remaining <= 1 ) { - adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject, - !remaining ); - - // Use .then() to unwrap secondary thenables (cf. gh-3000) - if ( master.state() === "pending" || - isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { - - return master.then(); - } - } - - // Multiple arguments are aggregated like Promise.all array elements - while ( i-- ) { - adoptValue( resolveValues[ i ], updateFunc( i ), master.reject ); - } - - return master.promise(); - } -} ); - - -// These usually indicate a programmer mistake during development, -// warn about them ASAP rather than swallowing them by default. -var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; - -jQuery.Deferred.exceptionHook = function( error, stack ) { - - // Support: IE 8 - 9 only - // Console exists when dev tools are open, which can happen at any time - if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { - window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack ); - } -}; - - - - -jQuery.readyException = function( error ) { - window.setTimeout( function() { - throw error; - } ); -}; - - - - -// The deferred used on DOM ready -var readyList = jQuery.Deferred(); - -jQuery.fn.ready = function( fn ) { - - readyList - .then( fn ) - - // Wrap jQuery.readyException in a function so that the lookup - // happens at the time of error handling instead of callback - // registration. - .catch( function( error ) { - jQuery.readyException( error ); - } ); - - return this; -}; - -jQuery.extend( { - - // Is the DOM ready to be used? Set to true once it occurs. - isReady: false, - - // A counter to track how many items to wait for before - // the ready event fires. See #6781 - readyWait: 1, - - // Handle when the DOM is ready - ready: function( wait ) { - - // Abort if there are pending holds or we're already ready - if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { - return; - } - - // Remember that the DOM is ready - jQuery.isReady = true; - - // If a normal DOM Ready event fired, decrement, and wait if need be - if ( wait !== true && --jQuery.readyWait > 0 ) { - return; - } - - // If there are functions bound, to execute - readyList.resolveWith( document, [ jQuery ] ); - } -} ); - -jQuery.ready.then = readyList.then; - -// The ready event handler and self cleanup method -function completed() { - document.removeEventListener( "DOMContentLoaded", completed ); - window.removeEventListener( "load", completed ); - jQuery.ready(); -} - -// Catch cases where $(document).ready() is called -// after the browser event has already occurred. -// Support: IE <=9 - 10 only -// Older IE sometimes signals "interactive" too soon -if ( document.readyState === "complete" || - ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { - - // Handle it asynchronously to allow scripts the opportunity to delay ready - window.setTimeout( jQuery.ready ); - -} else { - - // Use the handy event callback - document.addEventListener( "DOMContentLoaded", completed ); - - // A fallback to window.onload, that will always work - window.addEventListener( "load", completed ); -} - - - - -// Multifunctional method to get and set values of a collection -// The value/s can optionally be executed if it's a function -var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { - var i = 0, - len = elems.length, - bulk = key == null; - - // Sets many values - if ( toType( key ) === "object" ) { - chainable = true; - for ( i in key ) { - access( elems, fn, i, key[ i ], true, emptyGet, raw ); - } - - // Sets one value - } else if ( value !== undefined ) { - chainable = true; - - if ( !isFunction( value ) ) { - raw = true; - } - - if ( bulk ) { - - // Bulk operations run against the entire set - if ( raw ) { - fn.call( elems, value ); - fn = null; - - // ...except when executing function values - } else { - bulk = fn; - fn = function( elem, _key, value ) { - return bulk.call( jQuery( elem ), value ); - }; - } - } - - if ( fn ) { - for ( ; i < len; i++ ) { - fn( - elems[ i ], key, raw ? - value : - value.call( elems[ i ], i, fn( elems[ i ], key ) ) - ); - } - } - } - - if ( chainable ) { - return elems; - } - - // Gets - if ( bulk ) { - return fn.call( elems ); - } - - return len ? fn( elems[ 0 ], key ) : emptyGet; -}; - - -// Matches dashed string for camelizing -var rmsPrefix = /^-ms-/, - rdashAlpha = /-([a-z])/g; - -// Used by camelCase as callback to replace() -function fcamelCase( _all, letter ) { - return letter.toUpperCase(); -} - -// Convert dashed to camelCase; used by the css and data modules -// Support: IE <=9 - 11, Edge 12 - 15 -// Microsoft forgot to hump their vendor prefix (#9572) -function camelCase( string ) { - return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); -} -var acceptData = function( owner ) { - - // Accepts only: - // - Node - // - Node.ELEMENT_NODE - // - Node.DOCUMENT_NODE - // - Object - // - Any - return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); -}; - - - - -function Data() { - this.expando = jQuery.expando + Data.uid++; -} - -Data.uid = 1; - -Data.prototype = { - - cache: function( owner ) { - - // Check if the owner object already has a cache - var value = owner[ this.expando ]; - - // If not, create one - if ( !value ) { - value = {}; - - // We can accept data for non-element nodes in modern browsers, - // but we should not, see #8335. - // Always return an empty object. - if ( acceptData( owner ) ) { - - // If it is a node unlikely to be stringify-ed or looped over - // use plain assignment - if ( owner.nodeType ) { - owner[ this.expando ] = value; - - // Otherwise secure it in a non-enumerable property - // configurable must be true to allow the property to be - // deleted when data is removed - } else { - Object.defineProperty( owner, this.expando, { - value: value, - configurable: true - } ); - } - } - } - - return value; - }, - set: function( owner, data, value ) { - var prop, - cache = this.cache( owner ); - - // Handle: [ owner, key, value ] args - // Always use camelCase key (gh-2257) - if ( typeof data === "string" ) { - cache[ camelCase( data ) ] = value; - - // Handle: [ owner, { properties } ] args - } else { - - // Copy the properties one-by-one to the cache object - for ( prop in data ) { - cache[ camelCase( prop ) ] = data[ prop ]; - } - } - return cache; - }, - get: function( owner, key ) { - return key === undefined ? - this.cache( owner ) : - - // Always use camelCase key (gh-2257) - owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ]; - }, - access: function( owner, key, value ) { - - // In cases where either: - // - // 1. No key was specified - // 2. A string key was specified, but no value provided - // - // Take the "read" path and allow the get method to determine - // which value to return, respectively either: - // - // 1. The entire cache object - // 2. The data stored at the key - // - if ( key === undefined || - ( ( key && typeof key === "string" ) && value === undefined ) ) { - - return this.get( owner, key ); - } - - // When the key is not a string, or both a key and value - // are specified, set or extend (existing objects) with either: - // - // 1. An object of properties - // 2. A key and value - // - this.set( owner, key, value ); - - // Since the "set" path can have two possible entry points - // return the expected data based on which path was taken[*] - return value !== undefined ? value : key; - }, - remove: function( owner, key ) { - var i, - cache = owner[ this.expando ]; - - if ( cache === undefined ) { - return; - } - - if ( key !== undefined ) { - - // Support array or space separated string of keys - if ( Array.isArray( key ) ) { - - // If key is an array of keys... - // We always set camelCase keys, so remove that. - key = key.map( camelCase ); - } else { - key = camelCase( key ); - - // If a key with the spaces exists, use it. - // Otherwise, create an array by matching non-whitespace - key = key in cache ? - [ key ] : - ( key.match( rnothtmlwhite ) || [] ); - } - - i = key.length; - - while ( i-- ) { - delete cache[ key[ i ] ]; - } - } - - // Remove the expando if there's no more data - if ( key === undefined || jQuery.isEmptyObject( cache ) ) { - - // Support: Chrome <=35 - 45 - // Webkit & Blink performance suffers when deleting properties - // from DOM nodes, so set to undefined instead - // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted) - if ( owner.nodeType ) { - owner[ this.expando ] = undefined; - } else { - delete owner[ this.expando ]; - } - } - }, - hasData: function( owner ) { - var cache = owner[ this.expando ]; - return cache !== undefined && !jQuery.isEmptyObject( cache ); - } -}; -var dataPriv = new Data(); - -var dataUser = new Data(); - - - -// Implementation Summary -// -// 1. Enforce API surface and semantic compatibility with 1.9.x branch -// 2. Improve the module's maintainability by reducing the storage -// paths to a single mechanism. -// 3. Use the same single mechanism to support "private" and "user" data. -// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) -// 5. Avoid exposing implementation details on user objects (eg. expando properties) -// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 - -var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, - rmultiDash = /[A-Z]/g; - -function getData( data ) { - if ( data === "true" ) { - return true; - } - - if ( data === "false" ) { - return false; - } - - if ( data === "null" ) { - return null; - } - - // Only convert to a number if it doesn't change the string - if ( data === +data + "" ) { - return +data; - } - - if ( rbrace.test( data ) ) { - return JSON.parse( data ); - } - - return data; -} - -function dataAttr( elem, key, data ) { - var name; - - // If nothing was found internally, try to fetch any - // data from the HTML5 data-* attribute - if ( data === undefined && elem.nodeType === 1 ) { - name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); - data = elem.getAttribute( name ); - - if ( typeof data === "string" ) { - try { - data = getData( data ); - } catch ( e ) {} - - // Make sure we set the data so it isn't changed later - dataUser.set( elem, key, data ); - } else { - data = undefined; - } - } - return data; -} - -jQuery.extend( { - hasData: function( elem ) { - return dataUser.hasData( elem ) || dataPriv.hasData( elem ); - }, - - data: function( elem, name, data ) { - return dataUser.access( elem, name, data ); - }, - - removeData: function( elem, name ) { - dataUser.remove( elem, name ); - }, - - // TODO: Now that all calls to _data and _removeData have been replaced - // with direct calls to dataPriv methods, these can be deprecated. - _data: function( elem, name, data ) { - return dataPriv.access( elem, name, data ); - }, - - _removeData: function( elem, name ) { - dataPriv.remove( elem, name ); - } -} ); - -jQuery.fn.extend( { - data: function( key, value ) { - var i, name, data, - elem = this[ 0 ], - attrs = elem && elem.attributes; - - // Gets all values - if ( key === undefined ) { - if ( this.length ) { - data = dataUser.get( elem ); - - if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { - i = attrs.length; - while ( i-- ) { - - // Support: IE 11 only - // The attrs elements can be null (#14894) - if ( attrs[ i ] ) { - name = attrs[ i ].name; - if ( name.indexOf( "data-" ) === 0 ) { - name = camelCase( name.slice( 5 ) ); - dataAttr( elem, name, data[ name ] ); - } - } - } - dataPriv.set( elem, "hasDataAttrs", true ); - } - } - - return data; - } - - // Sets multiple values - if ( typeof key === "object" ) { - return this.each( function() { - dataUser.set( this, key ); - } ); - } - - return access( this, function( value ) { - var data; - - // The calling jQuery object (element matches) is not empty - // (and therefore has an element appears at this[ 0 ]) and the - // `value` parameter was not undefined. An empty jQuery object - // will result in `undefined` for elem = this[ 0 ] which will - // throw an exception if an attempt to read a data cache is made. - if ( elem && value === undefined ) { - - // Attempt to get data from the cache - // The key will always be camelCased in Data - data = dataUser.get( elem, key ); - if ( data !== undefined ) { - return data; - } - - // Attempt to "discover" the data in - // HTML5 custom data-* attrs - data = dataAttr( elem, key ); - if ( data !== undefined ) { - return data; - } - - // We tried really hard, but the data doesn't exist. - return; - } - - // Set the data... - this.each( function() { - - // We always store the camelCased key - dataUser.set( this, key, value ); - } ); - }, null, value, arguments.length > 1, null, true ); - }, - - removeData: function( key ) { - return this.each( function() { - dataUser.remove( this, key ); - } ); - } -} ); - - -jQuery.extend( { - queue: function( elem, type, data ) { - var queue; - - if ( elem ) { - type = ( type || "fx" ) + "queue"; - queue = dataPriv.get( elem, type ); - - // Speed up dequeue by getting out quickly if this is just a lookup - if ( data ) { - if ( !queue || Array.isArray( data ) ) { - queue = dataPriv.access( elem, type, jQuery.makeArray( data ) ); - } else { - queue.push( data ); - } - } - return queue || []; - } - }, - - dequeue: function( elem, type ) { - type = type || "fx"; - - var queue = jQuery.queue( elem, type ), - startLength = queue.length, - fn = queue.shift(), - hooks = jQuery._queueHooks( elem, type ), - next = function() { - jQuery.dequeue( elem, type ); - }; - - // If the fx queue is dequeued, always remove the progress sentinel - if ( fn === "inprogress" ) { - fn = queue.shift(); - startLength--; - } - - if ( fn ) { - - // Add a progress sentinel to prevent the fx queue from being - // automatically dequeued - if ( type === "fx" ) { - queue.unshift( "inprogress" ); - } - - // Clear up the last queue stop function - delete hooks.stop; - fn.call( elem, next, hooks ); - } - - if ( !startLength && hooks ) { - hooks.empty.fire(); - } - }, - - // Not public - generate a queueHooks object, or return the current one - _queueHooks: function( elem, type ) { - var key = type + "queueHooks"; - return dataPriv.get( elem, key ) || dataPriv.access( elem, key, { - empty: jQuery.Callbacks( "once memory" ).add( function() { - dataPriv.remove( elem, [ type + "queue", key ] ); - } ) - } ); - } -} ); - -jQuery.fn.extend( { - queue: function( type, data ) { - var setter = 2; - - if ( typeof type !== "string" ) { - data = type; - type = "fx"; - setter--; - } - - if ( arguments.length < setter ) { - return jQuery.queue( this[ 0 ], type ); - } - - return data === undefined ? - this : - this.each( function() { - var queue = jQuery.queue( this, type, data ); - - // Ensure a hooks for this queue - jQuery._queueHooks( this, type ); - - if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { - jQuery.dequeue( this, type ); - } - } ); - }, - dequeue: function( type ) { - return this.each( function() { - jQuery.dequeue( this, type ); - } ); - }, - clearQueue: function( type ) { - return this.queue( type || "fx", [] ); - }, - - // Get a promise resolved when queues of a certain type - // are emptied (fx is the type by default) - promise: function( type, obj ) { - var tmp, - count = 1, - defer = jQuery.Deferred(), - elements = this, - i = this.length, - resolve = function() { - if ( !( --count ) ) { - defer.resolveWith( elements, [ elements ] ); - } - }; - - if ( typeof type !== "string" ) { - obj = type; - type = undefined; - } - type = type || "fx"; - - while ( i-- ) { - tmp = dataPriv.get( elements[ i ], type + "queueHooks" ); - if ( tmp && tmp.empty ) { - count++; - tmp.empty.add( resolve ); - } - } - resolve(); - return defer.promise( obj ); - } -} ); -var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; - -var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); - - -var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; - -var documentElement = document.documentElement; - - - - var isAttached = function( elem ) { - return jQuery.contains( elem.ownerDocument, elem ); - }, - composed = { composed: true }; - - // Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only - // Check attachment across shadow DOM boundaries when possible (gh-3504) - // Support: iOS 10.0-10.2 only - // Early iOS 10 versions support `attachShadow` but not `getRootNode`, - // leading to errors. We need to check for `getRootNode`. - if ( documentElement.getRootNode ) { - isAttached = function( elem ) { - return jQuery.contains( elem.ownerDocument, elem ) || - elem.getRootNode( composed ) === elem.ownerDocument; - }; - } -var isHiddenWithinTree = function( elem, el ) { - - // isHiddenWithinTree might be called from jQuery#filter function; - // in that case, element will be second argument - elem = el || elem; - - // Inline style trumps all - return elem.style.display === "none" || - elem.style.display === "" && - - // Otherwise, check computed style - // Support: Firefox <=43 - 45 - // Disconnected elements can have computed display: none, so first confirm that elem is - // in the document. - isAttached( elem ) && - - jQuery.css( elem, "display" ) === "none"; - }; - - - -function adjustCSS( elem, prop, valueParts, tween ) { - var adjusted, scale, - maxIterations = 20, - currentValue = tween ? - function() { - return tween.cur(); - } : - function() { - return jQuery.css( elem, prop, "" ); - }, - initial = currentValue(), - unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), - - // Starting value computation is required for potential unit mismatches - initialInUnit = elem.nodeType && - ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && - rcssNum.exec( jQuery.css( elem, prop ) ); - - if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { - - // Support: Firefox <=54 - // Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144) - initial = initial / 2; - - // Trust units reported by jQuery.css - unit = unit || initialInUnit[ 3 ]; - - // Iteratively approximate from a nonzero starting point - initialInUnit = +initial || 1; - - while ( maxIterations-- ) { - - // Evaluate and update our best guess (doubling guesses that zero out). - // Finish if the scale equals or crosses 1 (making the old*new product non-positive). - jQuery.style( elem, prop, initialInUnit + unit ); - if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) { - maxIterations = 0; - } - initialInUnit = initialInUnit / scale; - - } - - initialInUnit = initialInUnit * 2; - jQuery.style( elem, prop, initialInUnit + unit ); - - // Make sure we update the tween properties later on - valueParts = valueParts || []; - } - - if ( valueParts ) { - initialInUnit = +initialInUnit || +initial || 0; - - // Apply relative offset (+=/-=) if specified - adjusted = valueParts[ 1 ] ? - initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : - +valueParts[ 2 ]; - if ( tween ) { - tween.unit = unit; - tween.start = initialInUnit; - tween.end = adjusted; - } - } - return adjusted; -} - - -var defaultDisplayMap = {}; - -function getDefaultDisplay( elem ) { - var temp, - doc = elem.ownerDocument, - nodeName = elem.nodeName, - display = defaultDisplayMap[ nodeName ]; - - if ( display ) { - return display; - } - - temp = doc.body.appendChild( doc.createElement( nodeName ) ); - display = jQuery.css( temp, "display" ); - - temp.parentNode.removeChild( temp ); - - if ( display === "none" ) { - display = "block"; - } - defaultDisplayMap[ nodeName ] = display; - - return display; -} - -function showHide( elements, show ) { - var display, elem, - values = [], - index = 0, - length = elements.length; - - // Determine new display value for elements that need to change - for ( ; index < length; index++ ) { - elem = elements[ index ]; - if ( !elem.style ) { - continue; - } - - display = elem.style.display; - if ( show ) { - - // Since we force visibility upon cascade-hidden elements, an immediate (and slow) - // check is required in this first loop unless we have a nonempty display value (either - // inline or about-to-be-restored) - if ( display === "none" ) { - values[ index ] = dataPriv.get( elem, "display" ) || null; - if ( !values[ index ] ) { - elem.style.display = ""; - } - } - if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) { - values[ index ] = getDefaultDisplay( elem ); - } - } else { - if ( display !== "none" ) { - values[ index ] = "none"; - - // Remember what we're overwriting - dataPriv.set( elem, "display", display ); - } - } - } - - // Set the display of the elements in a second loop to avoid constant reflow - for ( index = 0; index < length; index++ ) { - if ( values[ index ] != null ) { - elements[ index ].style.display = values[ index ]; - } - } - - return elements; -} - -jQuery.fn.extend( { - show: function() { - return showHide( this, true ); - }, - hide: function() { - return showHide( this ); - }, - toggle: function( state ) { - if ( typeof state === "boolean" ) { - return state ? this.show() : this.hide(); - } - - return this.each( function() { - if ( isHiddenWithinTree( this ) ) { - jQuery( this ).show(); - } else { - jQuery( this ).hide(); - } - } ); - } -} ); -var rcheckableType = ( /^(?:checkbox|radio)$/i ); - -var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i ); - -var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i ); - - - -( function() { - var fragment = document.createDocumentFragment(), - div = fragment.appendChild( document.createElement( "div" ) ), - input = document.createElement( "input" ); - - // Support: Android 4.0 - 4.3 only - // Check state lost if the name is set (#11217) - // Support: Windows Web Apps (WWA) - // `name` and `type` must use .setAttribute for WWA (#14901) - input.setAttribute( "type", "radio" ); - input.setAttribute( "checked", "checked" ); - input.setAttribute( "name", "t" ); - - div.appendChild( input ); - - // Support: Android <=4.1 only - // Older WebKit doesn't clone checked state correctly in fragments - support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; - - // Support: IE <=11 only - // Make sure textarea (and checkbox) defaultValue is properly cloned - div.innerHTML = ""; - support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; - - // Support: IE <=9 only - // IE <=9 replaces "; - support.option = !!div.lastChild; -} )(); - - -// We have to close these tags to support XHTML (#13200) -var wrapMap = { - - // XHTML parsers do not magically insert elements in the - // same way that tag soup parsers do. So we cannot shorten - // this by omitting or other required elements. - thead: [ 1, "", "
" ], - col: [ 2, "", "
" ], - tr: [ 2, "", "
" ], - td: [ 3, "", "
" ], - - _default: [ 0, "", "" ] -}; - -wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; -wrapMap.th = wrapMap.td; - -// Support: IE <=9 only -if ( !support.option ) { - wrapMap.optgroup = wrapMap.option = [ 1, "" ]; -} - - -function getAll( context, tag ) { - - // Support: IE <=9 - 11 only - // Use typeof to avoid zero-argument method invocation on host objects (#15151) - var ret; - - if ( typeof context.getElementsByTagName !== "undefined" ) { - ret = context.getElementsByTagName( tag || "*" ); - - } else if ( typeof context.querySelectorAll !== "undefined" ) { - ret = context.querySelectorAll( tag || "*" ); - - } else { - ret = []; - } - - if ( tag === undefined || tag && nodeName( context, tag ) ) { - return jQuery.merge( [ context ], ret ); - } - - return ret; -} - - -// Mark scripts as having already been evaluated -function setGlobalEval( elems, refElements ) { - var i = 0, - l = elems.length; - - for ( ; i < l; i++ ) { - dataPriv.set( - elems[ i ], - "globalEval", - !refElements || dataPriv.get( refElements[ i ], "globalEval" ) - ); - } -} - - -var rhtml = /<|&#?\w+;/; - -function buildFragment( elems, context, scripts, selection, ignored ) { - var elem, tmp, tag, wrap, attached, j, - fragment = context.createDocumentFragment(), - nodes = [], - i = 0, - l = elems.length; - - for ( ; i < l; i++ ) { - elem = elems[ i ]; - - if ( elem || elem === 0 ) { - - // Add nodes directly - if ( toType( elem ) === "object" ) { - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); - - // Convert non-html into a text node - } else if ( !rhtml.test( elem ) ) { - nodes.push( context.createTextNode( elem ) ); - - // Convert html into DOM nodes - } else { - tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); - - // Deserialize a standard representation - tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); - wrap = wrapMap[ tag ] || wrapMap._default; - tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; - - // Descend through wrappers to the right content - j = wrap[ 0 ]; - while ( j-- ) { - tmp = tmp.lastChild; - } - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - jQuery.merge( nodes, tmp.childNodes ); - - // Remember the top-level container - tmp = fragment.firstChild; - - // Ensure the created nodes are orphaned (#12392) - tmp.textContent = ""; - } - } - } - - // Remove wrapper from fragment - fragment.textContent = ""; - - i = 0; - while ( ( elem = nodes[ i++ ] ) ) { - - // Skip elements already in the context collection (trac-4087) - if ( selection && jQuery.inArray( elem, selection ) > -1 ) { - if ( ignored ) { - ignored.push( elem ); - } - continue; - } - - attached = isAttached( elem ); - - // Append to fragment - tmp = getAll( fragment.appendChild( elem ), "script" ); - - // Preserve script evaluation history - if ( attached ) { - setGlobalEval( tmp ); - } - - // Capture executables - if ( scripts ) { - j = 0; - while ( ( elem = tmp[ j++ ] ) ) { - if ( rscriptType.test( elem.type || "" ) ) { - scripts.push( elem ); - } - } - } - } - - return fragment; -} - - -var - rkeyEvent = /^key/, - rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/, - rtypenamespace = /^([^.]*)(?:\.(.+)|)/; - -function returnTrue() { - return true; -} - -function returnFalse() { - return false; -} - -// Support: IE <=9 - 11+ -// focus() and blur() are asynchronous, except when they are no-op. -// So expect focus to be synchronous when the element is already active, -// and blur to be synchronous when the element is not already active. -// (focus and blur are always synchronous in other supported browsers, -// this just defines when we can count on it). -function expectSync( elem, type ) { - return ( elem === safeActiveElement() ) === ( type === "focus" ); -} - -// Support: IE <=9 only -// Accessing document.activeElement can throw unexpectedly -// https://bugs.jquery.com/ticket/13393 -function safeActiveElement() { - try { - return document.activeElement; - } catch ( err ) { } -} - -function on( elem, types, selector, data, fn, one ) { - var origFn, type; - - // Types can be a map of types/handlers - if ( typeof types === "object" ) { - - // ( types-Object, selector, data ) - if ( typeof selector !== "string" ) { - - // ( types-Object, data ) - data = data || selector; - selector = undefined; - } - for ( type in types ) { - on( elem, type, selector, data, types[ type ], one ); - } - return elem; - } - - if ( data == null && fn == null ) { - - // ( types, fn ) - fn = selector; - data = selector = undefined; - } else if ( fn == null ) { - if ( typeof selector === "string" ) { - - // ( types, selector, fn ) - fn = data; - data = undefined; - } else { - - // ( types, data, fn ) - fn = data; - data = selector; - selector = undefined; - } - } - if ( fn === false ) { - fn = returnFalse; - } else if ( !fn ) { - return elem; - } - - if ( one === 1 ) { - origFn = fn; - fn = function( event ) { - - // Can use an empty set, since event contains the info - jQuery().off( event ); - return origFn.apply( this, arguments ); - }; - - // Use same guid so caller can remove using origFn - fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); - } - return elem.each( function() { - jQuery.event.add( this, types, fn, data, selector ); - } ); -} - -/* - * Helper functions for managing events -- not part of the public interface. - * Props to Dean Edwards' addEvent library for many of the ideas. - */ -jQuery.event = { - - global: {}, - - add: function( elem, types, handler, data, selector ) { - - var handleObjIn, eventHandle, tmp, - events, t, handleObj, - special, handlers, type, namespaces, origType, - elemData = dataPriv.get( elem ); - - // Only attach events to objects that accept data - if ( !acceptData( elem ) ) { - return; - } - - // Caller can pass in an object of custom data in lieu of the handler - if ( handler.handler ) { - handleObjIn = handler; - handler = handleObjIn.handler; - selector = handleObjIn.selector; - } - - // Ensure that invalid selectors throw exceptions at attach time - // Evaluate against documentElement in case elem is a non-element node (e.g., document) - if ( selector ) { - jQuery.find.matchesSelector( documentElement, selector ); - } - - // Make sure that the handler has a unique ID, used to find/remove it later - if ( !handler.guid ) { - handler.guid = jQuery.guid++; - } - - // Init the element's event structure and main handler, if this is the first - if ( !( events = elemData.events ) ) { - events = elemData.events = Object.create( null ); - } - if ( !( eventHandle = elemData.handle ) ) { - eventHandle = elemData.handle = function( e ) { - - // Discard the second event of a jQuery.event.trigger() and - // when an event is called after a page has unloaded - return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? - jQuery.event.dispatch.apply( elem, arguments ) : undefined; - }; - } - - // Handle multiple events separated by a space - types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; - t = types.length; - while ( t-- ) { - tmp = rtypenamespace.exec( types[ t ] ) || []; - type = origType = tmp[ 1 ]; - namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); - - // There *must* be a type, no attaching namespace-only handlers - if ( !type ) { - continue; - } - - // If event changes its type, use the special event handlers for the changed type - special = jQuery.event.special[ type ] || {}; - - // If selector defined, determine special event api type, otherwise given type - type = ( selector ? special.delegateType : special.bindType ) || type; - - // Update special based on newly reset type - special = jQuery.event.special[ type ] || {}; - - // handleObj is passed to all event handlers - handleObj = jQuery.extend( { - type: type, - origType: origType, - data: data, - handler: handler, - guid: handler.guid, - selector: selector, - needsContext: selector && jQuery.expr.match.needsContext.test( selector ), - namespace: namespaces.join( "." ) - }, handleObjIn ); - - // Init the event handler queue if we're the first - if ( !( handlers = events[ type ] ) ) { - handlers = events[ type ] = []; - handlers.delegateCount = 0; - - // Only use addEventListener if the special events handler returns false - if ( !special.setup || - special.setup.call( elem, data, namespaces, eventHandle ) === false ) { - - if ( elem.addEventListener ) { - elem.addEventListener( type, eventHandle ); - } - } - } - - if ( special.add ) { - special.add.call( elem, handleObj ); - - if ( !handleObj.handler.guid ) { - handleObj.handler.guid = handler.guid; - } - } - - // Add to the element's handler list, delegates in front - if ( selector ) { - handlers.splice( handlers.delegateCount++, 0, handleObj ); - } else { - handlers.push( handleObj ); - } - - // Keep track of which events have ever been used, for event optimization - jQuery.event.global[ type ] = true; - } - - }, - - // Detach an event or set of events from an element - remove: function( elem, types, handler, selector, mappedTypes ) { - - var j, origCount, tmp, - events, t, handleObj, - special, handlers, type, namespaces, origType, - elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); - - if ( !elemData || !( events = elemData.events ) ) { - return; - } - - // Once for each type.namespace in types; type may be omitted - types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; - t = types.length; - while ( t-- ) { - tmp = rtypenamespace.exec( types[ t ] ) || []; - type = origType = tmp[ 1 ]; - namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); - - // Unbind all events (on this namespace, if provided) for the element - if ( !type ) { - for ( type in events ) { - jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); - } - continue; - } - - special = jQuery.event.special[ type ] || {}; - type = ( selector ? special.delegateType : special.bindType ) || type; - handlers = events[ type ] || []; - tmp = tmp[ 2 ] && - new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); - - // Remove matching events - origCount = j = handlers.length; - while ( j-- ) { - handleObj = handlers[ j ]; - - if ( ( mappedTypes || origType === handleObj.origType ) && - ( !handler || handler.guid === handleObj.guid ) && - ( !tmp || tmp.test( handleObj.namespace ) ) && - ( !selector || selector === handleObj.selector || - selector === "**" && handleObj.selector ) ) { - handlers.splice( j, 1 ); - - if ( handleObj.selector ) { - handlers.delegateCount--; - } - if ( special.remove ) { - special.remove.call( elem, handleObj ); - } - } - } - - // Remove generic event handler if we removed something and no more handlers exist - // (avoids potential for endless recursion during removal of special event handlers) - if ( origCount && !handlers.length ) { - if ( !special.teardown || - special.teardown.call( elem, namespaces, elemData.handle ) === false ) { - - jQuery.removeEvent( elem, type, elemData.handle ); - } - - delete events[ type ]; - } - } - - // Remove data and the expando if it's no longer used - if ( jQuery.isEmptyObject( events ) ) { - dataPriv.remove( elem, "handle events" ); - } - }, - - dispatch: function( nativeEvent ) { - - var i, j, ret, matched, handleObj, handlerQueue, - args = new Array( arguments.length ), - - // Make a writable jQuery.Event from the native event object - event = jQuery.event.fix( nativeEvent ), - - handlers = ( - dataPriv.get( this, "events" ) || Object.create( null ) - )[ event.type ] || [], - special = jQuery.event.special[ event.type ] || {}; - - // Use the fix-ed jQuery.Event rather than the (read-only) native event - args[ 0 ] = event; - - for ( i = 1; i < arguments.length; i++ ) { - args[ i ] = arguments[ i ]; - } - - event.delegateTarget = this; - - // Call the preDispatch hook for the mapped type, and let it bail if desired - if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { - return; - } - - // Determine handlers - handlerQueue = jQuery.event.handlers.call( this, event, handlers ); - - // Run delegates first; they may want to stop propagation beneath us - i = 0; - while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { - event.currentTarget = matched.elem; - - j = 0; - while ( ( handleObj = matched.handlers[ j++ ] ) && - !event.isImmediatePropagationStopped() ) { - - // If the event is namespaced, then each handler is only invoked if it is - // specially universal or its namespaces are a superset of the event's. - if ( !event.rnamespace || handleObj.namespace === false || - event.rnamespace.test( handleObj.namespace ) ) { - - event.handleObj = handleObj; - event.data = handleObj.data; - - ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || - handleObj.handler ).apply( matched.elem, args ); - - if ( ret !== undefined ) { - if ( ( event.result = ret ) === false ) { - event.preventDefault(); - event.stopPropagation(); - } - } - } - } - } - - // Call the postDispatch hook for the mapped type - if ( special.postDispatch ) { - special.postDispatch.call( this, event ); - } - - return event.result; - }, - - handlers: function( event, handlers ) { - var i, handleObj, sel, matchedHandlers, matchedSelectors, - handlerQueue = [], - delegateCount = handlers.delegateCount, - cur = event.target; - - // Find delegate handlers - if ( delegateCount && - - // Support: IE <=9 - // Black-hole SVG instance trees (trac-13180) - cur.nodeType && - - // Support: Firefox <=42 - // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) - // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click - // Support: IE 11 only - // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) - !( event.type === "click" && event.button >= 1 ) ) { - - for ( ; cur !== this; cur = cur.parentNode || this ) { - - // Don't check non-elements (#13208) - // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) - if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { - matchedHandlers = []; - matchedSelectors = {}; - for ( i = 0; i < delegateCount; i++ ) { - handleObj = handlers[ i ]; - - // Don't conflict with Object.prototype properties (#13203) - sel = handleObj.selector + " "; - - if ( matchedSelectors[ sel ] === undefined ) { - matchedSelectors[ sel ] = handleObj.needsContext ? - jQuery( sel, this ).index( cur ) > -1 : - jQuery.find( sel, this, null, [ cur ] ).length; - } - if ( matchedSelectors[ sel ] ) { - matchedHandlers.push( handleObj ); - } - } - if ( matchedHandlers.length ) { - handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); - } - } - } - } - - // Add the remaining (directly-bound) handlers - cur = this; - if ( delegateCount < handlers.length ) { - handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); - } - - return handlerQueue; - }, - - addProp: function( name, hook ) { - Object.defineProperty( jQuery.Event.prototype, name, { - enumerable: true, - configurable: true, - - get: isFunction( hook ) ? - function() { - if ( this.originalEvent ) { - return hook( this.originalEvent ); - } - } : - function() { - if ( this.originalEvent ) { - return this.originalEvent[ name ]; - } - }, - - set: function( value ) { - Object.defineProperty( this, name, { - enumerable: true, - configurable: true, - writable: true, - value: value - } ); - } - } ); - }, - - fix: function( originalEvent ) { - return originalEvent[ jQuery.expando ] ? - originalEvent : - new jQuery.Event( originalEvent ); - }, - - special: { - load: { - - // Prevent triggered image.load events from bubbling to window.load - noBubble: true - }, - click: { - - // Utilize native event to ensure correct state for checkable inputs - setup: function( data ) { - - // For mutual compressibility with _default, replace `this` access with a local var. - // `|| data` is dead code meant only to preserve the variable through minification. - var el = this || data; - - // Claim the first handler - if ( rcheckableType.test( el.type ) && - el.click && nodeName( el, "input" ) ) { - - // dataPriv.set( el, "click", ... ) - leverageNative( el, "click", returnTrue ); - } - - // Return false to allow normal processing in the caller - return false; - }, - trigger: function( data ) { - - // For mutual compressibility with _default, replace `this` access with a local var. - // `|| data` is dead code meant only to preserve the variable through minification. - var el = this || data; - - // Force setup before triggering a click - if ( rcheckableType.test( el.type ) && - el.click && nodeName( el, "input" ) ) { - - leverageNative( el, "click" ); - } - - // Return non-false to allow normal event-path propagation - return true; - }, - - // For cross-browser consistency, suppress native .click() on links - // Also prevent it if we're currently inside a leveraged native-event stack - _default: function( event ) { - var target = event.target; - return rcheckableType.test( target.type ) && - target.click && nodeName( target, "input" ) && - dataPriv.get( target, "click" ) || - nodeName( target, "a" ); - } - }, - - beforeunload: { - postDispatch: function( event ) { - - // Support: Firefox 20+ - // Firefox doesn't alert if the returnValue field is not set. - if ( event.result !== undefined && event.originalEvent ) { - event.originalEvent.returnValue = event.result; - } - } - } - } -}; - -// Ensure the presence of an event listener that handles manually-triggered -// synthetic events by interrupting progress until reinvoked in response to -// *native* events that it fires directly, ensuring that state changes have -// already occurred before other listeners are invoked. -function leverageNative( el, type, expectSync ) { - - // Missing expectSync indicates a trigger call, which must force setup through jQuery.event.add - if ( !expectSync ) { - if ( dataPriv.get( el, type ) === undefined ) { - jQuery.event.add( el, type, returnTrue ); - } - return; - } - - // Register the controller as a special universal handler for all event namespaces - dataPriv.set( el, type, false ); - jQuery.event.add( el, type, { - namespace: false, - handler: function( event ) { - var notAsync, result, - saved = dataPriv.get( this, type ); - - if ( ( event.isTrigger & 1 ) && this[ type ] ) { - - // Interrupt processing of the outer synthetic .trigger()ed event - // Saved data should be false in such cases, but might be a leftover capture object - // from an async native handler (gh-4350) - if ( !saved.length ) { - - // Store arguments for use when handling the inner native event - // There will always be at least one argument (an event object), so this array - // will not be confused with a leftover capture object. - saved = slice.call( arguments ); - dataPriv.set( this, type, saved ); - - // Trigger the native event and capture its result - // Support: IE <=9 - 11+ - // focus() and blur() are asynchronous - notAsync = expectSync( this, type ); - this[ type ](); - result = dataPriv.get( this, type ); - if ( saved !== result || notAsync ) { - dataPriv.set( this, type, false ); - } else { - result = {}; - } - if ( saved !== result ) { - - // Cancel the outer synthetic event - event.stopImmediatePropagation(); - event.preventDefault(); - return result.value; - } - - // If this is an inner synthetic event for an event with a bubbling surrogate - // (focus or blur), assume that the surrogate already propagated from triggering the - // native event and prevent that from happening again here. - // This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the - // bubbling surrogate propagates *after* the non-bubbling base), but that seems - // less bad than duplication. - } else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) { - event.stopPropagation(); - } - - // If this is a native event triggered above, everything is now in order - // Fire an inner synthetic event with the original arguments - } else if ( saved.length ) { - - // ...and capture the result - dataPriv.set( this, type, { - value: jQuery.event.trigger( - - // Support: IE <=9 - 11+ - // Extend with the prototype to reset the above stopImmediatePropagation() - jQuery.extend( saved[ 0 ], jQuery.Event.prototype ), - saved.slice( 1 ), - this - ) - } ); - - // Abort handling of the native event - event.stopImmediatePropagation(); - } - } - } ); -} - -jQuery.removeEvent = function( elem, type, handle ) { - - // This "if" is needed for plain objects - if ( elem.removeEventListener ) { - elem.removeEventListener( type, handle ); - } -}; - -jQuery.Event = function( src, props ) { - - // Allow instantiation without the 'new' keyword - if ( !( this instanceof jQuery.Event ) ) { - return new jQuery.Event( src, props ); - } - - // Event object - if ( src && src.type ) { - this.originalEvent = src; - this.type = src.type; - - // Events bubbling up the document may have been marked as prevented - // by a handler lower down the tree; reflect the correct value. - this.isDefaultPrevented = src.defaultPrevented || - src.defaultPrevented === undefined && - - // Support: Android <=2.3 only - src.returnValue === false ? - returnTrue : - returnFalse; - - // Create target properties - // Support: Safari <=6 - 7 only - // Target should not be a text node (#504, #13143) - this.target = ( src.target && src.target.nodeType === 3 ) ? - src.target.parentNode : - src.target; - - this.currentTarget = src.currentTarget; - this.relatedTarget = src.relatedTarget; - - // Event type - } else { - this.type = src; - } - - // Put explicitly provided properties onto the event object - if ( props ) { - jQuery.extend( this, props ); - } - - // Create a timestamp if incoming event doesn't have one - this.timeStamp = src && src.timeStamp || Date.now(); - - // Mark it as fixed - this[ jQuery.expando ] = true; -}; - -// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding -// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html -jQuery.Event.prototype = { - constructor: jQuery.Event, - isDefaultPrevented: returnFalse, - isPropagationStopped: returnFalse, - isImmediatePropagationStopped: returnFalse, - isSimulated: false, - - preventDefault: function() { - var e = this.originalEvent; - - this.isDefaultPrevented = returnTrue; - - if ( e && !this.isSimulated ) { - e.preventDefault(); - } - }, - stopPropagation: function() { - var e = this.originalEvent; - - this.isPropagationStopped = returnTrue; - - if ( e && !this.isSimulated ) { - e.stopPropagation(); - } - }, - stopImmediatePropagation: function() { - var e = this.originalEvent; - - this.isImmediatePropagationStopped = returnTrue; - - if ( e && !this.isSimulated ) { - e.stopImmediatePropagation(); - } - - this.stopPropagation(); - } -}; - -// Includes all common event props including KeyEvent and MouseEvent specific props -jQuery.each( { - altKey: true, - bubbles: true, - cancelable: true, - changedTouches: true, - ctrlKey: true, - detail: true, - eventPhase: true, - metaKey: true, - pageX: true, - pageY: true, - shiftKey: true, - view: true, - "char": true, - code: true, - charCode: true, - key: true, - keyCode: true, - button: true, - buttons: true, - clientX: true, - clientY: true, - offsetX: true, - offsetY: true, - pointerId: true, - pointerType: true, - screenX: true, - screenY: true, - targetTouches: true, - toElement: true, - touches: true, - - which: function( event ) { - var button = event.button; - - // Add which for key events - if ( event.which == null && rkeyEvent.test( event.type ) ) { - return event.charCode != null ? event.charCode : event.keyCode; - } - - // Add which for click: 1 === left; 2 === middle; 3 === right - if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) { - if ( button & 1 ) { - return 1; - } - - if ( button & 2 ) { - return 3; - } - - if ( button & 4 ) { - return 2; - } - - return 0; - } - - return event.which; - } -}, jQuery.event.addProp ); - -jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) { - jQuery.event.special[ type ] = { - - // Utilize native event if possible so blur/focus sequence is correct - setup: function() { - - // Claim the first handler - // dataPriv.set( this, "focus", ... ) - // dataPriv.set( this, "blur", ... ) - leverageNative( this, type, expectSync ); - - // Return false to allow normal processing in the caller - return false; - }, - trigger: function() { - - // Force setup before trigger - leverageNative( this, type ); - - // Return non-false to allow normal event-path propagation - return true; - }, - - delegateType: delegateType - }; -} ); - -// Create mouseenter/leave events using mouseover/out and event-time checks -// so that event delegation works in jQuery. -// Do the same for pointerenter/pointerleave and pointerover/pointerout -// -// Support: Safari 7 only -// Safari sends mouseenter too often; see: -// https://bugs.chromium.org/p/chromium/issues/detail?id=470258 -// for the description of the bug (it existed in older Chrome versions as well). -jQuery.each( { - mouseenter: "mouseover", - mouseleave: "mouseout", - pointerenter: "pointerover", - pointerleave: "pointerout" -}, function( orig, fix ) { - jQuery.event.special[ orig ] = { - delegateType: fix, - bindType: fix, - - handle: function( event ) { - var ret, - target = this, - related = event.relatedTarget, - handleObj = event.handleObj; - - // For mouseenter/leave call the handler if related is outside the target. - // NB: No relatedTarget if the mouse left/entered the browser window - if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { - event.type = handleObj.origType; - ret = handleObj.handler.apply( this, arguments ); - event.type = fix; - } - return ret; - } - }; -} ); - -jQuery.fn.extend( { - - on: function( types, selector, data, fn ) { - return on( this, types, selector, data, fn ); - }, - one: function( types, selector, data, fn ) { - return on( this, types, selector, data, fn, 1 ); - }, - off: function( types, selector, fn ) { - var handleObj, type; - if ( types && types.preventDefault && types.handleObj ) { - - // ( event ) dispatched jQuery.Event - handleObj = types.handleObj; - jQuery( types.delegateTarget ).off( - handleObj.namespace ? - handleObj.origType + "." + handleObj.namespace : - handleObj.origType, - handleObj.selector, - handleObj.handler - ); - return this; - } - if ( typeof types === "object" ) { - - // ( types-object [, selector] ) - for ( type in types ) { - this.off( type, selector, types[ type ] ); - } - return this; - } - if ( selector === false || typeof selector === "function" ) { - - // ( types [, fn] ) - fn = selector; - selector = undefined; - } - if ( fn === false ) { - fn = returnFalse; - } - return this.each( function() { - jQuery.event.remove( this, types, fn, selector ); - } ); - } -} ); - - -var - - // Support: IE <=10 - 11, Edge 12 - 13 only - // In IE/Edge using regex groups here causes severe slowdowns. - // See https://connect.microsoft.com/IE/feedback/details/1736512/ - rnoInnerhtml = /\s*$/g; - -// Prefer a tbody over its parent table for containing new rows -function manipulationTarget( elem, content ) { - if ( nodeName( elem, "table" ) && - nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { - - return jQuery( elem ).children( "tbody" )[ 0 ] || elem; - } - - return elem; -} - -// Replace/restore the type attribute of script elements for safe DOM manipulation -function disableScript( elem ) { - elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; - return elem; -} -function restoreScript( elem ) { - if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) { - elem.type = elem.type.slice( 5 ); - } else { - elem.removeAttribute( "type" ); - } - - return elem; -} - -function cloneCopyEvent( src, dest ) { - var i, l, type, pdataOld, udataOld, udataCur, events; - - if ( dest.nodeType !== 1 ) { - return; - } - - // 1. Copy private data: events, handlers, etc. - if ( dataPriv.hasData( src ) ) { - pdataOld = dataPriv.get( src ); - events = pdataOld.events; - - if ( events ) { - dataPriv.remove( dest, "handle events" ); - - for ( type in events ) { - for ( i = 0, l = events[ type ].length; i < l; i++ ) { - jQuery.event.add( dest, type, events[ type ][ i ] ); - } - } - } - } - - // 2. Copy user data - if ( dataUser.hasData( src ) ) { - udataOld = dataUser.access( src ); - udataCur = jQuery.extend( {}, udataOld ); - - dataUser.set( dest, udataCur ); - } -} - -// Fix IE bugs, see support tests -function fixInput( src, dest ) { - var nodeName = dest.nodeName.toLowerCase(); - - // Fails to persist the checked state of a cloned checkbox or radio button. - if ( nodeName === "input" && rcheckableType.test( src.type ) ) { - dest.checked = src.checked; - - // Fails to return the selected option to the default selected state when cloning options - } else if ( nodeName === "input" || nodeName === "textarea" ) { - dest.defaultValue = src.defaultValue; - } -} - -function domManip( collection, args, callback, ignored ) { - - // Flatten any nested arrays - args = flat( args ); - - var fragment, first, scripts, hasScripts, node, doc, - i = 0, - l = collection.length, - iNoClone = l - 1, - value = args[ 0 ], - valueIsFunction = isFunction( value ); - - // We can't cloneNode fragments that contain checked, in WebKit - if ( valueIsFunction || - ( l > 1 && typeof value === "string" && - !support.checkClone && rchecked.test( value ) ) ) { - return collection.each( function( index ) { - var self = collection.eq( index ); - if ( valueIsFunction ) { - args[ 0 ] = value.call( this, index, self.html() ); - } - domManip( self, args, callback, ignored ); - } ); - } - - if ( l ) { - fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); - first = fragment.firstChild; - - if ( fragment.childNodes.length === 1 ) { - fragment = first; - } - - // Require either new content or an interest in ignored elements to invoke the callback - if ( first || ignored ) { - scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); - hasScripts = scripts.length; - - // Use the original fragment for the last item - // instead of the first because it can end up - // being emptied incorrectly in certain situations (#8070). - for ( ; i < l; i++ ) { - node = fragment; - - if ( i !== iNoClone ) { - node = jQuery.clone( node, true, true ); - - // Keep references to cloned scripts for later restoration - if ( hasScripts ) { - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - jQuery.merge( scripts, getAll( node, "script" ) ); - } - } - - callback.call( collection[ i ], node, i ); - } - - if ( hasScripts ) { - doc = scripts[ scripts.length - 1 ].ownerDocument; - - // Reenable scripts - jQuery.map( scripts, restoreScript ); - - // Evaluate executable scripts on first document insertion - for ( i = 0; i < hasScripts; i++ ) { - node = scripts[ i ]; - if ( rscriptType.test( node.type || "" ) && - !dataPriv.access( node, "globalEval" ) && - jQuery.contains( doc, node ) ) { - - if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) { - - // Optional AJAX dependency, but won't run scripts if not present - if ( jQuery._evalUrl && !node.noModule ) { - jQuery._evalUrl( node.src, { - nonce: node.nonce || node.getAttribute( "nonce" ) - }, doc ); - } - } else { - DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc ); - } - } - } - } - } - } - - return collection; -} - -function remove( elem, selector, keepData ) { - var node, - nodes = selector ? jQuery.filter( selector, elem ) : elem, - i = 0; - - for ( ; ( node = nodes[ i ] ) != null; i++ ) { - if ( !keepData && node.nodeType === 1 ) { - jQuery.cleanData( getAll( node ) ); - } - - if ( node.parentNode ) { - if ( keepData && isAttached( node ) ) { - setGlobalEval( getAll( node, "script" ) ); - } - node.parentNode.removeChild( node ); - } - } - - return elem; -} - -jQuery.extend( { - htmlPrefilter: function( html ) { - return html; - }, - - clone: function( elem, dataAndEvents, deepDataAndEvents ) { - var i, l, srcElements, destElements, - clone = elem.cloneNode( true ), - inPage = isAttached( elem ); - - // Fix IE cloning issues - if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && - !jQuery.isXMLDoc( elem ) ) { - - // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2 - destElements = getAll( clone ); - srcElements = getAll( elem ); - - for ( i = 0, l = srcElements.length; i < l; i++ ) { - fixInput( srcElements[ i ], destElements[ i ] ); - } - } - - // Copy the events from the original to the clone - if ( dataAndEvents ) { - if ( deepDataAndEvents ) { - srcElements = srcElements || getAll( elem ); - destElements = destElements || getAll( clone ); - - for ( i = 0, l = srcElements.length; i < l; i++ ) { - cloneCopyEvent( srcElements[ i ], destElements[ i ] ); - } - } else { - cloneCopyEvent( elem, clone ); - } - } - - // Preserve script evaluation history - destElements = getAll( clone, "script" ); - if ( destElements.length > 0 ) { - setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); - } - - // Return the cloned set - return clone; - }, - - cleanData: function( elems ) { - var data, elem, type, - special = jQuery.event.special, - i = 0; - - for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { - if ( acceptData( elem ) ) { - if ( ( data = elem[ dataPriv.expando ] ) ) { - if ( data.events ) { - for ( type in data.events ) { - if ( special[ type ] ) { - jQuery.event.remove( elem, type ); - - // This is a shortcut to avoid jQuery.event.remove's overhead - } else { - jQuery.removeEvent( elem, type, data.handle ); - } - } - } - - // Support: Chrome <=35 - 45+ - // Assign undefined instead of using delete, see Data#remove - elem[ dataPriv.expando ] = undefined; - } - if ( elem[ dataUser.expando ] ) { - - // Support: Chrome <=35 - 45+ - // Assign undefined instead of using delete, see Data#remove - elem[ dataUser.expando ] = undefined; - } - } - } - } -} ); - -jQuery.fn.extend( { - detach: function( selector ) { - return remove( this, selector, true ); - }, - - remove: function( selector ) { - return remove( this, selector ); - }, - - text: function( value ) { - return access( this, function( value ) { - return value === undefined ? - jQuery.text( this ) : - this.empty().each( function() { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - this.textContent = value; - } - } ); - }, null, value, arguments.length ); - }, - - append: function() { - return domManip( this, arguments, function( elem ) { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - var target = manipulationTarget( this, elem ); - target.appendChild( elem ); - } - } ); - }, - - prepend: function() { - return domManip( this, arguments, function( elem ) { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - var target = manipulationTarget( this, elem ); - target.insertBefore( elem, target.firstChild ); - } - } ); - }, - - before: function() { - return domManip( this, arguments, function( elem ) { - if ( this.parentNode ) { - this.parentNode.insertBefore( elem, this ); - } - } ); - }, - - after: function() { - return domManip( this, arguments, function( elem ) { - if ( this.parentNode ) { - this.parentNode.insertBefore( elem, this.nextSibling ); - } - } ); - }, - - empty: function() { - var elem, - i = 0; - - for ( ; ( elem = this[ i ] ) != null; i++ ) { - if ( elem.nodeType === 1 ) { - - // Prevent memory leaks - jQuery.cleanData( getAll( elem, false ) ); - - // Remove any remaining nodes - elem.textContent = ""; - } - } - - return this; - }, - - clone: function( dataAndEvents, deepDataAndEvents ) { - dataAndEvents = dataAndEvents == null ? false : dataAndEvents; - deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; - - return this.map( function() { - return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); - } ); - }, - - html: function( value ) { - return access( this, function( value ) { - var elem = this[ 0 ] || {}, - i = 0, - l = this.length; - - if ( value === undefined && elem.nodeType === 1 ) { - return elem.innerHTML; - } - - // See if we can take a shortcut and just use innerHTML - if ( typeof value === "string" && !rnoInnerhtml.test( value ) && - !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { - - value = jQuery.htmlPrefilter( value ); - - try { - for ( ; i < l; i++ ) { - elem = this[ i ] || {}; - - // Remove element nodes and prevent memory leaks - if ( elem.nodeType === 1 ) { - jQuery.cleanData( getAll( elem, false ) ); - elem.innerHTML = value; - } - } - - elem = 0; - - // If using innerHTML throws an exception, use the fallback method - } catch ( e ) {} - } - - if ( elem ) { - this.empty().append( value ); - } - }, null, value, arguments.length ); - }, - - replaceWith: function() { - var ignored = []; - - // Make the changes, replacing each non-ignored context element with the new content - return domManip( this, arguments, function( elem ) { - var parent = this.parentNode; - - if ( jQuery.inArray( this, ignored ) < 0 ) { - jQuery.cleanData( getAll( this ) ); - if ( parent ) { - parent.replaceChild( elem, this ); - } - } - - // Force callback invocation - }, ignored ); - } -} ); - -jQuery.each( { - appendTo: "append", - prependTo: "prepend", - insertBefore: "before", - insertAfter: "after", - replaceAll: "replaceWith" -}, function( name, original ) { - jQuery.fn[ name ] = function( selector ) { - var elems, - ret = [], - insert = jQuery( selector ), - last = insert.length - 1, - i = 0; - - for ( ; i <= last; i++ ) { - elems = i === last ? this : this.clone( true ); - jQuery( insert[ i ] )[ original ]( elems ); - - // Support: Android <=4.0 only, PhantomJS 1 only - // .get() because push.apply(_, arraylike) throws on ancient WebKit - push.apply( ret, elems.get() ); - } - - return this.pushStack( ret ); - }; -} ); -var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); - -var getStyles = function( elem ) { - - // Support: IE <=11 only, Firefox <=30 (#15098, #14150) - // IE throws on elements created in popups - // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" - var view = elem.ownerDocument.defaultView; - - if ( !view || !view.opener ) { - view = window; - } - - return view.getComputedStyle( elem ); - }; - -var swap = function( elem, options, callback ) { - var ret, name, - old = {}; - - // Remember the old values, and insert the new ones - for ( name in options ) { - old[ name ] = elem.style[ name ]; - elem.style[ name ] = options[ name ]; - } - - ret = callback.call( elem ); - - // Revert the old values - for ( name in options ) { - elem.style[ name ] = old[ name ]; - } - - return ret; -}; - - -var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); - - - -( function() { - - // Executing both pixelPosition & boxSizingReliable tests require only one layout - // so they're executed at the same time to save the second computation. - function computeStyleTests() { - - // This is a singleton, we need to execute it only once - if ( !div ) { - return; - } - - container.style.cssText = "position:absolute;left:-11111px;width:60px;" + - "margin-top:1px;padding:0;border:0"; - div.style.cssText = - "position:relative;display:block;box-sizing:border-box;overflow:scroll;" + - "margin:auto;border:1px;padding:1px;" + - "width:60%;top:1%"; - documentElement.appendChild( container ).appendChild( div ); - - var divStyle = window.getComputedStyle( div ); - pixelPositionVal = divStyle.top !== "1%"; - - // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 - reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12; - - // Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3 - // Some styles come back with percentage values, even though they shouldn't - div.style.right = "60%"; - pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36; - - // Support: IE 9 - 11 only - // Detect misreporting of content dimensions for box-sizing:border-box elements - boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36; - - // Support: IE 9 only - // Detect overflow:scroll screwiness (gh-3699) - // Support: Chrome <=64 - // Don't get tricked when zoom affects offsetWidth (gh-4029) - div.style.position = "absolute"; - scrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12; - - documentElement.removeChild( container ); - - // Nullify the div so it wouldn't be stored in the memory and - // it will also be a sign that checks already performed - div = null; - } - - function roundPixelMeasures( measure ) { - return Math.round( parseFloat( measure ) ); - } - - var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal, - reliableTrDimensionsVal, reliableMarginLeftVal, - container = document.createElement( "div" ), - div = document.createElement( "div" ); - - // Finish early in limited (non-browser) environments - if ( !div.style ) { - return; - } - - // Support: IE <=9 - 11 only - // Style of cloned element affects source element cloned (#8908) - div.style.backgroundClip = "content-box"; - div.cloneNode( true ).style.backgroundClip = ""; - support.clearCloneStyle = div.style.backgroundClip === "content-box"; - - jQuery.extend( support, { - boxSizingReliable: function() { - computeStyleTests(); - return boxSizingReliableVal; - }, - pixelBoxStyles: function() { - computeStyleTests(); - return pixelBoxStylesVal; - }, - pixelPosition: function() { - computeStyleTests(); - return pixelPositionVal; - }, - reliableMarginLeft: function() { - computeStyleTests(); - return reliableMarginLeftVal; - }, - scrollboxSize: function() { - computeStyleTests(); - return scrollboxSizeVal; - }, - - // Support: IE 9 - 11+, Edge 15 - 18+ - // IE/Edge misreport `getComputedStyle` of table rows with width/height - // set in CSS while `offset*` properties report correct values. - // Behavior in IE 9 is more subtle than in newer versions & it passes - // some versions of this test; make sure not to make it pass there! - reliableTrDimensions: function() { - var table, tr, trChild, trStyle; - if ( reliableTrDimensionsVal == null ) { - table = document.createElement( "table" ); - tr = document.createElement( "tr" ); - trChild = document.createElement( "div" ); - - table.style.cssText = "position:absolute;left:-11111px"; - tr.style.height = "1px"; - trChild.style.height = "9px"; - - documentElement - .appendChild( table ) - .appendChild( tr ) - .appendChild( trChild ); - - trStyle = window.getComputedStyle( tr ); - reliableTrDimensionsVal = parseInt( trStyle.height ) > 3; - - documentElement.removeChild( table ); - } - return reliableTrDimensionsVal; - } - } ); -} )(); - - -function curCSS( elem, name, computed ) { - var width, minWidth, maxWidth, ret, - - // Support: Firefox 51+ - // Retrieving style before computed somehow - // fixes an issue with getting wrong values - // on detached elements - style = elem.style; - - computed = computed || getStyles( elem ); - - // getPropertyValue is needed for: - // .css('filter') (IE 9 only, #12537) - // .css('--customProperty) (#3144) - if ( computed ) { - ret = computed.getPropertyValue( name ) || computed[ name ]; - - if ( ret === "" && !isAttached( elem ) ) { - ret = jQuery.style( elem, name ); - } - - // A tribute to the "awesome hack by Dean Edwards" - // Android Browser returns percentage for some values, - // but width seems to be reliably pixels. - // This is against the CSSOM draft spec: - // https://drafts.csswg.org/cssom/#resolved-values - if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) { - - // Remember the original values - width = style.width; - minWidth = style.minWidth; - maxWidth = style.maxWidth; - - // Put in the new values to get a computed value out - style.minWidth = style.maxWidth = style.width = ret; - ret = computed.width; - - // Revert the changed values - style.width = width; - style.minWidth = minWidth; - style.maxWidth = maxWidth; - } - } - - return ret !== undefined ? - - // Support: IE <=9 - 11 only - // IE returns zIndex value as an integer. - ret + "" : - ret; -} - - -function addGetHookIf( conditionFn, hookFn ) { - - // Define the hook, we'll check on the first run if it's really needed. - return { - get: function() { - if ( conditionFn() ) { - - // Hook not needed (or it's not possible to use it due - // to missing dependency), remove it. - delete this.get; - return; - } - - // Hook needed; redefine it so that the support test is not executed again. - return ( this.get = hookFn ).apply( this, arguments ); - } - }; -} - - -var cssPrefixes = [ "Webkit", "Moz", "ms" ], - emptyStyle = document.createElement( "div" ).style, - vendorProps = {}; - -// Return a vendor-prefixed property or undefined -function vendorPropName( name ) { - - // Check for vendor prefixed names - var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), - i = cssPrefixes.length; - - while ( i-- ) { - name = cssPrefixes[ i ] + capName; - if ( name in emptyStyle ) { - return name; - } - } -} - -// Return a potentially-mapped jQuery.cssProps or vendor prefixed property -function finalPropName( name ) { - var final = jQuery.cssProps[ name ] || vendorProps[ name ]; - - if ( final ) { - return final; - } - if ( name in emptyStyle ) { - return name; - } - return vendorProps[ name ] = vendorPropName( name ) || name; -} - - -var - - // Swappable if display is none or starts with table - // except "table", "table-cell", or "table-caption" - // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display - rdisplayswap = /^(none|table(?!-c[ea]).+)/, - rcustomProp = /^--/, - cssShow = { position: "absolute", visibility: "hidden", display: "block" }, - cssNormalTransform = { - letterSpacing: "0", - fontWeight: "400" - }; - -function setPositiveNumber( _elem, value, subtract ) { - - // Any relative (+/-) values have already been - // normalized at this point - var matches = rcssNum.exec( value ); - return matches ? - - // Guard against undefined "subtract", e.g., when used as in cssHooks - Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : - value; -} - -function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) { - var i = dimension === "width" ? 1 : 0, - extra = 0, - delta = 0; - - // Adjustment may not be necessary - if ( box === ( isBorderBox ? "border" : "content" ) ) { - return 0; - } - - for ( ; i < 4; i += 2 ) { - - // Both box models exclude margin - if ( box === "margin" ) { - delta += jQuery.css( elem, box + cssExpand[ i ], true, styles ); - } - - // If we get here with a content-box, we're seeking "padding" or "border" or "margin" - if ( !isBorderBox ) { - - // Add padding - delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); - - // For "border" or "margin", add border - if ( box !== "padding" ) { - delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); - - // But still keep track of it otherwise - } else { - extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); - } - - // If we get here with a border-box (content + padding + border), we're seeking "content" or - // "padding" or "margin" - } else { - - // For "content", subtract padding - if ( box === "content" ) { - delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); - } - - // For "content" or "padding", subtract border - if ( box !== "margin" ) { - delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); - } - } - } - - // Account for positive content-box scroll gutter when requested by providing computedVal - if ( !isBorderBox && computedVal >= 0 ) { - - // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border - // Assuming integer scroll gutter, subtract the rest and round down - delta += Math.max( 0, Math.ceil( - elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - - computedVal - - delta - - extra - - 0.5 - - // If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter - // Use an explicit zero to avoid NaN (gh-3964) - ) ) || 0; - } - - return delta; -} - -function getWidthOrHeight( elem, dimension, extra ) { - - // Start with computed style - var styles = getStyles( elem ), - - // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322). - // Fake content-box until we know it's needed to know the true value. - boxSizingNeeded = !support.boxSizingReliable() || extra, - isBorderBox = boxSizingNeeded && - jQuery.css( elem, "boxSizing", false, styles ) === "border-box", - valueIsBorderBox = isBorderBox, - - val = curCSS( elem, dimension, styles ), - offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ); - - // Support: Firefox <=54 - // Return a confounding non-pixel value or feign ignorance, as appropriate. - if ( rnumnonpx.test( val ) ) { - if ( !extra ) { - return val; - } - val = "auto"; - } - - - // Support: IE 9 - 11 only - // Use offsetWidth/offsetHeight for when box sizing is unreliable. - // In those cases, the computed value can be trusted to be border-box. - if ( ( !support.boxSizingReliable() && isBorderBox || - - // Support: IE 10 - 11+, Edge 15 - 18+ - // IE/Edge misreport `getComputedStyle` of table rows with width/height - // set in CSS while `offset*` properties report correct values. - // Interestingly, in some cases IE 9 doesn't suffer from this issue. - !support.reliableTrDimensions() && nodeName( elem, "tr" ) || - - // Fall back to offsetWidth/offsetHeight when value is "auto" - // This happens for inline elements with no explicit setting (gh-3571) - val === "auto" || - - // Support: Android <=4.1 - 4.3 only - // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602) - !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) && - - // Make sure the element is visible & connected - elem.getClientRects().length ) { - - isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; - - // Where available, offsetWidth/offsetHeight approximate border box dimensions. - // Where not available (e.g., SVG), assume unreliable box-sizing and interpret the - // retrieved value as a content box dimension. - valueIsBorderBox = offsetProp in elem; - if ( valueIsBorderBox ) { - val = elem[ offsetProp ]; - } - } - - // Normalize "" and auto - val = parseFloat( val ) || 0; - - // Adjust for the element's box model - return ( val + - boxModelAdjustment( - elem, - dimension, - extra || ( isBorderBox ? "border" : "content" ), - valueIsBorderBox, - styles, - - // Provide the current computed size to request scroll gutter calculation (gh-3589) - val - ) - ) + "px"; -} - -jQuery.extend( { - - // Add in style property hooks for overriding the default - // behavior of getting and setting a style property - cssHooks: { - opacity: { - get: function( elem, computed ) { - if ( computed ) { - - // We should always get a number back from opacity - var ret = curCSS( elem, "opacity" ); - return ret === "" ? "1" : ret; - } - } - } - }, - - // Don't automatically add "px" to these possibly-unitless properties - cssNumber: { - "animationIterationCount": true, - "columnCount": true, - "fillOpacity": true, - "flexGrow": true, - "flexShrink": true, - "fontWeight": true, - "gridArea": true, - "gridColumn": true, - "gridColumnEnd": true, - "gridColumnStart": true, - "gridRow": true, - "gridRowEnd": true, - "gridRowStart": true, - "lineHeight": true, - "opacity": true, - "order": true, - "orphans": true, - "widows": true, - "zIndex": true, - "zoom": true - }, - - // Add in properties whose names you wish to fix before - // setting or getting the value - cssProps: {}, - - // Get and set the style property on a DOM Node - style: function( elem, name, value, extra ) { - - // Don't set styles on text and comment nodes - if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { - return; - } - - // Make sure that we're working with the right name - var ret, type, hooks, - origName = camelCase( name ), - isCustomProp = rcustomProp.test( name ), - style = elem.style; - - // Make sure that we're working with the right name. We don't - // want to query the value if it is a CSS custom property - // since they are user-defined. - if ( !isCustomProp ) { - name = finalPropName( origName ); - } - - // Gets hook for the prefixed version, then unprefixed version - hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; - - // Check if we're setting a value - if ( value !== undefined ) { - type = typeof value; - - // Convert "+=" or "-=" to relative numbers (#7345) - if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { - value = adjustCSS( elem, name, ret ); - - // Fixes bug #9237 - type = "number"; - } - - // Make sure that null and NaN values aren't set (#7116) - if ( value == null || value !== value ) { - return; - } - - // If a number was passed in, add the unit (except for certain CSS properties) - // The isCustomProp check can be removed in jQuery 4.0 when we only auto-append - // "px" to a few hardcoded values. - if ( type === "number" && !isCustomProp ) { - value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); - } - - // background-* props affect original clone's values - if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { - style[ name ] = "inherit"; - } - - // If a hook was provided, use that value, otherwise just set the specified value - if ( !hooks || !( "set" in hooks ) || - ( value = hooks.set( elem, value, extra ) ) !== undefined ) { - - if ( isCustomProp ) { - style.setProperty( name, value ); - } else { - style[ name ] = value; - } - } - - } else { - - // If a hook was provided get the non-computed value from there - if ( hooks && "get" in hooks && - ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { - - return ret; - } - - // Otherwise just get the value from the style object - return style[ name ]; - } - }, - - css: function( elem, name, extra, styles ) { - var val, num, hooks, - origName = camelCase( name ), - isCustomProp = rcustomProp.test( name ); - - // Make sure that we're working with the right name. We don't - // want to modify the value if it is a CSS custom property - // since they are user-defined. - if ( !isCustomProp ) { - name = finalPropName( origName ); - } - - // Try prefixed name followed by the unprefixed name - hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; - - // If a hook was provided get the computed value from there - if ( hooks && "get" in hooks ) { - val = hooks.get( elem, true, extra ); - } - - // Otherwise, if a way to get the computed value exists, use that - if ( val === undefined ) { - val = curCSS( elem, name, styles ); - } - - // Convert "normal" to computed value - if ( val === "normal" && name in cssNormalTransform ) { - val = cssNormalTransform[ name ]; - } - - // Make numeric if forced or a qualifier was provided and val looks numeric - if ( extra === "" || extra ) { - num = parseFloat( val ); - return extra === true || isFinite( num ) ? num || 0 : val; - } - - return val; - } -} ); - -jQuery.each( [ "height", "width" ], function( _i, dimension ) { - jQuery.cssHooks[ dimension ] = { - get: function( elem, computed, extra ) { - if ( computed ) { - - // Certain elements can have dimension info if we invisibly show them - // but it must have a current display style that would benefit - return rdisplayswap.test( jQuery.css( elem, "display" ) ) && - - // Support: Safari 8+ - // Table columns in Safari have non-zero offsetWidth & zero - // getBoundingClientRect().width unless display is changed. - // Support: IE <=11 only - // Running getBoundingClientRect on a disconnected node - // in IE throws an error. - ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? - swap( elem, cssShow, function() { - return getWidthOrHeight( elem, dimension, extra ); - } ) : - getWidthOrHeight( elem, dimension, extra ); - } - }, - - set: function( elem, value, extra ) { - var matches, - styles = getStyles( elem ), - - // Only read styles.position if the test has a chance to fail - // to avoid forcing a reflow. - scrollboxSizeBuggy = !support.scrollboxSize() && - styles.position === "absolute", - - // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991) - boxSizingNeeded = scrollboxSizeBuggy || extra, - isBorderBox = boxSizingNeeded && - jQuery.css( elem, "boxSizing", false, styles ) === "border-box", - subtract = extra ? - boxModelAdjustment( - elem, - dimension, - extra, - isBorderBox, - styles - ) : - 0; - - // Account for unreliable border-box dimensions by comparing offset* to computed and - // faking a content-box to get border and padding (gh-3699) - if ( isBorderBox && scrollboxSizeBuggy ) { - subtract -= Math.ceil( - elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - - parseFloat( styles[ dimension ] ) - - boxModelAdjustment( elem, dimension, "border", false, styles ) - - 0.5 - ); - } - - // Convert to pixels if value adjustment is needed - if ( subtract && ( matches = rcssNum.exec( value ) ) && - ( matches[ 3 ] || "px" ) !== "px" ) { - - elem.style[ dimension ] = value; - value = jQuery.css( elem, dimension ); - } - - return setPositiveNumber( elem, value, subtract ); - } - }; -} ); - -jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft, - function( elem, computed ) { - if ( computed ) { - return ( parseFloat( curCSS( elem, "marginLeft" ) ) || - elem.getBoundingClientRect().left - - swap( elem, { marginLeft: 0 }, function() { - return elem.getBoundingClientRect().left; - } ) - ) + "px"; - } - } -); - -// These hooks are used by animate to expand properties -jQuery.each( { - margin: "", - padding: "", - border: "Width" -}, function( prefix, suffix ) { - jQuery.cssHooks[ prefix + suffix ] = { - expand: function( value ) { - var i = 0, - expanded = {}, - - // Assumes a single number if not a string - parts = typeof value === "string" ? value.split( " " ) : [ value ]; - - for ( ; i < 4; i++ ) { - expanded[ prefix + cssExpand[ i ] + suffix ] = - parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; - } - - return expanded; - } - }; - - if ( prefix !== "margin" ) { - jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; - } -} ); - -jQuery.fn.extend( { - css: function( name, value ) { - return access( this, function( elem, name, value ) { - var styles, len, - map = {}, - i = 0; - - if ( Array.isArray( name ) ) { - styles = getStyles( elem ); - len = name.length; - - for ( ; i < len; i++ ) { - map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); - } - - return map; - } - - return value !== undefined ? - jQuery.style( elem, name, value ) : - jQuery.css( elem, name ); - }, name, value, arguments.length > 1 ); - } -} ); - - -function Tween( elem, options, prop, end, easing ) { - return new Tween.prototype.init( elem, options, prop, end, easing ); -} -jQuery.Tween = Tween; - -Tween.prototype = { - constructor: Tween, - init: function( elem, options, prop, end, easing, unit ) { - this.elem = elem; - this.prop = prop; - this.easing = easing || jQuery.easing._default; - this.options = options; - this.start = this.now = this.cur(); - this.end = end; - this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); - }, - cur: function() { - var hooks = Tween.propHooks[ this.prop ]; - - return hooks && hooks.get ? - hooks.get( this ) : - Tween.propHooks._default.get( this ); - }, - run: function( percent ) { - var eased, - hooks = Tween.propHooks[ this.prop ]; - - if ( this.options.duration ) { - this.pos = eased = jQuery.easing[ this.easing ]( - percent, this.options.duration * percent, 0, 1, this.options.duration - ); - } else { - this.pos = eased = percent; - } - this.now = ( this.end - this.start ) * eased + this.start; - - if ( this.options.step ) { - this.options.step.call( this.elem, this.now, this ); - } - - if ( hooks && hooks.set ) { - hooks.set( this ); - } else { - Tween.propHooks._default.set( this ); - } - return this; - } -}; - -Tween.prototype.init.prototype = Tween.prototype; - -Tween.propHooks = { - _default: { - get: function( tween ) { - var result; - - // Use a property on the element directly when it is not a DOM element, - // or when there is no matching style property that exists. - if ( tween.elem.nodeType !== 1 || - tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) { - return tween.elem[ tween.prop ]; - } - - // Passing an empty string as a 3rd parameter to .css will automatically - // attempt a parseFloat and fallback to a string if the parse fails. - // Simple values such as "10px" are parsed to Float; - // complex values such as "rotate(1rad)" are returned as-is. - result = jQuery.css( tween.elem, tween.prop, "" ); - - // Empty strings, null, undefined and "auto" are converted to 0. - return !result || result === "auto" ? 0 : result; - }, - set: function( tween ) { - - // Use step hook for back compat. - // Use cssHook if its there. - // Use .style if available and use plain properties where available. - if ( jQuery.fx.step[ tween.prop ] ) { - jQuery.fx.step[ tween.prop ]( tween ); - } else if ( tween.elem.nodeType === 1 && ( - jQuery.cssHooks[ tween.prop ] || - tween.elem.style[ finalPropName( tween.prop ) ] != null ) ) { - jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); - } else { - tween.elem[ tween.prop ] = tween.now; - } - } - } -}; - -// Support: IE <=9 only -// Panic based approach to setting things on disconnected nodes -Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { - set: function( tween ) { - if ( tween.elem.nodeType && tween.elem.parentNode ) { - tween.elem[ tween.prop ] = tween.now; - } - } -}; - -jQuery.easing = { - linear: function( p ) { - return p; - }, - swing: function( p ) { - return 0.5 - Math.cos( p * Math.PI ) / 2; - }, - _default: "swing" -}; - -jQuery.fx = Tween.prototype.init; - -// Back compat <1.8 extension point -jQuery.fx.step = {}; - - - - -var - fxNow, inProgress, - rfxtypes = /^(?:toggle|show|hide)$/, - rrun = /queueHooks$/; - -function schedule() { - if ( inProgress ) { - if ( document.hidden === false && window.requestAnimationFrame ) { - window.requestAnimationFrame( schedule ); - } else { - window.setTimeout( schedule, jQuery.fx.interval ); - } - - jQuery.fx.tick(); - } -} - -// Animations created synchronously will run synchronously -function createFxNow() { - window.setTimeout( function() { - fxNow = undefined; - } ); - return ( fxNow = Date.now() ); -} - -// Generate parameters to create a standard animation -function genFx( type, includeWidth ) { - var which, - i = 0, - attrs = { height: type }; - - // If we include width, step value is 1 to do all cssExpand values, - // otherwise step value is 2 to skip over Left and Right - includeWidth = includeWidth ? 1 : 0; - for ( ; i < 4; i += 2 - includeWidth ) { - which = cssExpand[ i ]; - attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; - } - - if ( includeWidth ) { - attrs.opacity = attrs.width = type; - } - - return attrs; -} - -function createTween( value, prop, animation ) { - var tween, - collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ), - index = 0, - length = collection.length; - for ( ; index < length; index++ ) { - if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) { - - // We're done with this property - return tween; - } - } -} - -function defaultPrefilter( elem, props, opts ) { - var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, - isBox = "width" in props || "height" in props, - anim = this, - orig = {}, - style = elem.style, - hidden = elem.nodeType && isHiddenWithinTree( elem ), - dataShow = dataPriv.get( elem, "fxshow" ); - - // Queue-skipping animations hijack the fx hooks - if ( !opts.queue ) { - hooks = jQuery._queueHooks( elem, "fx" ); - if ( hooks.unqueued == null ) { - hooks.unqueued = 0; - oldfire = hooks.empty.fire; - hooks.empty.fire = function() { - if ( !hooks.unqueued ) { - oldfire(); - } - }; - } - hooks.unqueued++; - - anim.always( function() { - - // Ensure the complete handler is called before this completes - anim.always( function() { - hooks.unqueued--; - if ( !jQuery.queue( elem, "fx" ).length ) { - hooks.empty.fire(); - } - } ); - } ); - } - - // Detect show/hide animations - for ( prop in props ) { - value = props[ prop ]; - if ( rfxtypes.test( value ) ) { - delete props[ prop ]; - toggle = toggle || value === "toggle"; - if ( value === ( hidden ? "hide" : "show" ) ) { - - // Pretend to be hidden if this is a "show" and - // there is still data from a stopped show/hide - if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { - hidden = true; - - // Ignore all other no-op show/hide data - } else { - continue; - } - } - orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); - } - } - - // Bail out if this is a no-op like .hide().hide() - propTween = !jQuery.isEmptyObject( props ); - if ( !propTween && jQuery.isEmptyObject( orig ) ) { - return; - } - - // Restrict "overflow" and "display" styles during box animations - if ( isBox && elem.nodeType === 1 ) { - - // Support: IE <=9 - 11, Edge 12 - 15 - // Record all 3 overflow attributes because IE does not infer the shorthand - // from identically-valued overflowX and overflowY and Edge just mirrors - // the overflowX value there. - opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; - - // Identify a display type, preferring old show/hide data over the CSS cascade - restoreDisplay = dataShow && dataShow.display; - if ( restoreDisplay == null ) { - restoreDisplay = dataPriv.get( elem, "display" ); - } - display = jQuery.css( elem, "display" ); - if ( display === "none" ) { - if ( restoreDisplay ) { - display = restoreDisplay; - } else { - - // Get nonempty value(s) by temporarily forcing visibility - showHide( [ elem ], true ); - restoreDisplay = elem.style.display || restoreDisplay; - display = jQuery.css( elem, "display" ); - showHide( [ elem ] ); - } - } - - // Animate inline elements as inline-block - if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { - if ( jQuery.css( elem, "float" ) === "none" ) { - - // Restore the original display value at the end of pure show/hide animations - if ( !propTween ) { - anim.done( function() { - style.display = restoreDisplay; - } ); - if ( restoreDisplay == null ) { - display = style.display; - restoreDisplay = display === "none" ? "" : display; - } - } - style.display = "inline-block"; - } - } - } - - if ( opts.overflow ) { - style.overflow = "hidden"; - anim.always( function() { - style.overflow = opts.overflow[ 0 ]; - style.overflowX = opts.overflow[ 1 ]; - style.overflowY = opts.overflow[ 2 ]; - } ); - } - - // Implement show/hide animations - propTween = false; - for ( prop in orig ) { - - // General show/hide setup for this element animation - if ( !propTween ) { - if ( dataShow ) { - if ( "hidden" in dataShow ) { - hidden = dataShow.hidden; - } - } else { - dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); - } - - // Store hidden/visible for toggle so `.stop().toggle()` "reverses" - if ( toggle ) { - dataShow.hidden = !hidden; - } - - // Show elements before animating them - if ( hidden ) { - showHide( [ elem ], true ); - } - - /* eslint-disable no-loop-func */ - - anim.done( function() { - - /* eslint-enable no-loop-func */ - - // The final step of a "hide" animation is actually hiding the element - if ( !hidden ) { - showHide( [ elem ] ); - } - dataPriv.remove( elem, "fxshow" ); - for ( prop in orig ) { - jQuery.style( elem, prop, orig[ prop ] ); - } - } ); - } - - // Per-property setup - propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); - if ( !( prop in dataShow ) ) { - dataShow[ prop ] = propTween.start; - if ( hidden ) { - propTween.end = propTween.start; - propTween.start = 0; - } - } - } -} - -function propFilter( props, specialEasing ) { - var index, name, easing, value, hooks; - - // camelCase, specialEasing and expand cssHook pass - for ( index in props ) { - name = camelCase( index ); - easing = specialEasing[ name ]; - value = props[ index ]; - if ( Array.isArray( value ) ) { - easing = value[ 1 ]; - value = props[ index ] = value[ 0 ]; - } - - if ( index !== name ) { - props[ name ] = value; - delete props[ index ]; - } - - hooks = jQuery.cssHooks[ name ]; - if ( hooks && "expand" in hooks ) { - value = hooks.expand( value ); - delete props[ name ]; - - // Not quite $.extend, this won't overwrite existing keys. - // Reusing 'index' because we have the correct "name" - for ( index in value ) { - if ( !( index in props ) ) { - props[ index ] = value[ index ]; - specialEasing[ index ] = easing; - } - } - } else { - specialEasing[ name ] = easing; - } - } -} - -function Animation( elem, properties, options ) { - var result, - stopped, - index = 0, - length = Animation.prefilters.length, - deferred = jQuery.Deferred().always( function() { - - // Don't match elem in the :animated selector - delete tick.elem; - } ), - tick = function() { - if ( stopped ) { - return false; - } - var currentTime = fxNow || createFxNow(), - remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), - - // Support: Android 2.3 only - // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497) - temp = remaining / animation.duration || 0, - percent = 1 - temp, - index = 0, - length = animation.tweens.length; - - for ( ; index < length; index++ ) { - animation.tweens[ index ].run( percent ); - } - - deferred.notifyWith( elem, [ animation, percent, remaining ] ); - - // If there's more to do, yield - if ( percent < 1 && length ) { - return remaining; - } - - // If this was an empty animation, synthesize a final progress notification - if ( !length ) { - deferred.notifyWith( elem, [ animation, 1, 0 ] ); - } - - // Resolve the animation and report its conclusion - deferred.resolveWith( elem, [ animation ] ); - return false; - }, - animation = deferred.promise( { - elem: elem, - props: jQuery.extend( {}, properties ), - opts: jQuery.extend( true, { - specialEasing: {}, - easing: jQuery.easing._default - }, options ), - originalProperties: properties, - originalOptions: options, - startTime: fxNow || createFxNow(), - duration: options.duration, - tweens: [], - createTween: function( prop, end ) { - var tween = jQuery.Tween( elem, animation.opts, prop, end, - animation.opts.specialEasing[ prop ] || animation.opts.easing ); - animation.tweens.push( tween ); - return tween; - }, - stop: function( gotoEnd ) { - var index = 0, - - // If we are going to the end, we want to run all the tweens - // otherwise we skip this part - length = gotoEnd ? animation.tweens.length : 0; - if ( stopped ) { - return this; - } - stopped = true; - for ( ; index < length; index++ ) { - animation.tweens[ index ].run( 1 ); - } - - // Resolve when we played the last frame; otherwise, reject - if ( gotoEnd ) { - deferred.notifyWith( elem, [ animation, 1, 0 ] ); - deferred.resolveWith( elem, [ animation, gotoEnd ] ); - } else { - deferred.rejectWith( elem, [ animation, gotoEnd ] ); - } - return this; - } - } ), - props = animation.props; - - propFilter( props, animation.opts.specialEasing ); - - for ( ; index < length; index++ ) { - result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); - if ( result ) { - if ( isFunction( result.stop ) ) { - jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = - result.stop.bind( result ); - } - return result; - } - } - - jQuery.map( props, createTween, animation ); - - if ( isFunction( animation.opts.start ) ) { - animation.opts.start.call( elem, animation ); - } - - // Attach callbacks from options - animation - .progress( animation.opts.progress ) - .done( animation.opts.done, animation.opts.complete ) - .fail( animation.opts.fail ) - .always( animation.opts.always ); - - jQuery.fx.timer( - jQuery.extend( tick, { - elem: elem, - anim: animation, - queue: animation.opts.queue - } ) - ); - - return animation; -} - -jQuery.Animation = jQuery.extend( Animation, { - - tweeners: { - "*": [ function( prop, value ) { - var tween = this.createTween( prop, value ); - adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); - return tween; - } ] - }, - - tweener: function( props, callback ) { - if ( isFunction( props ) ) { - callback = props; - props = [ "*" ]; - } else { - props = props.match( rnothtmlwhite ); - } - - var prop, - index = 0, - length = props.length; - - for ( ; index < length; index++ ) { - prop = props[ index ]; - Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; - Animation.tweeners[ prop ].unshift( callback ); - } - }, - - prefilters: [ defaultPrefilter ], - - prefilter: function( callback, prepend ) { - if ( prepend ) { - Animation.prefilters.unshift( callback ); - } else { - Animation.prefilters.push( callback ); - } - } -} ); - -jQuery.speed = function( speed, easing, fn ) { - var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { - complete: fn || !fn && easing || - isFunction( speed ) && speed, - duration: speed, - easing: fn && easing || easing && !isFunction( easing ) && easing - }; - - // Go to the end state if fx are off - if ( jQuery.fx.off ) { - opt.duration = 0; - - } else { - if ( typeof opt.duration !== "number" ) { - if ( opt.duration in jQuery.fx.speeds ) { - opt.duration = jQuery.fx.speeds[ opt.duration ]; - - } else { - opt.duration = jQuery.fx.speeds._default; - } - } - } - - // Normalize opt.queue - true/undefined/null -> "fx" - if ( opt.queue == null || opt.queue === true ) { - opt.queue = "fx"; - } - - // Queueing - opt.old = opt.complete; - - opt.complete = function() { - if ( isFunction( opt.old ) ) { - opt.old.call( this ); - } - - if ( opt.queue ) { - jQuery.dequeue( this, opt.queue ); - } - }; - - return opt; -}; - -jQuery.fn.extend( { - fadeTo: function( speed, to, easing, callback ) { - - // Show any hidden elements after setting opacity to 0 - return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show() - - // Animate to the value specified - .end().animate( { opacity: to }, speed, easing, callback ); - }, - animate: function( prop, speed, easing, callback ) { - var empty = jQuery.isEmptyObject( prop ), - optall = jQuery.speed( speed, easing, callback ), - doAnimation = function() { - - // Operate on a copy of prop so per-property easing won't be lost - var anim = Animation( this, jQuery.extend( {}, prop ), optall ); - - // Empty animations, or finishing resolves immediately - if ( empty || dataPriv.get( this, "finish" ) ) { - anim.stop( true ); - } - }; - doAnimation.finish = doAnimation; - - return empty || optall.queue === false ? - this.each( doAnimation ) : - this.queue( optall.queue, doAnimation ); - }, - stop: function( type, clearQueue, gotoEnd ) { - var stopQueue = function( hooks ) { - var stop = hooks.stop; - delete hooks.stop; - stop( gotoEnd ); - }; - - if ( typeof type !== "string" ) { - gotoEnd = clearQueue; - clearQueue = type; - type = undefined; - } - if ( clearQueue ) { - this.queue( type || "fx", [] ); - } - - return this.each( function() { - var dequeue = true, - index = type != null && type + "queueHooks", - timers = jQuery.timers, - data = dataPriv.get( this ); - - if ( index ) { - if ( data[ index ] && data[ index ].stop ) { - stopQueue( data[ index ] ); - } - } else { - for ( index in data ) { - if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { - stopQueue( data[ index ] ); - } - } - } - - for ( index = timers.length; index--; ) { - if ( timers[ index ].elem === this && - ( type == null || timers[ index ].queue === type ) ) { - - timers[ index ].anim.stop( gotoEnd ); - dequeue = false; - timers.splice( index, 1 ); - } - } - - // Start the next in the queue if the last step wasn't forced. - // Timers currently will call their complete callbacks, which - // will dequeue but only if they were gotoEnd. - if ( dequeue || !gotoEnd ) { - jQuery.dequeue( this, type ); - } - } ); - }, - finish: function( type ) { - if ( type !== false ) { - type = type || "fx"; - } - return this.each( function() { - var index, - data = dataPriv.get( this ), - queue = data[ type + "queue" ], - hooks = data[ type + "queueHooks" ], - timers = jQuery.timers, - length = queue ? queue.length : 0; - - // Enable finishing flag on private data - data.finish = true; - - // Empty the queue first - jQuery.queue( this, type, [] ); - - if ( hooks && hooks.stop ) { - hooks.stop.call( this, true ); - } - - // Look for any active animations, and finish them - for ( index = timers.length; index--; ) { - if ( timers[ index ].elem === this && timers[ index ].queue === type ) { - timers[ index ].anim.stop( true ); - timers.splice( index, 1 ); - } - } - - // Look for any animations in the old queue and finish them - for ( index = 0; index < length; index++ ) { - if ( queue[ index ] && queue[ index ].finish ) { - queue[ index ].finish.call( this ); - } - } - - // Turn off finishing flag - delete data.finish; - } ); - } -} ); - -jQuery.each( [ "toggle", "show", "hide" ], function( _i, name ) { - var cssFn = jQuery.fn[ name ]; - jQuery.fn[ name ] = function( speed, easing, callback ) { - return speed == null || typeof speed === "boolean" ? - cssFn.apply( this, arguments ) : - this.animate( genFx( name, true ), speed, easing, callback ); - }; -} ); - -// Generate shortcuts for custom animations -jQuery.each( { - slideDown: genFx( "show" ), - slideUp: genFx( "hide" ), - slideToggle: genFx( "toggle" ), - fadeIn: { opacity: "show" }, - fadeOut: { opacity: "hide" }, - fadeToggle: { opacity: "toggle" } -}, function( name, props ) { - jQuery.fn[ name ] = function( speed, easing, callback ) { - return this.animate( props, speed, easing, callback ); - }; -} ); - -jQuery.timers = []; -jQuery.fx.tick = function() { - var timer, - i = 0, - timers = jQuery.timers; - - fxNow = Date.now(); - - for ( ; i < timers.length; i++ ) { - timer = timers[ i ]; - - // Run the timer and safely remove it when done (allowing for external removal) - if ( !timer() && timers[ i ] === timer ) { - timers.splice( i--, 1 ); - } - } - - if ( !timers.length ) { - jQuery.fx.stop(); - } - fxNow = undefined; -}; - -jQuery.fx.timer = function( timer ) { - jQuery.timers.push( timer ); - jQuery.fx.start(); -}; - -jQuery.fx.interval = 13; -jQuery.fx.start = function() { - if ( inProgress ) { - return; - } - - inProgress = true; - schedule(); -}; - -jQuery.fx.stop = function() { - inProgress = null; -}; - -jQuery.fx.speeds = { - slow: 600, - fast: 200, - - // Default speed - _default: 400 -}; - - -// Based off of the plugin by Clint Helfers, with permission. -// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/ -jQuery.fn.delay = function( time, type ) { - time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; - type = type || "fx"; - - return this.queue( type, function( next, hooks ) { - var timeout = window.setTimeout( next, time ); - hooks.stop = function() { - window.clearTimeout( timeout ); - }; - } ); -}; - - -( function() { - var input = document.createElement( "input" ), - select = document.createElement( "select" ), - opt = select.appendChild( document.createElement( "option" ) ); - - input.type = "checkbox"; - - // Support: Android <=4.3 only - // Default value for a checkbox should be "on" - support.checkOn = input.value !== ""; - - // Support: IE <=11 only - // Must access selectedIndex to make default options select - support.optSelected = opt.selected; - - // Support: IE <=11 only - // An input loses its value after becoming a radio - input = document.createElement( "input" ); - input.value = "t"; - input.type = "radio"; - support.radioValue = input.value === "t"; -} )(); - - -var boolHook, - attrHandle = jQuery.expr.attrHandle; - -jQuery.fn.extend( { - attr: function( name, value ) { - return access( this, jQuery.attr, name, value, arguments.length > 1 ); - }, - - removeAttr: function( name ) { - return this.each( function() { - jQuery.removeAttr( this, name ); - } ); - } -} ); - -jQuery.extend( { - attr: function( elem, name, value ) { - var ret, hooks, - nType = elem.nodeType; - - // Don't get/set attributes on text, comment and attribute nodes - if ( nType === 3 || nType === 8 || nType === 2 ) { - return; - } - - // Fallback to prop when attributes are not supported - if ( typeof elem.getAttribute === "undefined" ) { - return jQuery.prop( elem, name, value ); - } - - // Attribute hooks are determined by the lowercase version - // Grab necessary hook if one is defined - if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { - hooks = jQuery.attrHooks[ name.toLowerCase() ] || - ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); - } - - if ( value !== undefined ) { - if ( value === null ) { - jQuery.removeAttr( elem, name ); - return; - } - - if ( hooks && "set" in hooks && - ( ret = hooks.set( elem, value, name ) ) !== undefined ) { - return ret; - } - - elem.setAttribute( name, value + "" ); - return value; - } - - if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { - return ret; - } - - ret = jQuery.find.attr( elem, name ); - - // Non-existent attributes return null, we normalize to undefined - return ret == null ? undefined : ret; - }, - - attrHooks: { - type: { - set: function( elem, value ) { - if ( !support.radioValue && value === "radio" && - nodeName( elem, "input" ) ) { - var val = elem.value; - elem.setAttribute( "type", value ); - if ( val ) { - elem.value = val; - } - return value; - } - } - } - }, - - removeAttr: function( elem, value ) { - var name, - i = 0, - - // Attribute names can contain non-HTML whitespace characters - // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 - attrNames = value && value.match( rnothtmlwhite ); - - if ( attrNames && elem.nodeType === 1 ) { - while ( ( name = attrNames[ i++ ] ) ) { - elem.removeAttribute( name ); - } - } - } -} ); - -// Hooks for boolean attributes -boolHook = { - set: function( elem, value, name ) { - if ( value === false ) { - - // Remove boolean attributes when set to false - jQuery.removeAttr( elem, name ); - } else { - elem.setAttribute( name, name ); - } - return name; - } -}; - -jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( _i, name ) { - var getter = attrHandle[ name ] || jQuery.find.attr; - - attrHandle[ name ] = function( elem, name, isXML ) { - var ret, handle, - lowercaseName = name.toLowerCase(); - - if ( !isXML ) { - - // Avoid an infinite loop by temporarily removing this function from the getter - handle = attrHandle[ lowercaseName ]; - attrHandle[ lowercaseName ] = ret; - ret = getter( elem, name, isXML ) != null ? - lowercaseName : - null; - attrHandle[ lowercaseName ] = handle; - } - return ret; - }; -} ); - - - - -var rfocusable = /^(?:input|select|textarea|button)$/i, - rclickable = /^(?:a|area)$/i; - -jQuery.fn.extend( { - prop: function( name, value ) { - return access( this, jQuery.prop, name, value, arguments.length > 1 ); - }, - - removeProp: function( name ) { - return this.each( function() { - delete this[ jQuery.propFix[ name ] || name ]; - } ); - } -} ); - -jQuery.extend( { - prop: function( elem, name, value ) { - var ret, hooks, - nType = elem.nodeType; - - // Don't get/set properties on text, comment and attribute nodes - if ( nType === 3 || nType === 8 || nType === 2 ) { - return; - } - - if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { - - // Fix name and attach hooks - name = jQuery.propFix[ name ] || name; - hooks = jQuery.propHooks[ name ]; - } - - if ( value !== undefined ) { - if ( hooks && "set" in hooks && - ( ret = hooks.set( elem, value, name ) ) !== undefined ) { - return ret; - } - - return ( elem[ name ] = value ); - } - - if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { - return ret; - } - - return elem[ name ]; - }, - - propHooks: { - tabIndex: { - get: function( elem ) { - - // Support: IE <=9 - 11 only - // elem.tabIndex doesn't always return the - // correct value when it hasn't been explicitly set - // https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ - // Use proper attribute retrieval(#12072) - var tabindex = jQuery.find.attr( elem, "tabindex" ); - - if ( tabindex ) { - return parseInt( tabindex, 10 ); - } - - if ( - rfocusable.test( elem.nodeName ) || - rclickable.test( elem.nodeName ) && - elem.href - ) { - return 0; - } - - return -1; - } - } - }, - - propFix: { - "for": "htmlFor", - "class": "className" - } -} ); - -// Support: IE <=11 only -// Accessing the selectedIndex property -// forces the browser to respect setting selected -// on the option -// The getter ensures a default option is selected -// when in an optgroup -// eslint rule "no-unused-expressions" is disabled for this code -// since it considers such accessions noop -if ( !support.optSelected ) { - jQuery.propHooks.selected = { - get: function( elem ) { - - /* eslint no-unused-expressions: "off" */ - - var parent = elem.parentNode; - if ( parent && parent.parentNode ) { - parent.parentNode.selectedIndex; - } - return null; - }, - set: function( elem ) { - - /* eslint no-unused-expressions: "off" */ - - var parent = elem.parentNode; - if ( parent ) { - parent.selectedIndex; - - if ( parent.parentNode ) { - parent.parentNode.selectedIndex; - } - } - } - }; -} - -jQuery.each( [ - "tabIndex", - "readOnly", - "maxLength", - "cellSpacing", - "cellPadding", - "rowSpan", - "colSpan", - "useMap", - "frameBorder", - "contentEditable" -], function() { - jQuery.propFix[ this.toLowerCase() ] = this; -} ); - - - - - // Strip and collapse whitespace according to HTML spec - // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace - function stripAndCollapse( value ) { - var tokens = value.match( rnothtmlwhite ) || []; - return tokens.join( " " ); - } - - -function getClass( elem ) { - return elem.getAttribute && elem.getAttribute( "class" ) || ""; -} - -function classesToArray( value ) { - if ( Array.isArray( value ) ) { - return value; - } - if ( typeof value === "string" ) { - return value.match( rnothtmlwhite ) || []; - } - return []; -} - -jQuery.fn.extend( { - addClass: function( value ) { - var classes, elem, cur, curValue, clazz, j, finalValue, - i = 0; - - if ( isFunction( value ) ) { - return this.each( function( j ) { - jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); - } ); - } - - classes = classesToArray( value ); - - if ( classes.length ) { - while ( ( elem = this[ i++ ] ) ) { - curValue = getClass( elem ); - cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); - - if ( cur ) { - j = 0; - while ( ( clazz = classes[ j++ ] ) ) { - if ( cur.indexOf( " " + clazz + " " ) < 0 ) { - cur += clazz + " "; - } - } - - // Only assign if different to avoid unneeded rendering. - finalValue = stripAndCollapse( cur ); - if ( curValue !== finalValue ) { - elem.setAttribute( "class", finalValue ); - } - } - } - } - - return this; - }, - - removeClass: function( value ) { - var classes, elem, cur, curValue, clazz, j, finalValue, - i = 0; - - if ( isFunction( value ) ) { - return this.each( function( j ) { - jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); - } ); - } - - if ( !arguments.length ) { - return this.attr( "class", "" ); - } - - classes = classesToArray( value ); - - if ( classes.length ) { - while ( ( elem = this[ i++ ] ) ) { - curValue = getClass( elem ); - - // This expression is here for better compressibility (see addClass) - cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); - - if ( cur ) { - j = 0; - while ( ( clazz = classes[ j++ ] ) ) { - - // Remove *all* instances - while ( cur.indexOf( " " + clazz + " " ) > -1 ) { - cur = cur.replace( " " + clazz + " ", " " ); - } - } - - // Only assign if different to avoid unneeded rendering. - finalValue = stripAndCollapse( cur ); - if ( curValue !== finalValue ) { - elem.setAttribute( "class", finalValue ); - } - } - } - } - - return this; - }, - - toggleClass: function( value, stateVal ) { - var type = typeof value, - isValidValue = type === "string" || Array.isArray( value ); - - if ( typeof stateVal === "boolean" && isValidValue ) { - return stateVal ? this.addClass( value ) : this.removeClass( value ); - } - - if ( isFunction( value ) ) { - return this.each( function( i ) { - jQuery( this ).toggleClass( - value.call( this, i, getClass( this ), stateVal ), - stateVal - ); - } ); - } - - return this.each( function() { - var className, i, self, classNames; - - if ( isValidValue ) { - - // Toggle individual class names - i = 0; - self = jQuery( this ); - classNames = classesToArray( value ); - - while ( ( className = classNames[ i++ ] ) ) { - - // Check each className given, space separated list - if ( self.hasClass( className ) ) { - self.removeClass( className ); - } else { - self.addClass( className ); - } - } - - // Toggle whole class name - } else if ( value === undefined || type === "boolean" ) { - className = getClass( this ); - if ( className ) { - - // Store className if set - dataPriv.set( this, "__className__", className ); - } - - // If the element has a class name or if we're passed `false`, - // then remove the whole classname (if there was one, the above saved it). - // Otherwise bring back whatever was previously saved (if anything), - // falling back to the empty string if nothing was stored. - if ( this.setAttribute ) { - this.setAttribute( "class", - className || value === false ? - "" : - dataPriv.get( this, "__className__" ) || "" - ); - } - } - } ); - }, - - hasClass: function( selector ) { - var className, elem, - i = 0; - - className = " " + selector + " "; - while ( ( elem = this[ i++ ] ) ) { - if ( elem.nodeType === 1 && - ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { - return true; - } - } - - return false; - } -} ); - - - - -var rreturn = /\r/g; - -jQuery.fn.extend( { - val: function( value ) { - var hooks, ret, valueIsFunction, - elem = this[ 0 ]; - - if ( !arguments.length ) { - if ( elem ) { - hooks = jQuery.valHooks[ elem.type ] || - jQuery.valHooks[ elem.nodeName.toLowerCase() ]; - - if ( hooks && - "get" in hooks && - ( ret = hooks.get( elem, "value" ) ) !== undefined - ) { - return ret; - } - - ret = elem.value; - - // Handle most common string cases - if ( typeof ret === "string" ) { - return ret.replace( rreturn, "" ); - } - - // Handle cases where value is null/undef or number - return ret == null ? "" : ret; - } - - return; - } - - valueIsFunction = isFunction( value ); - - return this.each( function( i ) { - var val; - - if ( this.nodeType !== 1 ) { - return; - } - - if ( valueIsFunction ) { - val = value.call( this, i, jQuery( this ).val() ); - } else { - val = value; - } - - // Treat null/undefined as ""; convert numbers to string - if ( val == null ) { - val = ""; - - } else if ( typeof val === "number" ) { - val += ""; - - } else if ( Array.isArray( val ) ) { - val = jQuery.map( val, function( value ) { - return value == null ? "" : value + ""; - } ); - } - - hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; - - // If set returns undefined, fall back to normal setting - if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { - this.value = val; - } - } ); - } -} ); - -jQuery.extend( { - valHooks: { - option: { - get: function( elem ) { - - var val = jQuery.find.attr( elem, "value" ); - return val != null ? - val : - - // Support: IE <=10 - 11 only - // option.text throws exceptions (#14686, #14858) - // Strip and collapse whitespace - // https://html.spec.whatwg.org/#strip-and-collapse-whitespace - stripAndCollapse( jQuery.text( elem ) ); - } - }, - select: { - get: function( elem ) { - var value, option, i, - options = elem.options, - index = elem.selectedIndex, - one = elem.type === "select-one", - values = one ? null : [], - max = one ? index + 1 : options.length; - - if ( index < 0 ) { - i = max; - - } else { - i = one ? index : 0; - } - - // Loop through all the selected options - for ( ; i < max; i++ ) { - option = options[ i ]; - - // Support: IE <=9 only - // IE8-9 doesn't update selected after form reset (#2551) - if ( ( option.selected || i === index ) && - - // Don't return options that are disabled or in a disabled optgroup - !option.disabled && - ( !option.parentNode.disabled || - !nodeName( option.parentNode, "optgroup" ) ) ) { - - // Get the specific value for the option - value = jQuery( option ).val(); - - // We don't need an array for one selects - if ( one ) { - return value; - } - - // Multi-Selects return an array - values.push( value ); - } - } - - return values; - }, - - set: function( elem, value ) { - var optionSet, option, - options = elem.options, - values = jQuery.makeArray( value ), - i = options.length; - - while ( i-- ) { - option = options[ i ]; - - /* eslint-disable no-cond-assign */ - - if ( option.selected = - jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 - ) { - optionSet = true; - } - - /* eslint-enable no-cond-assign */ - } - - // Force browsers to behave consistently when non-matching value is set - if ( !optionSet ) { - elem.selectedIndex = -1; - } - return values; - } - } - } -} ); - -// Radios and checkboxes getter/setter -jQuery.each( [ "radio", "checkbox" ], function() { - jQuery.valHooks[ this ] = { - set: function( elem, value ) { - if ( Array.isArray( value ) ) { - return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); - } - } - }; - if ( !support.checkOn ) { - jQuery.valHooks[ this ].get = function( elem ) { - return elem.getAttribute( "value" ) === null ? "on" : elem.value; - }; - } -} ); - - - - -// Return jQuery for attributes-only inclusion - - -support.focusin = "onfocusin" in window; - - -var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, - stopPropagationCallback = function( e ) { - e.stopPropagation(); - }; - -jQuery.extend( jQuery.event, { - - trigger: function( event, data, elem, onlyHandlers ) { - - var i, cur, tmp, bubbleType, ontype, handle, special, lastElement, - eventPath = [ elem || document ], - type = hasOwn.call( event, "type" ) ? event.type : event, - namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; - - cur = lastElement = tmp = elem = elem || document; - - // Don't do events on text and comment nodes - if ( elem.nodeType === 3 || elem.nodeType === 8 ) { - return; - } - - // focus/blur morphs to focusin/out; ensure we're not firing them right now - if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { - return; - } - - if ( type.indexOf( "." ) > -1 ) { - - // Namespaced trigger; create a regexp to match event type in handle() - namespaces = type.split( "." ); - type = namespaces.shift(); - namespaces.sort(); - } - ontype = type.indexOf( ":" ) < 0 && "on" + type; - - // Caller can pass in a jQuery.Event object, Object, or just an event type string - event = event[ jQuery.expando ] ? - event : - new jQuery.Event( type, typeof event === "object" && event ); - - // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) - event.isTrigger = onlyHandlers ? 2 : 3; - event.namespace = namespaces.join( "." ); - event.rnamespace = event.namespace ? - new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : - null; - - // Clean up the event in case it is being reused - event.result = undefined; - if ( !event.target ) { - event.target = elem; - } - - // Clone any incoming data and prepend the event, creating the handler arg list - data = data == null ? - [ event ] : - jQuery.makeArray( data, [ event ] ); - - // Allow special events to draw outside the lines - special = jQuery.event.special[ type ] || {}; - if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { - return; - } - - // Determine event propagation path in advance, per W3C events spec (#9951) - // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) - if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) { - - bubbleType = special.delegateType || type; - if ( !rfocusMorph.test( bubbleType + type ) ) { - cur = cur.parentNode; - } - for ( ; cur; cur = cur.parentNode ) { - eventPath.push( cur ); - tmp = cur; - } - - // Only add window if we got to document (e.g., not plain obj or detached DOM) - if ( tmp === ( elem.ownerDocument || document ) ) { - eventPath.push( tmp.defaultView || tmp.parentWindow || window ); - } - } - - // Fire handlers on the event path - i = 0; - while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { - lastElement = cur; - event.type = i > 1 ? - bubbleType : - special.bindType || type; - - // jQuery handler - handle = ( - dataPriv.get( cur, "events" ) || Object.create( null ) - )[ event.type ] && - dataPriv.get( cur, "handle" ); - if ( handle ) { - handle.apply( cur, data ); - } - - // Native handler - handle = ontype && cur[ ontype ]; - if ( handle && handle.apply && acceptData( cur ) ) { - event.result = handle.apply( cur, data ); - if ( event.result === false ) { - event.preventDefault(); - } - } - } - event.type = type; - - // If nobody prevented the default action, do it now - if ( !onlyHandlers && !event.isDefaultPrevented() ) { - - if ( ( !special._default || - special._default.apply( eventPath.pop(), data ) === false ) && - acceptData( elem ) ) { - - // Call a native DOM method on the target with the same name as the event. - // Don't do default actions on window, that's where global variables be (#6170) - if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) { - - // Don't re-trigger an onFOO event when we call its FOO() method - tmp = elem[ ontype ]; - - if ( tmp ) { - elem[ ontype ] = null; - } - - // Prevent re-triggering of the same event, since we already bubbled it above - jQuery.event.triggered = type; - - if ( event.isPropagationStopped() ) { - lastElement.addEventListener( type, stopPropagationCallback ); - } - - elem[ type ](); - - if ( event.isPropagationStopped() ) { - lastElement.removeEventListener( type, stopPropagationCallback ); - } - - jQuery.event.triggered = undefined; - - if ( tmp ) { - elem[ ontype ] = tmp; - } - } - } - } - - return event.result; - }, - - // Piggyback on a donor event to simulate a different one - // Used only for `focus(in | out)` events - simulate: function( type, elem, event ) { - var e = jQuery.extend( - new jQuery.Event(), - event, - { - type: type, - isSimulated: true - } - ); - - jQuery.event.trigger( e, null, elem ); - } - -} ); - -jQuery.fn.extend( { - - trigger: function( type, data ) { - return this.each( function() { - jQuery.event.trigger( type, data, this ); - } ); - }, - triggerHandler: function( type, data ) { - var elem = this[ 0 ]; - if ( elem ) { - return jQuery.event.trigger( type, data, elem, true ); - } - } -} ); - - -// Support: Firefox <=44 -// Firefox doesn't have focus(in | out) events -// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 -// -// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 -// focus(in | out) events fire after focus & blur events, -// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order -// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 -if ( !support.focusin ) { - jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) { - - // Attach a single capturing handler on the document while someone wants focusin/focusout - var handler = function( event ) { - jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) ); - }; - - jQuery.event.special[ fix ] = { - setup: function() { - - // Handle: regular nodes (via `this.ownerDocument`), window - // (via `this.document`) & document (via `this`). - var doc = this.ownerDocument || this.document || this, - attaches = dataPriv.access( doc, fix ); - - if ( !attaches ) { - doc.addEventListener( orig, handler, true ); - } - dataPriv.access( doc, fix, ( attaches || 0 ) + 1 ); - }, - teardown: function() { - var doc = this.ownerDocument || this.document || this, - attaches = dataPriv.access( doc, fix ) - 1; - - if ( !attaches ) { - doc.removeEventListener( orig, handler, true ); - dataPriv.remove( doc, fix ); - - } else { - dataPriv.access( doc, fix, attaches ); - } - } - }; - } ); -} -var location = window.location; - -var nonce = { guid: Date.now() }; - -var rquery = ( /\?/ ); - - - -// Cross-browser xml parsing -jQuery.parseXML = function( data ) { - var xml; - if ( !data || typeof data !== "string" ) { - return null; - } - - // Support: IE 9 - 11 only - // IE throws on parseFromString with invalid input. - try { - xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); - } catch ( e ) { - xml = undefined; - } - - if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) { - jQuery.error( "Invalid XML: " + data ); - } - return xml; -}; - - -var - rbracket = /\[\]$/, - rCRLF = /\r?\n/g, - rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, - rsubmittable = /^(?:input|select|textarea|keygen)/i; - -function buildParams( prefix, obj, traditional, add ) { - var name; - - if ( Array.isArray( obj ) ) { - - // Serialize array item. - jQuery.each( obj, function( i, v ) { - if ( traditional || rbracket.test( prefix ) ) { - - // Treat each array item as a scalar. - add( prefix, v ); - - } else { - - // Item is non-scalar (array or object), encode its numeric index. - buildParams( - prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", - v, - traditional, - add - ); - } - } ); - - } else if ( !traditional && toType( obj ) === "object" ) { - - // Serialize object item. - for ( name in obj ) { - buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); - } - - } else { - - // Serialize scalar item. - add( prefix, obj ); - } -} - -// Serialize an array of form elements or a set of -// key/values into a query string -jQuery.param = function( a, traditional ) { - var prefix, - s = [], - add = function( key, valueOrFunction ) { - - // If value is a function, invoke it and use its return value - var value = isFunction( valueOrFunction ) ? - valueOrFunction() : - valueOrFunction; - - s[ s.length ] = encodeURIComponent( key ) + "=" + - encodeURIComponent( value == null ? "" : value ); - }; - - if ( a == null ) { - return ""; - } - - // If an array was passed in, assume that it is an array of form elements. - if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { - - // Serialize the form elements - jQuery.each( a, function() { - add( this.name, this.value ); - } ); - - } else { - - // If traditional, encode the "old" way (the way 1.3.2 or older - // did it), otherwise encode params recursively. - for ( prefix in a ) { - buildParams( prefix, a[ prefix ], traditional, add ); - } - } - - // Return the resulting serialization - return s.join( "&" ); -}; - -jQuery.fn.extend( { - serialize: function() { - return jQuery.param( this.serializeArray() ); - }, - serializeArray: function() { - return this.map( function() { - - // Can add propHook for "elements" to filter or add form elements - var elements = jQuery.prop( this, "elements" ); - return elements ? jQuery.makeArray( elements ) : this; - } ) - .filter( function() { - var type = this.type; - - // Use .is( ":disabled" ) so that fieldset[disabled] works - return this.name && !jQuery( this ).is( ":disabled" ) && - rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && - ( this.checked || !rcheckableType.test( type ) ); - } ) - .map( function( _i, elem ) { - var val = jQuery( this ).val(); - - if ( val == null ) { - return null; - } - - if ( Array.isArray( val ) ) { - return jQuery.map( val, function( val ) { - return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; - } ); - } - - return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; - } ).get(); - } -} ); - - -var - r20 = /%20/g, - rhash = /#.*$/, - rantiCache = /([?&])_=[^&]*/, - rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, - - // #7653, #8125, #8152: local protocol detection - rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, - rnoContent = /^(?:GET|HEAD)$/, - rprotocol = /^\/\//, - - /* Prefilters - * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) - * 2) These are called: - * - BEFORE asking for a transport - * - AFTER param serialization (s.data is a string if s.processData is true) - * 3) key is the dataType - * 4) the catchall symbol "*" can be used - * 5) execution will start with transport dataType and THEN continue down to "*" if needed - */ - prefilters = {}, - - /* Transports bindings - * 1) key is the dataType - * 2) the catchall symbol "*" can be used - * 3) selection will start with transport dataType and THEN go to "*" if needed - */ - transports = {}, - - // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression - allTypes = "*/".concat( "*" ), - - // Anchor tag for parsing the document origin - originAnchor = document.createElement( "a" ); - originAnchor.href = location.href; - -// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport -function addToPrefiltersOrTransports( structure ) { - - // dataTypeExpression is optional and defaults to "*" - return function( dataTypeExpression, func ) { - - if ( typeof dataTypeExpression !== "string" ) { - func = dataTypeExpression; - dataTypeExpression = "*"; - } - - var dataType, - i = 0, - dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; - - if ( isFunction( func ) ) { - - // For each dataType in the dataTypeExpression - while ( ( dataType = dataTypes[ i++ ] ) ) { - - // Prepend if requested - if ( dataType[ 0 ] === "+" ) { - dataType = dataType.slice( 1 ) || "*"; - ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func ); - - // Otherwise append - } else { - ( structure[ dataType ] = structure[ dataType ] || [] ).push( func ); - } - } - } - }; -} - -// Base inspection function for prefilters and transports -function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { - - var inspected = {}, - seekingTransport = ( structure === transports ); - - function inspect( dataType ) { - var selected; - inspected[ dataType ] = true; - jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { - var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); - if ( typeof dataTypeOrTransport === "string" && - !seekingTransport && !inspected[ dataTypeOrTransport ] ) { - - options.dataTypes.unshift( dataTypeOrTransport ); - inspect( dataTypeOrTransport ); - return false; - } else if ( seekingTransport ) { - return !( selected = dataTypeOrTransport ); - } - } ); - return selected; - } - - return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); -} - -// A special extend for ajax options -// that takes "flat" options (not to be deep extended) -// Fixes #9887 -function ajaxExtend( target, src ) { - var key, deep, - flatOptions = jQuery.ajaxSettings.flatOptions || {}; - - for ( key in src ) { - if ( src[ key ] !== undefined ) { - ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; - } - } - if ( deep ) { - jQuery.extend( true, target, deep ); - } - - return target; -} - -/* Handles responses to an ajax request: - * - finds the right dataType (mediates between content-type and expected dataType) - * - returns the corresponding response - */ -function ajaxHandleResponses( s, jqXHR, responses ) { - - var ct, type, finalDataType, firstDataType, - contents = s.contents, - dataTypes = s.dataTypes; - - // Remove auto dataType and get content-type in the process - while ( dataTypes[ 0 ] === "*" ) { - dataTypes.shift(); - if ( ct === undefined ) { - ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" ); - } - } - - // Check if we're dealing with a known content-type - if ( ct ) { - for ( type in contents ) { - if ( contents[ type ] && contents[ type ].test( ct ) ) { - dataTypes.unshift( type ); - break; - } - } - } - - // Check to see if we have a response for the expected dataType - if ( dataTypes[ 0 ] in responses ) { - finalDataType = dataTypes[ 0 ]; - } else { - - // Try convertible dataTypes - for ( type in responses ) { - if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) { - finalDataType = type; - break; - } - if ( !firstDataType ) { - firstDataType = type; - } - } - - // Or just use first one - finalDataType = finalDataType || firstDataType; - } - - // If we found a dataType - // We add the dataType to the list if needed - // and return the corresponding response - if ( finalDataType ) { - if ( finalDataType !== dataTypes[ 0 ] ) { - dataTypes.unshift( finalDataType ); - } - return responses[ finalDataType ]; - } -} - -/* Chain conversions given the request and the original response - * Also sets the responseXXX fields on the jqXHR instance - */ -function ajaxConvert( s, response, jqXHR, isSuccess ) { - var conv2, current, conv, tmp, prev, - converters = {}, - - // Work with a copy of dataTypes in case we need to modify it for conversion - dataTypes = s.dataTypes.slice(); - - // Create converters map with lowercased keys - if ( dataTypes[ 1 ] ) { - for ( conv in s.converters ) { - converters[ conv.toLowerCase() ] = s.converters[ conv ]; - } - } - - current = dataTypes.shift(); - - // Convert to each sequential dataType - while ( current ) { - - if ( s.responseFields[ current ] ) { - jqXHR[ s.responseFields[ current ] ] = response; - } - - // Apply the dataFilter if provided - if ( !prev && isSuccess && s.dataFilter ) { - response = s.dataFilter( response, s.dataType ); - } - - prev = current; - current = dataTypes.shift(); - - if ( current ) { - - // There's only work to do if current dataType is non-auto - if ( current === "*" ) { - - current = prev; - - // Convert response if prev dataType is non-auto and differs from current - } else if ( prev !== "*" && prev !== current ) { - - // Seek a direct converter - conv = converters[ prev + " " + current ] || converters[ "* " + current ]; - - // If none found, seek a pair - if ( !conv ) { - for ( conv2 in converters ) { - - // If conv2 outputs current - tmp = conv2.split( " " ); - if ( tmp[ 1 ] === current ) { - - // If prev can be converted to accepted input - conv = converters[ prev + " " + tmp[ 0 ] ] || - converters[ "* " + tmp[ 0 ] ]; - if ( conv ) { - - // Condense equivalence converters - if ( conv === true ) { - conv = converters[ conv2 ]; - - // Otherwise, insert the intermediate dataType - } else if ( converters[ conv2 ] !== true ) { - current = tmp[ 0 ]; - dataTypes.unshift( tmp[ 1 ] ); - } - break; - } - } - } - } - - // Apply converter (if not an equivalence) - if ( conv !== true ) { - - // Unless errors are allowed to bubble, catch and return them - if ( conv && s.throws ) { - response = conv( response ); - } else { - try { - response = conv( response ); - } catch ( e ) { - return { - state: "parsererror", - error: conv ? e : "No conversion from " + prev + " to " + current - }; - } - } - } - } - } - } - - return { state: "success", data: response }; -} - -jQuery.extend( { - - // Counter for holding the number of active queries - active: 0, - - // Last-Modified header cache for next request - lastModified: {}, - etag: {}, - - ajaxSettings: { - url: location.href, - type: "GET", - isLocal: rlocalProtocol.test( location.protocol ), - global: true, - processData: true, - async: true, - contentType: "application/x-www-form-urlencoded; charset=UTF-8", - - /* - timeout: 0, - data: null, - dataType: null, - username: null, - password: null, - cache: null, - throws: false, - traditional: false, - headers: {}, - */ - - accepts: { - "*": allTypes, - text: "text/plain", - html: "text/html", - xml: "application/xml, text/xml", - json: "application/json, text/javascript" - }, - - contents: { - xml: /\bxml\b/, - html: /\bhtml/, - json: /\bjson\b/ - }, - - responseFields: { - xml: "responseXML", - text: "responseText", - json: "responseJSON" - }, - - // Data converters - // Keys separate source (or catchall "*") and destination types with a single space - converters: { - - // Convert anything to text - "* text": String, - - // Text to html (true = no transformation) - "text html": true, - - // Evaluate text as a json expression - "text json": JSON.parse, - - // Parse text as xml - "text xml": jQuery.parseXML - }, - - // For options that shouldn't be deep extended: - // you can add your own custom options here if - // and when you create one that shouldn't be - // deep extended (see ajaxExtend) - flatOptions: { - url: true, - context: true - } - }, - - // Creates a full fledged settings object into target - // with both ajaxSettings and settings fields. - // If target is omitted, writes into ajaxSettings. - ajaxSetup: function( target, settings ) { - return settings ? - - // Building a settings object - ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : - - // Extending ajaxSettings - ajaxExtend( jQuery.ajaxSettings, target ); - }, - - ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), - ajaxTransport: addToPrefiltersOrTransports( transports ), - - // Main method - ajax: function( url, options ) { - - // If url is an object, simulate pre-1.5 signature - if ( typeof url === "object" ) { - options = url; - url = undefined; - } - - // Force options to be an object - options = options || {}; - - var transport, - - // URL without anti-cache param - cacheURL, - - // Response headers - responseHeadersString, - responseHeaders, - - // timeout handle - timeoutTimer, - - // Url cleanup var - urlAnchor, - - // Request state (becomes false upon send and true upon completion) - completed, - - // To know if global events are to be dispatched - fireGlobals, - - // Loop variable - i, - - // uncached part of the url - uncached, - - // Create the final options object - s = jQuery.ajaxSetup( {}, options ), - - // Callbacks context - callbackContext = s.context || s, - - // Context for global events is callbackContext if it is a DOM node or jQuery collection - globalEventContext = s.context && - ( callbackContext.nodeType || callbackContext.jquery ) ? - jQuery( callbackContext ) : - jQuery.event, - - // Deferreds - deferred = jQuery.Deferred(), - completeDeferred = jQuery.Callbacks( "once memory" ), - - // Status-dependent callbacks - statusCode = s.statusCode || {}, - - // Headers (they are sent all at once) - requestHeaders = {}, - requestHeadersNames = {}, - - // Default abort message - strAbort = "canceled", - - // Fake xhr - jqXHR = { - readyState: 0, - - // Builds headers hashtable if needed - getResponseHeader: function( key ) { - var match; - if ( completed ) { - if ( !responseHeaders ) { - responseHeaders = {}; - while ( ( match = rheaders.exec( responseHeadersString ) ) ) { - responseHeaders[ match[ 1 ].toLowerCase() + " " ] = - ( responseHeaders[ match[ 1 ].toLowerCase() + " " ] || [] ) - .concat( match[ 2 ] ); - } - } - match = responseHeaders[ key.toLowerCase() + " " ]; - } - return match == null ? null : match.join( ", " ); - }, - - // Raw string - getAllResponseHeaders: function() { - return completed ? responseHeadersString : null; - }, - - // Caches the header - setRequestHeader: function( name, value ) { - if ( completed == null ) { - name = requestHeadersNames[ name.toLowerCase() ] = - requestHeadersNames[ name.toLowerCase() ] || name; - requestHeaders[ name ] = value; - } - return this; - }, - - // Overrides response content-type header - overrideMimeType: function( type ) { - if ( completed == null ) { - s.mimeType = type; - } - return this; - }, - - // Status-dependent callbacks - statusCode: function( map ) { - var code; - if ( map ) { - if ( completed ) { - - // Execute the appropriate callbacks - jqXHR.always( map[ jqXHR.status ] ); - } else { - - // Lazy-add the new callbacks in a way that preserves old ones - for ( code in map ) { - statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; - } - } - } - return this; - }, - - // Cancel the request - abort: function( statusText ) { - var finalText = statusText || strAbort; - if ( transport ) { - transport.abort( finalText ); - } - done( 0, finalText ); - return this; - } - }; - - // Attach deferreds - deferred.promise( jqXHR ); - - // Add protocol if not provided (prefilters might expect it) - // Handle falsy url in the settings object (#10093: consistency with old signature) - // We also use the url parameter if available - s.url = ( ( url || s.url || location.href ) + "" ) - .replace( rprotocol, location.protocol + "//" ); - - // Alias method option to type as per ticket #12004 - s.type = options.method || options.type || s.method || s.type; - - // Extract dataTypes list - s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; - - // A cross-domain request is in order when the origin doesn't match the current origin. - if ( s.crossDomain == null ) { - urlAnchor = document.createElement( "a" ); - - // Support: IE <=8 - 11, Edge 12 - 15 - // IE throws exception on accessing the href property if url is malformed, - // e.g. http://example.com:80x/ - try { - urlAnchor.href = s.url; - - // Support: IE <=8 - 11 only - // Anchor's host property isn't correctly set when s.url is relative - urlAnchor.href = urlAnchor.href; - s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== - urlAnchor.protocol + "//" + urlAnchor.host; - } catch ( e ) { - - // If there is an error parsing the URL, assume it is crossDomain, - // it can be rejected by the transport if it is invalid - s.crossDomain = true; - } - } - - // Convert data if not already a string - if ( s.data && s.processData && typeof s.data !== "string" ) { - s.data = jQuery.param( s.data, s.traditional ); - } - - // Apply prefilters - inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); - - // If request was aborted inside a prefilter, stop there - if ( completed ) { - return jqXHR; - } - - // We can fire global events as of now if asked to - // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118) - fireGlobals = jQuery.event && s.global; - - // Watch for a new set of requests - if ( fireGlobals && jQuery.active++ === 0 ) { - jQuery.event.trigger( "ajaxStart" ); - } - - // Uppercase the type - s.type = s.type.toUpperCase(); - - // Determine if request has content - s.hasContent = !rnoContent.test( s.type ); - - // Save the URL in case we're toying with the If-Modified-Since - // and/or If-None-Match header later on - // Remove hash to simplify url manipulation - cacheURL = s.url.replace( rhash, "" ); - - // More options handling for requests with no content - if ( !s.hasContent ) { - - // Remember the hash so we can put it back - uncached = s.url.slice( cacheURL.length ); - - // If data is available and should be processed, append data to url - if ( s.data && ( s.processData || typeof s.data === "string" ) ) { - cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; - - // #9682: remove data so that it's not used in an eventual retry - delete s.data; - } - - // Add or update anti-cache param if needed - if ( s.cache === false ) { - cacheURL = cacheURL.replace( rantiCache, "$1" ); - uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce.guid++ ) + - uncached; - } - - // Put hash and anti-cache on the URL that will be requested (gh-1732) - s.url = cacheURL + uncached; - - // Change '%20' to '+' if this is encoded form body content (gh-2658) - } else if ( s.data && s.processData && - ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { - s.data = s.data.replace( r20, "+" ); - } - - // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. - if ( s.ifModified ) { - if ( jQuery.lastModified[ cacheURL ] ) { - jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); - } - if ( jQuery.etag[ cacheURL ] ) { - jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); - } - } - - // Set the correct header, if data is being sent - if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { - jqXHR.setRequestHeader( "Content-Type", s.contentType ); - } - - // Set the Accepts header for the server, depending on the dataType - jqXHR.setRequestHeader( - "Accept", - s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? - s.accepts[ s.dataTypes[ 0 ] ] + - ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : - s.accepts[ "*" ] - ); - - // Check for headers option - for ( i in s.headers ) { - jqXHR.setRequestHeader( i, s.headers[ i ] ); - } - - // Allow custom headers/mimetypes and early abort - if ( s.beforeSend && - ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { - - // Abort if not done already and return - return jqXHR.abort(); - } - - // Aborting is no longer a cancellation - strAbort = "abort"; - - // Install callbacks on deferreds - completeDeferred.add( s.complete ); - jqXHR.done( s.success ); - jqXHR.fail( s.error ); - - // Get transport - transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); - - // If no transport, we auto-abort - if ( !transport ) { - done( -1, "No Transport" ); - } else { - jqXHR.readyState = 1; - - // Send global event - if ( fireGlobals ) { - globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); - } - - // If request was aborted inside ajaxSend, stop there - if ( completed ) { - return jqXHR; - } - - // Timeout - if ( s.async && s.timeout > 0 ) { - timeoutTimer = window.setTimeout( function() { - jqXHR.abort( "timeout" ); - }, s.timeout ); - } - - try { - completed = false; - transport.send( requestHeaders, done ); - } catch ( e ) { - - // Rethrow post-completion exceptions - if ( completed ) { - throw e; - } - - // Propagate others as results - done( -1, e ); - } - } - - // Callback for when everything is done - function done( status, nativeStatusText, responses, headers ) { - var isSuccess, success, error, response, modified, - statusText = nativeStatusText; - - // Ignore repeat invocations - if ( completed ) { - return; - } - - completed = true; - - // Clear timeout if it exists - if ( timeoutTimer ) { - window.clearTimeout( timeoutTimer ); - } - - // Dereference transport for early garbage collection - // (no matter how long the jqXHR object will be used) - transport = undefined; - - // Cache response headers - responseHeadersString = headers || ""; - - // Set readyState - jqXHR.readyState = status > 0 ? 4 : 0; - - // Determine if successful - isSuccess = status >= 200 && status < 300 || status === 304; - - // Get response data - if ( responses ) { - response = ajaxHandleResponses( s, jqXHR, responses ); - } - - // Use a noop converter for missing script - if ( !isSuccess && jQuery.inArray( "script", s.dataTypes ) > -1 ) { - s.converters[ "text script" ] = function() {}; - } - - // Convert no matter what (that way responseXXX fields are always set) - response = ajaxConvert( s, response, jqXHR, isSuccess ); - - // If successful, handle type chaining - if ( isSuccess ) { - - // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. - if ( s.ifModified ) { - modified = jqXHR.getResponseHeader( "Last-Modified" ); - if ( modified ) { - jQuery.lastModified[ cacheURL ] = modified; - } - modified = jqXHR.getResponseHeader( "etag" ); - if ( modified ) { - jQuery.etag[ cacheURL ] = modified; - } - } - - // if no content - if ( status === 204 || s.type === "HEAD" ) { - statusText = "nocontent"; - - // if not modified - } else if ( status === 304 ) { - statusText = "notmodified"; - - // If we have data, let's convert it - } else { - statusText = response.state; - success = response.data; - error = response.error; - isSuccess = !error; - } - } else { - - // Extract error from statusText and normalize for non-aborts - error = statusText; - if ( status || !statusText ) { - statusText = "error"; - if ( status < 0 ) { - status = 0; - } - } - } - - // Set data for the fake xhr object - jqXHR.status = status; - jqXHR.statusText = ( nativeStatusText || statusText ) + ""; - - // Success/Error - if ( isSuccess ) { - deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); - } else { - deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); - } - - // Status-dependent callbacks - jqXHR.statusCode( statusCode ); - statusCode = undefined; - - if ( fireGlobals ) { - globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", - [ jqXHR, s, isSuccess ? success : error ] ); - } - - // Complete - completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); - - if ( fireGlobals ) { - globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); - - // Handle the global AJAX counter - if ( !( --jQuery.active ) ) { - jQuery.event.trigger( "ajaxStop" ); - } - } - } - - return jqXHR; - }, - - getJSON: function( url, data, callback ) { - return jQuery.get( url, data, callback, "json" ); - }, - - getScript: function( url, callback ) { - return jQuery.get( url, undefined, callback, "script" ); - } -} ); - -jQuery.each( [ "get", "post" ], function( _i, method ) { - jQuery[ method ] = function( url, data, callback, type ) { - - // Shift arguments if data argument was omitted - if ( isFunction( data ) ) { - type = type || callback; - callback = data; - data = undefined; - } - - // The url can be an options object (which then must have .url) - return jQuery.ajax( jQuery.extend( { - url: url, - type: method, - dataType: type, - data: data, - success: callback - }, jQuery.isPlainObject( url ) && url ) ); - }; -} ); - -jQuery.ajaxPrefilter( function( s ) { - var i; - for ( i in s.headers ) { - if ( i.toLowerCase() === "content-type" ) { - s.contentType = s.headers[ i ] || ""; - } - } -} ); - - -jQuery._evalUrl = function( url, options, doc ) { - return jQuery.ajax( { - url: url, - - // Make this explicit, since user can override this through ajaxSetup (#11264) - type: "GET", - dataType: "script", - cache: true, - async: false, - global: false, - - // Only evaluate the response if it is successful (gh-4126) - // dataFilter is not invoked for failure responses, so using it instead - // of the default converter is kludgy but it works. - converters: { - "text script": function() {} - }, - dataFilter: function( response ) { - jQuery.globalEval( response, options, doc ); - } - } ); -}; - - -jQuery.fn.extend( { - wrapAll: function( html ) { - var wrap; - - if ( this[ 0 ] ) { - if ( isFunction( html ) ) { - html = html.call( this[ 0 ] ); - } - - // The elements to wrap the target around - wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); - - if ( this[ 0 ].parentNode ) { - wrap.insertBefore( this[ 0 ] ); - } - - wrap.map( function() { - var elem = this; - - while ( elem.firstElementChild ) { - elem = elem.firstElementChild; - } - - return elem; - } ).append( this ); - } - - return this; - }, - - wrapInner: function( html ) { - if ( isFunction( html ) ) { - return this.each( function( i ) { - jQuery( this ).wrapInner( html.call( this, i ) ); - } ); - } - - return this.each( function() { - var self = jQuery( this ), - contents = self.contents(); - - if ( contents.length ) { - contents.wrapAll( html ); - - } else { - self.append( html ); - } - } ); - }, - - wrap: function( html ) { - var htmlIsFunction = isFunction( html ); - - return this.each( function( i ) { - jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html ); - } ); - }, - - unwrap: function( selector ) { - this.parent( selector ).not( "body" ).each( function() { - jQuery( this ).replaceWith( this.childNodes ); - } ); - return this; - } -} ); - - -jQuery.expr.pseudos.hidden = function( elem ) { - return !jQuery.expr.pseudos.visible( elem ); -}; -jQuery.expr.pseudos.visible = function( elem ) { - return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); -}; - - - - -jQuery.ajaxSettings.xhr = function() { - try { - return new window.XMLHttpRequest(); - } catch ( e ) {} -}; - -var xhrSuccessStatus = { - - // File protocol always yields status code 0, assume 200 - 0: 200, - - // Support: IE <=9 only - // #1450: sometimes IE returns 1223 when it should be 204 - 1223: 204 - }, - xhrSupported = jQuery.ajaxSettings.xhr(); - -support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); -support.ajax = xhrSupported = !!xhrSupported; - -jQuery.ajaxTransport( function( options ) { - var callback, errorCallback; - - // Cross domain only allowed if supported through XMLHttpRequest - if ( support.cors || xhrSupported && !options.crossDomain ) { - return { - send: function( headers, complete ) { - var i, - xhr = options.xhr(); - - xhr.open( - options.type, - options.url, - options.async, - options.username, - options.password - ); - - // Apply custom fields if provided - if ( options.xhrFields ) { - for ( i in options.xhrFields ) { - xhr[ i ] = options.xhrFields[ i ]; - } - } - - // Override mime type if needed - if ( options.mimeType && xhr.overrideMimeType ) { - xhr.overrideMimeType( options.mimeType ); - } - - // X-Requested-With header - // For cross-domain requests, seeing as conditions for a preflight are - // akin to a jigsaw puzzle, we simply never set it to be sure. - // (it can always be set on a per-request basis or even using ajaxSetup) - // For same-domain requests, won't change header if already provided. - if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) { - headers[ "X-Requested-With" ] = "XMLHttpRequest"; - } - - // Set headers - for ( i in headers ) { - xhr.setRequestHeader( i, headers[ i ] ); - } - - // Callback - callback = function( type ) { - return function() { - if ( callback ) { - callback = errorCallback = xhr.onload = - xhr.onerror = xhr.onabort = xhr.ontimeout = - xhr.onreadystatechange = null; - - if ( type === "abort" ) { - xhr.abort(); - } else if ( type === "error" ) { - - // Support: IE <=9 only - // On a manual native abort, IE9 throws - // errors on any property access that is not readyState - if ( typeof xhr.status !== "number" ) { - complete( 0, "error" ); - } else { - complete( - - // File: protocol always yields status 0; see #8605, #14207 - xhr.status, - xhr.statusText - ); - } - } else { - complete( - xhrSuccessStatus[ xhr.status ] || xhr.status, - xhr.statusText, - - // Support: IE <=9 only - // IE9 has no XHR2 but throws on binary (trac-11426) - // For XHR2 non-text, let the caller handle it (gh-2498) - ( xhr.responseType || "text" ) !== "text" || - typeof xhr.responseText !== "string" ? - { binary: xhr.response } : - { text: xhr.responseText }, - xhr.getAllResponseHeaders() - ); - } - } - }; - }; - - // Listen to events - xhr.onload = callback(); - errorCallback = xhr.onerror = xhr.ontimeout = callback( "error" ); - - // Support: IE 9 only - // Use onreadystatechange to replace onabort - // to handle uncaught aborts - if ( xhr.onabort !== undefined ) { - xhr.onabort = errorCallback; - } else { - xhr.onreadystatechange = function() { - - // Check readyState before timeout as it changes - if ( xhr.readyState === 4 ) { - - // Allow onerror to be called first, - // but that will not handle a native abort - // Also, save errorCallback to a variable - // as xhr.onerror cannot be accessed - window.setTimeout( function() { - if ( callback ) { - errorCallback(); - } - } ); - } - }; - } - - // Create the abort callback - callback = callback( "abort" ); - - try { - - // Do send the request (this may raise an exception) - xhr.send( options.hasContent && options.data || null ); - } catch ( e ) { - - // #14683: Only rethrow if this hasn't been notified as an error yet - if ( callback ) { - throw e; - } - } - }, - - abort: function() { - if ( callback ) { - callback(); - } - } - }; - } -} ); - - - - -// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) -jQuery.ajaxPrefilter( function( s ) { - if ( s.crossDomain ) { - s.contents.script = false; - } -} ); - -// Install script dataType -jQuery.ajaxSetup( { - accepts: { - script: "text/javascript, application/javascript, " + - "application/ecmascript, application/x-ecmascript" - }, - contents: { - script: /\b(?:java|ecma)script\b/ - }, - converters: { - "text script": function( text ) { - jQuery.globalEval( text ); - return text; - } - } -} ); - -// Handle cache's special case and crossDomain -jQuery.ajaxPrefilter( "script", function( s ) { - if ( s.cache === undefined ) { - s.cache = false; - } - if ( s.crossDomain ) { - s.type = "GET"; - } -} ); - -// Bind script tag hack transport -jQuery.ajaxTransport( "script", function( s ) { - - // This transport only deals with cross domain or forced-by-attrs requests - if ( s.crossDomain || s.scriptAttrs ) { - var script, callback; - return { - send: function( _, complete ) { - script = jQuery( " - - - - - - - - - - - - - - - -
- - - - - - - Python Slack SDK - - -
- - -
-
- - - - - -
- -
-
-

Audit Logs API Clientยถ

-

Audit Logs API is a set of APIs for monitoring whatโ€™s happening in your Enterprise Grid organization.

-

The Audit Logs API can be used by security information and event management (SIEM) tools to provide an analysis of how your Slack organization is being accessed. You can also use this API to write your own applications to see how members of your organization are using Slack.

-

Follow the instructions in the API document to get a valid token for using Audit Logs API. The Slack app using the Audit Logs API needs to be installed in the Enterprise Grid Organization, not an individual workspace within the organization.

-

The Python document for this module is available at https://slack.dev/python-slack-sdk/api-docs/slack_sdk/

-
-

AuditLogsClientยถ

-

An OAuth token with the admin scope is required to access this API.

-

You will likely use the /logs endpoint as itโ€™s the essential part of this API.

-

To learn about the available parameters for this endpoint, check out this guide. You can also learn more about the data structure of api_response.typed_body from the class source code.

-
import os
-from slack_sdk.audit_logs import AuditLogsClient
-
-client = AuditLogsClient(token=os.environ["SLACK_ORG_ADMIN_USER_TOKEN"])
-
-api_response = client.logs(action="user_login", limit=1)
-api_response.typed_body  # slack_sdk.audit_logs.v1.LogsResponse
-
-
-

If you would like to access /schemes or /actions, you can use the following methods:

-
api_response = client.schemas()
-api_response = client.actions()
-
-
-
-
-

AsyncAuditLogsClientยถ

-

If you are keen to use asyncio for SCIM API calls, we offer AsyncSCIMClient for it. This client relies on aiohttp library.

-
from slack_sdk.audit_logs.async_client import AsyncAuditLogsClient
-client = AsyncAuditLogsClient(token=os.environ["SLACK_ORG_ADMIN_USER_TOKEN"])
-
-api_response = await client.logs(action="user_login", limit=1)
-api_response.typed_body  # slack_sdk.audit_logs.v1.LogsResponse
-
-
-
-
-
-

RetryHandlerยถ

-

With the default settings, only ConnectionErrorRetryHandler with its default configuration (=only one retry in the manner of exponential backoff and jitter) is enabled. The retry handler retries if an API client encounters a connectivity-related failure (e.g., Connection reset by peer).

-

To use other retry handlers, you can pass a list of RetryHandler to the client constructor. For instance, you can add the built-in RateLimitErrorRetryHandler this way:

-
import os
-from slack_sdk.audit_logs import AuditLogsClient
-client = AuditLogsClient(token=os.environ["SLACK_ORG_ADMIN_USER_TOKEN"])
-
-# This handler does retries when HTTP status 429 is returned
-from slack_sdk.http_retry.builtin_handlers import RateLimitErrorRetryHandler
-rate_limit_handler = RateLimitErrorRetryHandler(max_retry_count=1)
-
-# Enable rate limited error retries as well
-client.retry_handlers.append(rate_limit_handler)
-
-
-

Creating your own ones is also quite simple. Defining a new class that inherits slack_sdk.http_retry.RetryHandler (AsyncRetryHandler for asyncio apps) and implements required methods (internals of can_retry / prepare_for_next_retry). Check the built-in onesโ€™ source code for learning how to properly implement.

-
import socket
-from typing import Optional
-from slack_sdk.http_retry import (RetryHandler, RetryState, HttpRequest, HttpResponse)
-from slack_sdk.http_retry.builtin_interval_calculators import BackoffRetryIntervalCalculator
-from slack_sdk.http_retry.jitter import RandomJitter
-
-class MyRetryHandler(RetryHandler):
-    def _can_retry(
-        self,
-        *,
-        state: RetryState,
-        request: HttpRequest,
-        response: Optional[HttpResponse] = None,
-        error: Optional[Exception] = None
-    ) -> bool:
-        # [Errno 104] Connection reset by peer
-        return error is not None and isinstance(error, socket.error) and error.errno == 104
-
-client = AuditLogsClient(
-    token=os.environ["SLACK_ORG_ADMIN_USER_TOKEN"],
-    retry_handlers=[MyRetryHandler(
-        max_retry_count=1,
-        interval_calculator=BackoffRetryIntervalCalculator(
-            backoff_factor=0.5,
-            jitter=RandomJitter(),
-        ),
-    )],
-)
-
-
-

For asyncio apps, Async prefixed corresponding modules are available. All the methods in those methods are async/await compatible. Check the source code and tests for more details.

-
-
- - -
-
-
- -
-
- -
-

- ยฉ 2015- Slack Technologies, LLC and contributors -

-
- - - - - \ No newline at end of file diff --git a/docs/english/_sidebar.json b/docs/english/_sidebar.json new file mode 100644 index 000000000..6e5aa7358 --- /dev/null +++ b/docs/english/_sidebar.json @@ -0,0 +1,66 @@ +[ + { + "type": "doc", + "id": "tools/python-slack-sdk/index", + "label": "Python Slack SDK", + "className": "sidebar-title" + }, + { + "type": "html", + "value": "
" + }, + "tools/python-slack-sdk/installation", + "tools/python-slack-sdk/web", + "tools/python-slack-sdk/webhook", + "tools/python-slack-sdk/socket-mode", + "tools/python-slack-sdk/oauth", + "tools/python-slack-sdk/audit-logs", + "tools/python-slack-sdk/rtm", + "tools/python-slack-sdk/scim", + { "type": "html", "value": "
" }, + { + "type": "category", + "label": "Legacy slackclient v2", + "items": [ + "tools/python-slack-sdk/legacy/index", + "tools/python-slack-sdk/legacy/auth", + "tools/python-slack-sdk/legacy/basic_usage", + "tools/python-slack-sdk/legacy/conversations", + "tools/python-slack-sdk/legacy/real_time_messaging", + "tools/python-slack-sdk/legacy/faq", + "tools/python-slack-sdk/legacy/changelog" + ] + }, + "tools/python-slack-sdk/v3-migration", + { "type": "html", "value": "
" }, + { + "type": "category", + "label": "Tutorials", + "items": [ + "tools/python-slack-sdk/tutorial/uploading-files", + "tools/python-slack-sdk/tutorial/understanding-oauth-scopes" + ] + }, + { "type": "html", "value": "
" }, + { + "type": "link", + "label": "Reference", + "href": "https://docs.slack.dev/tools/python-slack-sdk/reference/index.html" + }, + { "type": "html", "value": "
" }, + { + "type": "link", + "label": "Release notes", + "href": "https://github.com/slackapi/python-slack-sdk/releases" + }, + { + "type": "link", + "label": "Code on GitHub", + "href": "https://github.com/SlackAPI/python-slack-sdk" + }, + { + "type": "link", + "label": "Contributors Guide", + "href": "https://github.com/SlackAPI/python-slack-sdk/blob/main/.github/contributing.md" + } +] diff --git a/docs/english/audit-logs.md b/docs/english/audit-logs.md new file mode 100644 index 000000000..1d8b930ce --- /dev/null +++ b/docs/english/audit-logs.md @@ -0,0 +1,103 @@ +# Audit Logs API client + +The [Audit Logs API](/admins/audit-logs-api) is a set of APIs that you can use to monitor what's happening in your [Enterprise Grid](/enterprise) organization. + +The Audit Logs API can be used by Security Information and Event Management (SIEM) tools to provide an analysis of how your Slack organization is being accessed. You can also use this API to write your own apps to see how members of your organization are using Slack. + +You'll need a valid token in order to use the Audit Logs API. In addition, the Slack app using the Audit Logs API needs to be installed in the Enterprise Grid organization, not an individual workspace within the organization. + +--- + +## AuditLogsClient {#auditlogsclient} + +An OAuth token with [the admin scope](/reference/scopes/admin) is required to access this API. + +You'll likely use the `/logs` endpoint as it's the essential part of this API. + +To learn about the available parameters for this endpoint, check out [using the Audit Logs API](/admins/audit-logs-api). You can also learn more about the data structure of `api_response.typed_body` from [the class source code](https://github.com/slackapi/python-slack-sdk/blob/main/slack_sdk/audit_logs/v1/logs.py). + +``` python +import os +from slack_sdk.audit_logs import AuditLogsClient + +client = AuditLogsClient(token=os.environ["SLACK_ORG_ADMIN_USER_TOKEN"]) + +api_response = client.logs(action="user_login", limit=1) +api_response.typed_body # slack_sdk.audit_logs.v1.LogsResponse +``` + +If you would like to access `/schemes` or `/actions`, you can use the +following methods: + +``` python +api_response = client.schemas() +api_response = client.actions() +``` + +## AsyncAuditLogsClient {#asyncauditlogsclient} + +If you are keen to use asyncio for SCIM API calls, we offer AsyncSCIMClient for it. This client relies on aiohttp library. + +``` python +from slack_sdk.audit_logs.async_client import AsyncAuditLogsClient +client = AsyncAuditLogsClient(token=os.environ["SLACK_ORG_ADMIN_USER_TOKEN"]) + +api_response = await client.logs(action="user_login", limit=1) +api_response.typed_body # slack_sdk.audit_logs.v1.LogsResponse +``` + +--- + +## RetryHandler {#retryhandler} + +With the default settings, only `ConnectionErrorRetryHandler` with its default configuration (=only one retry in the manner of [exponential backoff and jitter](https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/)) is enabled. The retry handler retries if an API client encounters a connectivity-related failure (e.g., connection reset by peer). + +To use other retry handlers, you can pass a list of `RetryHandler` to the client constructor. For instance, you can add the built-in `RateLimitErrorRetryHandler` this way: + +``` python +import os +from slack_sdk.audit_logs import AuditLogsClient +client = AuditLogsClient(token=os.environ["SLACK_ORG_ADMIN_USER_TOKEN"]) + +# This handler does retries when HTTP status 429 is returned +from slack_sdk.http_retry.builtin_handlers import RateLimitErrorRetryHandler +rate_limit_handler = RateLimitErrorRetryHandler(max_retry_count=1) + +# Enable rate limited error retries as well +client.retry_handlers.append(rate_limit_handler) +``` + +You can also create one on your own by defining a new class that inherits `slack_sdk.http_retry RetryHandler` (`AsyncRetryHandler` for asyncio apps) and implements required methods (internals of `can_retry` / `prepare_for_next_retry`). Check out the source code for the ones that are built in to learn how to properly implement them. + +``` python +import socket +from typing import Optional +from slack_sdk.http_retry import (RetryHandler, RetryState, HttpRequest, HttpResponse) +from slack_sdk.http_retry.builtin_interval_calculators import BackoffRetryIntervalCalculator +from slack_sdk.http_retry.jitter import RandomJitter + +class MyRetryHandler(RetryHandler): + def _can_retry( + self, + *, + state: RetryState, + request: HttpRequest, + response: Optional[HttpResponse] = None, + error: Optional[Exception] = None + ) -> bool: + # [Errno 104] Connection reset by peer + return error is not None and isinstance(error, socket.error) and error.errno == 104 + +client = AuditLogsClient( + token=os.environ["SLACK_ORG_ADMIN_USER_TOKEN"], + retry_handlers=[MyRetryHandler( + max_retry_count=1, + interval_calculator=BackoffRetryIntervalCalculator( + backoff_factor=0.5, + jitter=RandomJitter(), + ), + )], +) +``` + +For asyncio apps, `Async` prefixed corresponding modules are available. All the methods in those methods are async/await compatible. Check [the source code](https://github.com/slackapi/python-slack-sdk/blob/main/slack_sdk/http_retry/async_handler.py) for more details. diff --git a/docs/english/index.md b/docs/english/index.md new file mode 100644 index 000000000..7fdbcd6e4 --- /dev/null +++ b/docs/english/index.md @@ -0,0 +1,36 @@ +# Python Slack SDK + +The Slack Python SDK has corresponding packages for Slack APIs. They are small and powerful when used independently, and work seamlessly when used together, too. + +The Slack platform offers several APIs to build apps. Each Slack API delivers part of the capabilities from the platform, so that you can pick just those that fit your needs. + +## Features {#features} + +| Feature | Use | Package | +|---|---|---| +| [Web API](/tools/python-slack-sdk/web) | Send data to or query data from Slack using any of over 200 methods. | `slack_sdk.web`, `slack_sdk.web.async_client` | +| [Webhooks](/tools/python-slack-sdk/webhook) / `response_url` | Send a message using Incoming Webhooks or `response_url` | `slack_sdk.webhook`, `slack_sdk.webhook.async_client` | +| [Socket Mode](/tools/python-slack-sdk/socket-mode) | Receive and send messages over Socket Mode connections. | `slack_sdk.socket_mode` | +| [OAuth](/tools/python-slack-sdk/oauth) | Setup the authentication flow using V2 OAuth, OpenID Connect for Slack apps. | `slack_sdk.oauth` | +| [Audit Logs API](/tools/python-slack-sdk/audit-logs) | Receive audit logs API data. | `slack_sdk.audit_logs` | +| [SCIM API](/tools/python-slack-sdk/scim) | Utilize the SCIM APIs for provisioning and managing user accounts and groups. | `slack_sdk.scim` | +| [RTM API](/tools/python-slack-sdk/rtm) | Listen for incoming messages and a limited set of events happening in Slack, using WebSocket. | `slack_sdk.rtm_v2` | +| Request Signature Verification | Verify incoming requests from the Slack API servers. | `slack_sdk.signature` | +| UI Builders | Construct UI components using easy-to-use builders. | `slack_sdk.models` | + +You can also view the [Python module documents](https://docs.slack.dev/tools/python-slack-sdk/reference)! + +## Getting help {#getting-help} + +These docs have lots of information on the Python Slack SDK. There's also an in-depth Reference section. Please explore! + +If you 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/python-slack-sdk/issues) for questions, bug reports, feature requests, and general discussion related to the Python Slack SDK. Try searching for an existing issue before creating a new one. +* [Email](mailto:support@slack.com) our developer support team: `support@slack.com`. + +## Contributing {#contributing} + +These docs live within the [Python Slack SDK](https://github.com/slackapi/python-slack-sdk) repository and are open source. + +We welcome contributions from everyone! Please check out our [Contributor's Guide](https://github.com/slackapi/python-slack-sdk/blob/main/.github/contributing.md) for how to contribute in a helpful and collaborative way. diff --git a/docs/english/installation.md b/docs/english/installation.md new file mode 100644 index 000000000..17bae95d2 --- /dev/null +++ b/docs/english/installation.md @@ -0,0 +1,205 @@ +# Installation + +This package supports Python 3.7 and higher. We recommend using [PyPI](https://pypi.python.org/pypi) for installation. Run the following command: + +```bash +pip install slack-sdk +``` + +Alternatively, you can always pull the source code directly into your project: + +```bash +git clone https://github.com/slackapi/python-slack-sdk.git +cd python-slack-sdk +python3 -m venv .venv +source .venv/bin/activate +pip install -U pip +pip install -e . # install the SDK project into the virtual env +``` + +Create a `./test.py` file with the following: + +```python title="test.py" +# test.py +import sys +# Enable debug logging +import logging +logging.basicConfig(level=logging.DEBUG) +# Verify it works +from slack_sdk import WebClient +client = WebClient() +api_response = client.api_test() +``` + +Then, run the script: + +```bash +python test.py +``` + +It's also good to try on the Python REPL. + +## Access tokens {#handling-tokens} + +Making calls to the Slack API often requires a [token](/authentication/tokens) with associated scopes that grant access to resources. Collecting a token can be done from app settings or with an OAuth installation depending on your app's requirements. + +**Always keep your access tokens safe.** + +The OAuth token you use to call the Slack Web API has access to the data on the workspace where it is installed. Depending on the scopes granted to the token, it potentially has the ability to read and write data. Treat these tokens just as you would a password โ€” don't publish them, don't check them into source code, and don't share them with others. + +:::danger[Never do the following] + +```python +# don't do this! +token = 'xoxb-111-222-xxxxx' +``` + +::: + +We recommend you pass tokens in as environment variables, or store them in a database that is accessed at runtime. You can add a token to the environment by starting your app as follows: + +```python +SLACK_BOT_TOKEN="xoxb-111-222-xxxxx" python myapp.py +``` + +Then, retrieve the key as follows: + +```python +import os +SLACK_BOT_TOKEN = os.environ["SLACK_BOT_TOKEN"] +``` + +Refer to our [best practices for security](/security) page for more information. + +## Installing on a single workspace {#single-workspace} + +If you're building an application for a single Slack workspace, there's no need to build out the entire OAuth flow. Once you've set up your features, click the **Install App to Team** button on the **Install App** page. If you add new permission scopes or Slack app features after an app has been installed, you must reinstall the app to your workspace for the changes to take effect. + +Refer to the [Slack quickstart](/quickstart) guide for more details. + +## Installing on multiple workspaces {#multi-workspace} + +If you intend for an app to be installed on multiple Slack workspaces, you will need to handle this installation via the industry-standard OAuth protocol. Read more about [installing with OAuth](/authentication/installing-with-oauth). + +The OAuth exchange is facilitated via HTTP and requires a webserver; in this example, we'll use [Flask](https://flask.palletsprojects.com/). + +To configure your app for OAuth, you'll need a client ID, a client secret, and a set of one or more scopes that will be applied to the token once it is granted. The client ID and client secret are available from the [app page](https://api.slack.com/apps). The scopes are determined by the functionality of the app โ€” every method you wish to access has a corresponding scope, and your app will need to request that scope in order to be able to access the method. Review the full list of [OAuth scopes](/reference/scopes). + +```python +import os +from slack_sdk import WebClient +from flask import Flask, request + +client_id = os.environ["SLACK_CLIENT_ID"] +client_secret = os.environ["SLACK_CLIENT_SECRET"] +oauth_scope = os.environ["SLACK_SCOPES"] + +app = Flask(__name__) +``` + +### The OAuth initiation link {#oauth-link} + +To begin the OAuth flow that will install your app on a workspace, you'll need to provide the user with a link to the Slack OAuth page. This can be a simple link to `https://slack.com/oauth/v2/authorize` with the +`scope` and `client_id` query parameters. + +This link directs the user to the OAuth acceptance page, where the user will review and accept or decline the permissions your app is requesting as defined by the scope(s). + +```python +@app.route("/slack/install", methods=["GET"]) +def pre_install(): + state = "randomly-generated-one-time-value" + return '' \ + 'Add to Slack' +``` + +### The OAuth completion page {#oauth-completion} + +Once the user has agreed to the permissions you've requested, Slack will redirect the user to your auth completion page, which includes a `code` query string parameter. You'll use the `code` parameter to call the [`oauth.v2.access`](/reference/methods/oauth.v2.access) API method that will grant you the token. + +```python +@app.route("/slack/oauth_redirect", methods=["GET"]) +def post_install(): + # Verify the "state" parameter + + # Retrieve the auth code from the request params + code_param = request.args['code'] + + # An empty string is a valid token for this request + client = WebClient() + + # Request the auth tokens from Slack + response = client.oauth_v2_access( + client_id=client_id, + client_secret=client_secret, + code=code_param + ) +``` + +A successful request to the `oauth.v2.access` API method will yield a JSON payload with at least one token: a bot token that begins with `xoxb`. + +```python +@app.route("/slack/oauth_redirect", methods=["GET"]) +def post_install(): + # Verify the "state" parameter + + # Retrieve the auth code from the request params + code_param = request.args['code'] + + # An empty string is a valid token for this request + client = WebClient() + + # Request the auth tokens from Slack + response = client.oauth_v2_access( + client_id=client_id, + client_secret=client_secret, + code=code_param + ) + print(response) + + # Save the bot token to an environmental variable or to your data store + # for later use + os.environ["SLACK_BOT_TOKEN"] = response['access_token'] + + # Don't forget to let the user know that OAuth has succeeded! + return "Installation is completed!" + +if __name__ == "__main__": + app.run("localhost", 3000) +``` + +Once your user has completed the OAuth flow, you'll be able to use the provided tokens to call any of the Slack Web API methods that require an access token. + +Refer to the [basic usage](/tools/python-slack-sdk/legacy/basic_usage) page for more examples. + +## Installation troubleshooting {#troubleshooting} + +We recommend using [virtualenv (venv)](https://docs.python.org/3/tutorial/venv.html) to set up your +Python runtime. + +```bash +# Create a dedicated virtual env for running your Python scripts +python -m venv .venv + +# Run .venv\Scripts\activate on Windows OS +source .venv/bin/activate + +# Install slack_sdk PyPI package +pip install "slack_sdk>=3.0" + +# Set your token as an env variable (`set` command for Windows OS) +export SLACK_BOT_TOKEN=xoxb-*** +``` + +Then, verify the following code works on the Python REPL: + +```python +import os +import logging +from slack_sdk import WebClient +logging.basicConfig(level=logging.DEBUG) +client = WebClient(token=os.environ["SLACK_BOT_TOKEN"]) +res = client.api_test() +``` + +As the `slack` package is deprecated, we recommend switching to `slack_sdk` package. That being said, the code you're working on may be still using the old package. If you encounter an error saying `AttributeError: module 'slack' has no attribute 'WebClient'`, run `pip list`. If you find both `slack_sdk` and `slack` in the output, try removing `slack` by `pip uninstall slack` and reinstalling `slack_sdk`. diff --git a/docs/english/legacy/auth.md b/docs/english/legacy/auth.md new file mode 100644 index 000000000..23fe0aa23 --- /dev/null +++ b/docs/english/legacy/auth.md @@ -0,0 +1,135 @@ +# Tokens & installation + +:::danger[The [`slackclient`](https://pypi.org/project/slackclient/) PyPI project is in maintenance mode and the [slack-sdk](https://pypi.org/project/slack-sdk/) project is its successor.] + +The v3 SDK provides additional features such as Socket Mode, OAuth flow, SCIM API, Audit Logs API, better asyncio support, retry handlers, and more. + +::: + +## Access tokens {#handling-tokens} + +**Always keep your access tokens safe.** + +The OAuth token you use to call the Slack Web API has access to the data on the workspace where it is installed. Depending on the scopes granted to the token, it potentially has the ability to read and write data. Treat these tokens just as you would a password โ€” don't publish them, don't check them into source code, and don't share them with others. + +Never do the following: + +``` python +token = 'xoxb-111-222-xxxxx' +``` + +We recommend you pass tokens in as environment variables, or store them in a database that is accessed at runtime. You can add a token to the environment by starting your app as follows: + +``` python +SLACK_BOT_TOKEN="xoxb-111-222-xxxxx" python myapp.py +``` + +Then, retrieve the key as follows: + +``` python +import os +SLACK_BOT_TOKEN = os.environ["SLACK_BOT_TOKEN"] +``` + +Refer to our [best practices for security](/security) page for more information. + +## Installing on a single workspace {#single-workspace} + +If you're building an application for a single Slack workspace, there's no need to build out the entire OAuth flow. Once you've set up your features, click the **Install App to Team** button on the **Install App** page. If you add new permission scopes or Slack app features after an app has been installed, you must reinstall the app to your workspace for the changes to take effect. + +Refer to the [quickstart](/quickstart) guide for more details. + +## Installing on multiple workspaces {#multi-workspace} + +If you intend for an app to be installed on multiple Slack workspaces, you will need to handle this installation via the industry-standard OAuth protocol. Read more about [installing with OAuth](/authentication/installing-with-oauth). + +The OAuth exchange is facilitated via HTTP and requires a webserver; in this example, we'll use [Flask](https://flask.palletsprojects.com/). + +To configure your app for OAuth, you'll need a client ID, a client secret, and a set of one or more scopes that will be applied to the token once it is granted. The client ID and client secret are available from the [app page](https://api.slack.com/apps). The scopes are determined by the functionality of the app โ€” every method you wish to access has a corresponding scope, and your app will need to request that scope in order to be able to access the method. Review the full list of [OAuth scopes](/reference/scopes). + +``` python +import os +from slack import WebClient +from flask import Flask, request + +client_id = os.environ["SLACK_CLIENT_ID"] +client_secret = os.environ["SLACK_CLIENT_SECRET"] +oauth_scope = os.environ["SLACK_SCOPES"] + +app = Flask(__name__) +``` + +### The OAuth initiation link {#oauth-link} + +To begin the OAuth flow that will install your app on a workspace, you'll need to provide the user with a link to the Slack OAuth page. This can be a simple link to `https://slack.com/oauth/v2/authorize` with the +`scope` and `client_id` query parameters. + +This link directs the user to the OAuth acceptance page, where the user will review and accept or decline the permissions your app is requesting as defined by the scope(s). + +``` python +@app.route("/slack/install", methods=["GET"]) +def pre_install(): + state = "randomly-generated-one-time-value" + return '' \ + 'Add to Slack' +``` + +### The OAuth completion page {#oauth-completion} + +Once the user has agreed to the permissions you've requested, Slack will redirect the user to your auth completion page, which includes a `code` query string parameter. You'll use the `code` parameter to call the [`oauth.v2.access`](/reference/methods/oauth.v2.access) API method that will grant you the token. + +``` python +@app.route("/slack/oauth_redirect", methods=["GET"]) +def post_install(): + # Verify the "state" parameter + + # Retrieve the auth code from the request params + code_param = request.args['code'] + + # An empty string is a valid token for this request + client = WebClient() + + # Request the auth tokens from Slack + response = client.oauth_v2_access( + client_id=client_id, + client_secret=client_secret, + code=code_param + ) +``` + +A successful request to the `oauth.v2.access` API method will yield a JSON payload with at least one token: a bot token that begins with `xoxb`. + +``` python +@app.route("/slack/oauth_redirect", methods=["GET"]) +def post_install(): + # Verify the "state" parameter + + # Retrieve the auth code from the request params + code_param = request.args['code'] + + # An empty string is a valid token for this request + client = WebClient() + + # Request the auth tokens from Slack + response = client.oauth_v2_access( + client_id=client_id, + client_secret=client_secret, + code=code_param + ) + print(response) + + # Save the bot token to an environmental variable or to your data store + # for later use + os.environ["SLACK_BOT_TOKEN"] = response['access_token'] + + # Don't forget to let the user know that OAuth has succeeded! + return "Installation is completed!" + +if __name__ == "__main__": + app.run("localhost", 3000) +``` + +Once your user has completed the OAuth flow, you'll be able to use the provided tokens to call any of the Slack Web API methods that require an access token. + +Refer to the [basic usage](/tools/python-slack-sdk/legacy/basic_usage) page for more examples. diff --git a/docs/english/legacy/basic_usage.md b/docs/english/legacy/basic_usage.md new file mode 100644 index 000000000..75094dd9f --- /dev/null +++ b/docs/english/legacy/basic_usage.md @@ -0,0 +1,457 @@ +# Basic usage + +:::danger[The [`slackclient`](https://pypi.org/project/slackclient/) PyPI project is in maintenance mode and the [slack-sdk](https://pypi.org/project/slack-sdk/) project is its successor.] + +The v3 SDK provides additional features such as Socket Mode, OAuth flow, SCIM API, Audit Logs API, better async support, retry handlers, and more. + +::: + +The Slack Web API allows you to build applications that interact with Slack in more complex ways than the integrations we provide out of the box. + +Accessing Slack API methods requires an OAuth token โ€” read more about [installing with OAuth](/authentication/installing-with-oauth). + +Each of these [API methods](/reference/methods) is fully documented on our developer site at [docs.slack.dev](/). + +## Sending a message {#sending-messages} + +One of the primary uses of Slack is posting messages to a channel using the channel ID, or as a DM to another person using their user ID. This method will handle either a channel ID or a user ID passed to the `channel` parameter. + +``` python +import logging +logging.basicConfig(level=logging.DEBUG) + +import os +from slack import WebClient +from slack.errors import SlackApiError + +slack_token = os.environ["SLACK_API_TOKEN"] +client = WebClient(token=slack_token) + +try: + response = client.chat_postMessage( + channel="C0XXXXXX", + text="Hello from your app! :tada:" + ) +except SlackApiError as e: + # You will get a SlackApiError if "ok" is False + assert e.response["error"] # str like 'invalid_auth', 'channel_not_found' +``` + +Sending an ephemeral message, which is only visible to an assigned user in a specified channel, is nearly the same as sending a regular message but with an additional `user` parameter. + +``` python +import os +from slack import WebClient + +slack_token = os.environ["SLACK_API_TOKEN"] +client = WebClient(token=slack_token) + +response = client.chat_postEphemeral( + channel="C0XXXXXX", + text="Hello silently from your app! :tada:", + user="U0XXXXXXX" +) +``` + +See the [`chat.postEphemeral`](/reference/methods/chat.postEphemeral) API method for more details. + +## Formatting messages with Block Kit {#block-kit} + +Messages posted from apps can contain more than just text; they can also include full user interfaces composed of blocks using [Block Kit](/block-kit). + +The [`chat.postMessage method`](/reference/methods/chat.postMessage) takes an optional blocks argument that allows you to customize the layout of a message. Blocks are specified in a single object literal, so just add additional keys for any optional argument. + +To send a message to a channel, use the channel's ID. For DMs, use the user's ID. + +``` python +client.chat_postMessage( + channel="C0XXXXXX", + blocks=[ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "Danny Torrence left the following review for your property:" + } + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": " \n :star: \n Doors had too many axe holes, guest in room " + + "237 was far too rowdy, whole place felt stuck in the 1920s." + }, + "accessory": { + "type": "image", + "image_url": "https://images.pexels.com/photos/750319/pexels-photo-750319.jpeg", + "alt_text": "Haunted hotel image" + } + }, + { + "type": "section", + "fields": [ + { + "type": "mrkdwn", + "text": "*Average Rating*\n1.0" + } + ] + } + ] +) +``` + +:::tip[You can use [Block Kit Builder](https://app.slack.com/block-kit-builder/) to prototype your message's look and feel.] + +::: + +## Threading messages {#threading-messages} + +Threaded messages are a way of grouping messages together to provide greater context. You can reply to a thread or start a new threaded conversation by simply passing the original message's `ts` ID in the `thread_ts` attribute when posting a message. If you're replying to a threaded message, you'll pass the `thread_ts` ID of the message you're replying to. + +A channel or DM conversation is a nearly linear timeline of messages exchanged between people, bots, and apps. When one of these messages is replied to, it becomes the parent of a thread. By default, threaded replies do not appear directly in the channel, but are instead relegated to a kind of forked timeline descending from the parent message. + +``` python +response = client.chat_postMessage( + channel="C0XXXXXX", + thread_ts="1476746830.000003", + text="Hello from your app! :tada:" +) +``` + +By default, the `reply_broadcast` parameter is set to `False`. To indicate your reply is germane to all members of a channel and therefore a notification of the reply should be posted in-channel, set the `reply_broadcast` parameter to `True`. + +``` python +response = client.chat_postMessage( + channel="C0XXXXXX", + thread_ts="1476746830.000003", + text="Hello from your app! :tada:", + reply_broadcast=True +) +``` + +:::info[While threaded messages may contain attachments and message buttons, when your reply is broadcast to the channel, it'll actually be a reference to your reply and not the reply itself.] + +When appearing in the channel, it won't contain any attachments or message buttons. Updates and deletion of threaded replies works the same as regular messages. + +::: + +Refer to the [threading messages](/messaging#threading) page for more information. + +## Updating a message {#updating-messages} + +Let's say you have a bot that posts the status of a request. When that request changes, you'll want to update the message to reflect it's state. + +``` python +response = client.chat_update( + channel="C0XXXXXX", + ts="1476746830.000003", + text="updates from your app! :tada:" +) +``` + +See the [`chat.update`](/reference/methods/chat.update) API method for formatting options and some special considerations when calling this with a bot user. + +## Deleting a message {#deleting-messages} + +Sometimes you need to delete things. + +``` python +response = client.chat_delete( + channel="C0XXXXXX", + ts="1476745373.000002" +) +``` + +See the [`chat.delete`](/reference/methods/chat.delete) API method for more +details. + +## Opening a modal {#opening-modals} + +Modals allow you to collect data from users and display dynamic information in a focused surface. Modals use the same blocks that compose messages, with the addition of an `input` block. + +``` python +# This module is available since v2.6 +from slack.signature import SignatureVerifier +signature_verifier = SignatureVerifier(os.environ["SLACK_SIGNING_SECRET"]) + +from flask import Flask, request, make_response +app = Flask(__name__) + +@app.route("/slack/events", methods=["POST"]) +def slack_app(): + if not signature_verifier.is_valid_request(request.get_data(), request.headers): + return make_response("invalid request", 403) + + if "payload" in request.form: + payload = json.loads(request.form["payload"]) + + if payload["type"] == "shortcut" \ + and payload["callback_id"] == "open-modal-shortcut": + # Open a new modal by a global shortcut + try: + api_response = client.views_open( + trigger_id=payload["trigger_id"], + view={ + "type": "modal", + "callback_id": "modal-id", + "title": { + "type": "plain_text", + "text": "Awesome Modal" + }, + "submit": { + "type": "plain_text", + "text": "Submit" + }, + "close": { + "type": "plain_text", + "text": "Cancel" + }, + "blocks": [ + { + "type": "input", + "block_id": "b-id", + "label": { + "type": "plain_text", + "text": "Input label", + }, + "element": { + "action_id": "a-id", + "type": "plain_text_input", + } + } + ] + } + ) + return make_response("", 200) + except SlackApiError as e: + code = e.response["error"] + return make_response(f"Failed to open a modal due to {code}", 200) + + if payload["type"] == "view_submission" \ + and payload["view"]["callback_id"] == "modal-id": + # Handle a data submission request from the modal + submitted_data = payload["view"]["state"]["values"] + print(submitted_data) # {'b-id': {'a-id': {'type': 'plain_text_input', 'value': 'your input'}}} + return make_response("", 200) + + return make_response("", 404) + +if __name__ == "__main__": + # export SLACK_SIGNING_SECRET=*** + # export SLACK_API_TOKEN=xoxb-*** + # export FLASK_ENV=development + # python3 app.py + app.run("localhost", 3000) +``` + +See the [`views.open`](/reference/methods/views.open) API method more details and additional parameters. + +To run the above example, the following [app configurations](https://api.slack.com/apps) are required: + +* Enable **Interactivity** with a valid Request URL: `https://{your-public-domain}/slack/events` +* Add a global shortcut with the callback ID: `open-modal-shortcut` + +## Updating and pushing modals {#updating-pushing-modals} + +You can dynamically update a view inside of a modal by calling the `views.update` API method and passing the view ID returned in the previous `views.open` API method call. + +``` python +private_metadata = "any str data you want to store" +response = client.views_update( + view_id=payload["view"]["id"], + hash=payload["view"]["hash"], + view={ + "type": "modal", + "callback_id": "modal-id", + "private_metadata": private_metadata, + "title": { + "type": "plain_text", + "text": "Awesome Modal" + }, + "submit": { + "type": "plain_text", + "text": "Submit" + }, + "close": { + "type": "plain_text", + "text": "Cancel" + }, + "blocks": [ + { + "type": "input", + "block_id": "b-id", + "label": { + "type": "plain_text", + "text": "Input label", + }, + "element": { + "action_id": "a-id", + "type": "plain_text_input", + } + } + ] + } +) +``` + +See the [`views.update`](/reference/methods/views.update) API method for more details. + +If you want to push a new view onto the modal instead of updating an existing view, see the [`views.push`](/reference/methods/views.push) API method. + +## Emoji reactions {#emoji} + +You can quickly respond to any message on Slack with an emoji reaction. Reactions can be used for any purpose: voting, checking off to-do items, showing excitement, or just for fun. + +This method adds a reaction (emoji) to an item (`file`, `file comment`, `channel message`, `group message`, or `direct message`). One of `file`, `file_comment`, or the combination of `channel` and `timestamp` must be specified. + +``` python +response = client.reactions_add( + channel="C0XXXXXXX", + name="thumbsup", + timestamp="1234567890.123456" +) +``` + +Removing an emoji reaction is basically the same format, but you'll use the `reactions.remove` API method instead of the `reactions.add` API method. + +``` python +response = client.reactions_remove( + channel="C0XXXXXXX", + name="thumbsup", + timestamp="1234567890.123456" +) +``` + +See the [`reactions.add`](/reference/methods/reactions.add) and [`reactions.remove`](/reference/methods/reactions.remove) API methods for more details. + +## Listing public channels {#listing-public-channels} + +At some point, you'll want to find out what channels are available to your app. This is how you get that list. + +``` python +response = client.conversations_list(types="public_channel") +``` + +Archived channels are included by default. You can exclude them by passing `exclude_archived=1` to your request. + +``` python +response = client.conversations_list(exclude_archived=1) +``` + +See the [`conversations.list`](/reference/methods/conversations.list) API method for more details. + +## Getting a channel's info {#get-channel-info} + +Once you have the ID for a specific channel, you can fetch information about that channel. + +``` python +response = client.conversations_info(channel="C0XXXXXXX") +``` + +See the [`conversations.info`](/reference/methods/conversations.info) API method for more details. + +## Joining a channel {#join-channel} + +Channels are the social hub of most Slack teams. Here's how you hop into one: + +``` python +response = client.conversations_join(channel="C0XXXXXXY") +``` + +If you are already in the channel, the response is slightly different. The `already_in_channel` attribute will be true, and a limited `channel` object will be returned. Bot users cannot join a channel on their own, they need to be invited by another user. + +See the [`conversations.join`](/reference/methods/conversations.join) API method for more details. + +------------------------------------------------------------------------ + +## Leaving a channel {#leave-channel} + +Maybe you've finished up all the business you had in a channel, or maybe you joined one by accident. This is how you leave a channel. + +``` python +response = client.conversations_leave(channel="C0XXXXXXX") +``` + +See the [`conversations.leave`](/reference/methods/conversations.leave) API method for more details. + +## Listing team members {#list-team-members} + +``` python +response = client.users_list() +users = response["members"] +user_ids = list(map(lambda u: u["id"], users)) +``` + +See the [`users.list`](/reference/methods/users.list) API method for more details. + +## Uploading files {#uploading-files} + +``` python +response = client.files_upload_v2( + channel="C3UKJTQAC", + file="./files.pdf", + title="Test upload" +) +``` + +See the [`files.upload`](/reference/methods/files.upload) API method for more details. + +## Calling API methods {#calling-API-methods} + +This library covers all the public endpoints as the methods in `WebClient`. That said, you may see a bit of a delay with the library release. When you're in a hurry, you can directly use the `api_call` method as below. + +``` python +import os +from slack import WebClient + +client = WebClient(token=os.environ['SLACK_API_TOKEN']) +response = client.api_call( + api_method='chat.postMessage', + json={'channel': '#random','text': "Hello world!"} +) +assert response["message"]["text"] == "Hello world!" +``` + +## Rate limits {#rate-limits} + +When posting messages to a channel, Slack allows apps to send no more than one message per channel per second. We allow bursts over that limit for short periods; however, if your app continues to exceed the limit over a longer period of time, it will be rate limited. Different API methods have other limits โ€” be sure to check the [rate limits](/apis/web-api/rate-limits) and test that your app has a graceful fallback if it should hit those limits. + +If you go over these limits, Slack will begin returning *HTTP 429 Too Many Requests* errors, a JSON object containing the number of calls you have been making, and a *Retry-After* header containing the number of seconds until you can retry. + +Here's an example of how you might handle rate limited requests: + +``` python +import os +import time +from slack import WebClient +from slack.errors import SlackApiError + +client = WebClient(token=os.environ["SLACK_API_TOKEN"]) + +# Simple wrapper for sending a Slack message +def send_slack_message(channel, message): + return client.chat_postMessage( + channel=channel, + text=message + ) + +# Make the API call and save results to `response` +channel = "#random" +message = "Hello, from Python!" +# Do until being rate limited +while True: + try: + response = send_slack_message(channel, message) + except SlackApiError as e: + if e.response.status_code == 429: + # The `Retry-After` header will tell you how long to wait before retrying + delay = int(e.response.headers['Retry-After']) + print(f"Rate limited. Retrying in {delay} seconds") + time.sleep(delay) + response = send_slack_message(channel, message) + else: + # other errors + raise e +``` + +Refer to the [rate limits](/apis/web-api/rate-limits) page for more information. diff --git a/docs/english/legacy/changelog.md b/docs/english/legacy/changelog.md new file mode 100644 index 000000000..d58f0bf42 --- /dev/null +++ b/docs/english/legacy/changelog.md @@ -0,0 +1,399 @@ +# Changelog + +## v3.0.0 (2020-11-09) + +This is the first stable version of [slack_sdk](https://pypi.org/project/slack-sdk/) v3. The remarkable updates in this major version are: +- Newly added OAuth flow support +- Better async/sync separation for `WebClient` and `WebhookClient` +- Renamed packages (from `slack` to `slack_sdk`) with deprecation warnings + +Refer to [v3.0.0 milestone](https://github.com/slackapi/python-slack-sdk/milestone/10?closed=1) and [the docs website](/tools/python-slack-sdk/) for details. If you're a `slackclient` user, the migration guide for `slackclient` v2.x users is available at http://localhost:3000/python-slack-sdk/v3-migration. + +## v2.9.3 (2020-10-20) + +Refer to [v2.9.3 milestone](https://github.com/slackapi/python-slackclient/milestone/20?closed=1) to know the complete list of the issues resolved by this release. + +**Updates** + +- \[Block Kit\] #851 #852 Set default_type for HeaderBlock text - Thanks \@fwump38 +- \[Block Kit\] #853 #854 Enable to use input blocks in Home tab views - Thanks \@fwump38 +- \[RTMClient\] #857 #846 RTMClient does not pass timeout value to WebClient - Thanks \@Luden \@seratch + +## v2.9.2 (2020-10-09) + +Refer to [v2.9.2 milestone](https://github.com/slackapi/python-slackclient/milestone/19?closed=1) to know the complete list of the issues resolved by this release. + +**Updates** + +- \[Block Kit\] #841 Dispatch Action in Input blocks - Thanks \@seratch +- \[WebClient\] #838 Add apps.event.authorizations.list and other APIs - Thanks \@seratch +- \[WebClient\]\[WebhookClient\] #829 Improve error body parser to handle no charset responses - Thanks \@adamchainz \@seratch +- \[Block Kit\] #824 Correct text field validation in Header blocks - Thanks \@seratch + +## v2.9.1 (2020-09-23) + +Refer to [v2.9.1 milestone](https://github.com/slackapi/python-slackclient/milestone/18?closed=1) to know the complete list of the issues resolved by this release. + +**Updates** + +- \[WebClient\]\[WebhookClient\] #820 #821 #822 The proxy option in WebClient/WebhookClient no longer works - Thanks \@seratch + +## v2.9.0 (2020-09-17) + +Refer to [v2.9.0 milestone](https://github.com/slackapi/python-slackclient/milestone/17?closed=1) to know the complete list of the issues resolved by this release. + +**Updates** + +- \[WebClient\] #811 Add workflows.\* API support - Thanks \@misscoded +- \[WebClient\] #810 #809 Only set default filename in files_upload if file is an instance of str - Thanks \@csaska + +## v2.8.2 (2020-09-04) + +Refer to [v2.8.2 milestone](https://github.com/slackapi/python-slackclient/milestone/16?closed=1) to know the complete list of the issues resolved by this release. + +**Updates** + +- \[WebClient\] #795 #794 Add admin.conversations.\* API methods in WebClient/AsyncWebClient - Thanks \@ruberVulpes +- \[WebClient\] #796 Fix a link to the Static options documentation - Thanks \@Jamim + +## v2.8.1 (2020-08-28) + +Refer to [v2.8.1 milestone](https://github.com/slackapi/python-slackclient/milestone/15?closed=1) to know the complete list of the issues resolved by this release. + +**Updates** + +- \[WebClient\] #778 #779 Adding support for View objects for views.push/update/publish - Thanks \@ruberVulpes +- \[WebClient\] #786 Fix admin.conversations.restrictAccess.\* methods to match documentation - Thanks \@ruberVulpes + +## v2.8.0 (2020-08-06) + +Refer to [v2.8.0 milestone](https://github.com/slackapi/python-slackclient/milestone/14?closed=1) to know the complete list of the issues resolved by this release. + +**New Features** + +- \[WebClient\] #765 #766 Introduce AsyncWebClient/AsyncWebhookClient providing coroutines - Thanks \@seratch +- \[Block Kit\] #767 #768 Add \"header\" block support - Thanks \@mwbrooks + +**Updates** + +- \[WebClient\] #738 Add HTTP_PROXY, HTTPS_PROXY env variable support in async WebClient - Thanks \@iamtofr \@seratch +- \[WebClient\] #769 #773 Enable User-Agent to have additional info part - Thanks \@seratch +- \[WebClient\] #770 #771 Fix a bug where `files.upload`\'s file param doesn\'t accept bytes data - Thanks \@seratch + +## v2.7.3 (2020-07-20) + +Refer to [v2.7.3 milestone](https://github.com/slackapi/python-slackclient/milestone/13?closed=1) to know the complete list of the issues resolved by this release. + +**Updates** + +- \[WebClient\] #754 Fix #729 Add admin.conversations.restrictAccess.\*, conversations.mark API - Thanks \@ruberVulpes \@kian2attari +- \[WebClient\] #758 Fix #757 Add admin.usergroups.addTeams, calls.participants.remove API - Thanks \@seratch +- \[WebClient\] #727 Fix #645 Unclosed client session - Thanks \@NoAnyLove \@jourdanrodrigues +- \[WebClient\] #745 Fix #744 a validation logic bug in DatePickerElement - Thanks \@dzudi941 +- \[WebClient\] #752 Fix #733 Better error handling when getting TimeoutError in RTMClient#start() - Thanks \@liorblob \@seratch +- \[WebClient\] #751 Fix #718 by handling unexpected response body format - Thanks \@jeffbuswell \@seratch + +## v2.7.2 (2020-06-23) + +Refer to [v2.7.2 milestone](https://github.com/slackapi/python-slackclient/milestone/12?closed=1) to know the complete list of the issues resolved by this release. + +**Updates** + +- \[WebClient\] Fix #728 by adding bytearray support in files_upload (sync mode) - Thanks \@sofya-salmanova \@seratch +- \[WebClient\] #726 Fix InputBlock.hint validation failure - Thanks \@jourdanrodrigues +- \[WebClient\] #723 Correct the default value of InputBlock.label, hint - Thanks \@jourdanrodrigues + +## v2.7.1 (2020-06-04) + +This release includes the fixes for regression bugs in `WebClient` since v2.6.0. Refer to [v2.7.1 milestone](https://github.com/slackapi/python-slackclient/milestone/11?closed=1) to know the complete list of the issues resolved by this release. + +**Updates** + +- \[WebClient\] #716 #712 Support timeout in sync sync web clients - Thanks \@DanialErfanian \@seratch +- \[WebClient\] #713 Support custom SSL context in sync sync web clients - Thanks \@austinbutler +- \[WebClient\] #715 #714 Support proxy in sync sync web clients - Thanks \@austinbutler \@seratch + +## v2.7.0 (2020-06-02) + +Refer to [v2.7.0 milestone](https://github.com/slackapi/python-slackclient/milestone/6?closed=1) to know the complete list of the issues resolved by this release. + +**New Features** + +- \[WebhookClient\] #707 #270 #531 Add `WebhookClient` for Incoming Webhooks & response_url - Thanks \@seratch \@chubz \@Ambro17 + +**Updates** + +- \[WebClient\] #704 #695 Add `calls\_\*` methods to `WebClient` and `CallBlock` in Block Kit classes - Thanks \@seratch +- \[WebClient\] #710 #536 Allow Tokens to be specified per request - Thanks \@seratch +- \[WebClient\] #709 #708 Add default_to_current_conversation in conversations_select elements - Thanks \@seratch + +## v2.6.2 (2020-05-28) + +Refer to [v2.6.2 milestone](https://github.com/slackapi/python-slackclient/milestone/9?closed=1) to know the complete details of this release. + +**Updates** + +- \[WebClient\] #705 WebClient\'s paginated API calls may fail with no params - Thanks \@seratch + +## v2.6.1 (2020-05-24) + +This patch release is a quick fix for #701, a major issue that affected RTMClient users in v2.6.0. The malfunction was introduced by #667 trying to address #558 #619. Those issues were reopened and will be resolved by another approach. Refer to [v2.6.1 milestone](https://github.com/slackapi/python-slackclient/milestone/8) to know the complete list of the issues resolved by this release. + +**Updates** + +- \[RTMClient\] #701 RTMClient drops some messages when they come in rapid succession - Thanks \@pbrackin \@seratch + +## v2.6.0 (2020-05-21) + +Refer to [v2.6.0 milestone](https://github.com/slackapi/python-slackclient/milestone/5?closed=1) to know the complete list of the issues resolved by this release. + +**New Features** + +- \[Block Kit\] #659 Add complete supports for Block Kit components and fixed a few existing bugs as well (#500 #519 #623 #632 #635 #639 #676 #699) - Thanks \@seratch \@diurnalist \@ruberVulpes \@jeremyschulman \@e271828- \@RodneyU215 +- \[Signature\] #686 Add slack.signature.SignatureVerifier for request verification - Thanks \@seratch +- \[WebClient\] #682 Add missing Grid admin APIs (`admin.usergroups.\*`, `admin.users.\*`, `admin.apps.\*`) - Thanks \@stevengill \@seratch + +**Updates** + +- \[WebClient\]\[RTMClient\] Fixed a bunch of the currency issues this SDK had (#429 #463 #492 #497 #530 #569 #605 #613 #626 #630 #631 #633 #669) - Thanks \@seratch \@aaguilartablada \@aoberoi \@stevengill \@marshallino16 +- \[WebClient\] #681 #560 Enable using bool values for request parameters - Thanks \@roman-kachanovsky \@seratch +- \[WebClient\] #661 #678 Improve handling of required \"ids\" parameters (e.g., channel_ids, users) - Thanks \@seratch +- \[WebClient\] #680 Add non-conversation API deprecation warnings - Thanks \@seratch +- \[WebClient\] #671 #670 Enable passing None values for request parameters (they used to result in errors) - Thanks \@yuji38kwmt \@seratch +- \[WebClient\] #673 Fix #672 files.upload fails with a filepath containing multi byte chars - Thanks \@yuji38kwmt \@seratch +- \[WebClient\] #656 Fix #594 preview_image for files.remote.add API method is not properly supported - Thanks \@Eothred \@seratch +- \[Maintenance\] #618 Add py.typed file to package distribution - Thanks \@JKillian +- \[WebClient\] #599 Strip token string parameters of whitespace - Thanks \@TheFrozenFire +- \[WebClient\] #692 Fix superfluous_charset warnings since v2.4.0 - Thanks \@seratch +- \[WebClient\] #652 Update oauth_v2_access to include redirect_uri (as optional) - Thanks \@tomasreimers + +## v2.5.0 (2019-12-09) + +**New Features** + +- \[WebClient\] Adding new oauth.v2.access Web API method. #577 + +## v2.4.0 (2019-11-27) + +**New Features** + +- \[WebClient\] Adding new admin.\* Web API methods. #571 + +**Updates** + +- \[WebClient\] We\'re no longer validating token types for Web API methods. Improves compatibility with granular bot permissions. #568 (Thanks \@Smotko) +- \[WebClient\] Correcting typos in descriptions #554 (Thanks \@phamk) +- \[WebClient\] Fixed \'iteracting\' typo in library file headers #564 (Thanks \@acabey) +- \[Message Builders\] Remove value from LinkButtonElement #563 (Thanks \@pedroma) + +## v2.3.1 (2019-10-29) + +**Updates** + +- \[WebClient\] Fixing a regression that causes the client to close sessions prematurely. #544 (Thanks \@fatih-acar!) +- \[WebClient\] Adding required missing view param to views.update Web API method. #542 + +## v2.3.0 (2019-10-22) + +**New Features** + +- \[WebClient\] Adding new views.publish Web API method. #540 + +**Updates** + +- \[WebClient\] Some server responses don\'t return json. Correcting initial assumption. #540 +- \[Maintenance\] Add `py.typed` to mark the library to support type hinting #524s + +## v2.2.1 (2019-10-08) + +**Updates** + +- \[Docs\] Fix Indentation of Code Snippets in README.md #525 (Thanks \@abhishekjiitr) +- \[WebClient\] Fix Web Client custom iterator #521 (Thanks \@smaeda-ks) +- \[WebClient\] Oauth previously failed to pass along credentials properly. This is fixed now. #527 +- \[WebClient\] When a SlackApiError occurs we\'re now passing the entire SlackResponse into the exception. #527 + +## v2.2.0 (2019-09-25) + +**New Features** + +- \[WebClient\] Adding new admin and remote files API methods. #501 +- \[WebClient\] Adding new view API methods. #517 + +**Updates** + +- \[Message Builders\] Update BlockAttachment to not send invalid JSON due to fields attribute #473 (Thanks \@paul-griffith) +- \[Docs\] Add RTM section for docs v2 #477 (Thanks \@shanedewael) +- \[Docs\] Fix typo; recieved -\> received #478 (Thanks \@joakimnordling) +- \[Docs\] Fix block kit link & update docs #484 (Thanks \@clavin) +- \[RTMClient\] Return callback from `RTMClient.run_on` #490 (Thanks \@clavin) +- \[Docs\] Fix link to Auth Guide in readme #498 (Thanks \@asherf) +- \[Docs\] Fix missing word and typo #512 (Thanks \@marks) +- \[Message Builders\] bugfix for value length in button elements #514 (Thanks \@avanderm) +- \[Docs\] Fixes formatting #515 (Thanks \@vpetersson) +- \[Docs\] Improve a code snippet on README #516 (Thanks \@seratch) +- \[WebClient\] Fixed an OAuth Headers bug and made the `token` param optional. #517 + +## v2.1.0 (2019-07-01) + +**New Features** + +- Type-hinted helper classes for building messages in v2 #400 (Thanks \@paul-griffith) + +**Breaking Changes** + +- \[RTMClient\] Converted the `RTMClient#typing()` function to async #446 + +**Updates** + +- \[RTMClient\] Handle case in which aiohttp closes the websocket due to lack of ping responses. #453 (Thanks \@flyte) +- Modify package identifier in user agent to match v1.x identifier #418 (Thanks \@aoberoi) +- \[WebClient\] Fixed typo in Scheduled message #428 & #435 (Thanks \@splinterific) +- Transform install_requires of \'aiodns\' into extras_require. #440 (Thanks \@staticdev) + +**Thank you!** To everyone who has opened, commented or reacted to an issue; this project is better because of you! Thank you for helping the Slack community! + +## v2.0.0 (2019-04-29) + +[Original RFC](https://github.com/slackapi/python-slackclient/issues/384) + +[v2 PR](https://github.com/slackapi/python-slackclient/pull/394) + +**New Features** + +- Client Decomposition: We've split the client into two. + - WebClient: A HTTP client focused on Slack\'s Web API. + - RTMClient: A websocket client focused on Slack\'s RTM API. +- RTMClient: Completely redesigned, this client allows you to link your application\'s callbacks to corresponding Slack events. +- WebClient: The WebClient now provides built-in methods for Slack\'s Web API. These methods act as helpers enabling you to focus less on how the request is constructed. Here are a few things this provides: + - Basic information about each method through the docstring. + - Easy File Uploads: You can now pass in the location of a file and the library will handle opening and retrieving the file object to be transmitted. + - Token type validation: This gives you better error messaging when you\'re attempting to consume an API method that your token doesn\'t have access to. + - Constructs requests using Slack\'s preferred HTTP methods and content-types. + +**Breaking Changes:** If you\'re migrating from v1.x of slackclient to v2.x, Please follow our [migration guide](https://github.com/slackapi/python-slackclient/wiki/Migrating-to-2.x) to ensure your app continues working after updating. + +**Thank you!** This release would not have been possible without the support of our community. Thank you to everyone who has contributed to this release. + +## v1.3.1 (2019-02-28) + +- Lock websocket-client version to \< 0.55.0: temp fix for #385 + +## v1.3.0 (2018-09-11) + +**New Features** + +- Adds support for short lived tokens and automatic token refresh #347 (Thanks \@roach!) + +**Other** + +- Update RTM rate limiting comment and error message #308 (Thanks \@benoitlavigne!) +- Use logging instead of traceback #309 (Thanks \@harlowja!) +- Remove Python 3.3 from test environments #346 (Thanks \@roach!) +- Enforced linting when using VSCode. #347 (Thanks \@roach!) + +## v1.2.1 (2018-03-26) + +- Added rate limit handling for rtm connections (thanks \@jayalane!) + +## v1.2.0 (2018-03-20) + +- You can now tell the RTM client to automatically reconnect by passing `auto_reconnect=True` + +## v1.1.3 (2018-03-01) + +- Fixed another API param encoding bug. It encodes things properly now. + +## v1.1.2 (2018-01-31) + +- Fixed an encoding issue which was encoding some Web API params incorrectly + +## v1.1.1 (2018-01-30) + +- Adds HTTP response headers to `api_call` responses to expose things like rate limit info +- Moves `token` into auth header rather than request params + +## v1.1.0 (2017-11-21) + +- Adds new SlackClientError and ResponseParseError types to describe errors - thanks \@aoberoi! +- Fix Build Error (#245) - thanks \@stasfilin! +- Include email as user property (#173) - thanks \@acaire! +- Add http reply into slack login and slack connection error (#216) - thanks \@harlowja! +- Removed unused exception class (#233) +- Fix rtm_send_message bug (#225) - thanks \@kt5356! +- Allow use of custom parameters on rtm_connect() (#210) - thanks \@kamushadenes! +- Fix link to rtm.connect docs (#223) - \@sampart! + +## v1.0.9 (2017-08-31) + +- Fixed rtm_send_message ID bug introduced in 1.0.8 + +## v1.0.8 (2017-08-31) + +- Added rtm.connect support + +## v1.0.7 (2017-08-02) + +- Fixes an issue where connecting over RTM to large teams may result in "Websocket URL expired" errors +- A bunch of packaging improvements + +## v1.0.6 (2017-06-12) + +- Added proxy support (thanks \@timfeirg!) +- Tidied up docs (thanks \@schlueter!) +- Added tox settings for Python 3 testing (thanks \@cclauss!) + +## v1.0.5 (2017-01-23) + +- Allow RTM Channel.send_message to reply to a thread +- Index users by ID instead of Name (non-breaking change) +- Added timeout to api calls +- Fixed a typo about token access in auth.rst, thanks \@kelvintaywl! +- Added Message Threads to the docs + +## v1.0.4 (2016-12-15) + +- Fixed the ability to search for a user by ID + +## v1.0.3 (2016-12-13) + +- Fixed an issue causing RTM connections to fail for large teams + +## v1.0.2 (2016-09-22) + +- Removed unused ping counter +- Fixed contributor guidelines links +- Updated documentation +- Fix bug preventing API calls requiring a file ID +- Removes files from api_calls before JSON encoding, so the request is properly formatted + +## v1.0.1 (2016-03-25) + +- Fix for \_\_eq\_\_ comparison in channels using \'#\' in channel name +- Added copyright info to the LICENSE file + +## v1.0.0 (2016-02-28) + +- The `api_call` function now returns a decoded JSON object, rather than a JSON encoded string +- Some `api_call` calls now call actions on the parent server object: + - `dm.open` + - `mpdm.open`, `groups.create`, `groups.createChild` + - `channels.create`, `channels.join` + +## v0.18.0 (2016-02-21) + +- Moves to use semver for versioning +- Adds support for private groups and MPDMs +- Switches to use requests instead of urllib +- Gets Travis CI integration working +- Fixes some formatting issues so the code will work for python 2.6 +- Cleans up some unused imports, some PEP-8 fixes and a couple bad default args fixes + +## v0.17.0 (2016-02-15) + +- Fixes the server so that it doesn\'t add duplicate users or channels to its internal lists, https://github.com/slackapi/python-slackclient/commit/0cb4bcd6e887b428e27e8059b6278b86ee661aaa +- README updates: + - Updates the URLs pointing to Slack docs for configuring authentication, https://github.com/slackapi/python-slackclient/commit/7d01515cebc80918a29100b0e4793790eb83e7b9 + - s/channnels/channels, https://github.com/slackapi/python-slackclient/commit/d45285d2f1025899dcd65e259624ee73771f94bb + - Adds users to the local cache when they join the team, https://github.com/slackapi/python-slackclient/commit/f7bb8889580cc34471ba1ddc05afc34d1a5efa23 + - Fixes urllib py 2/3 compatibility, https://github.com/slackapi/python-slackclient/commit/1046cc2375a85a22e94573e2aad954ba7287c886 diff --git a/docs/english/legacy/conversations.md b/docs/english/legacy/conversations.md new file mode 100644 index 000000000..e617273ae --- /dev/null +++ b/docs/english/legacy/conversations.md @@ -0,0 +1,123 @@ +# Conversations API + +:::danger[The [`slackclient`](https://pypi.org/project/slackclient/) PyPI project is in maintenance mode and the [slack-sdk](https://pypi.org/project/slack-sdk/) project is its successor.] + +The v3 SDK provides additional features such as Socket Mode, OAuth flow, SCIM API, Audit Logs API, better async support, retry handlers, and more. + +::: + +The Slack Conversations API provides your app with a unified interface to work with all the channel-like things encountered in Slack: public channels, private channels, direct messages, group direct messages, and shared channels. + +Refer to [using the Conversations API](/apis/web-api/using-the-conversations-api) for more information. + +## Direct messages {#direct-messages} + +The `conversations.open` API method opens either a 1:1 direct message with a single user or a multi-person direct message, depending on the number of users supplied to the `users` parameter. (For public or private channels, use the `conversations.create` API method.) + +Provide a `users` parameter as an array with 1-8 user IDs to open or resume a conversation. Providing only 1 ID will create a direct message. providing more IDs will create a new multi-party direct message or will resume an existing conversation. + +Subsequent calls with the same set of users will return the already existing conversation. + +``` python +import os +from slack import WebClient + +client = WebClient(token=os.environ["SLACK_API_TOKEN"]) +response = client.conversations_open(users=["W123456789", "U987654321"]) +``` + +See the [`conversations.open`](/reference/methods/conversations.open) API method for additional details. + +## Creating channels {#creating-channels} + +Creates a new channel, either public or private. The `name` parameter is required and may contain numbers, letters, hyphens, or underscores, and must contain fewer than 80 characters. To make the channel private, set the optional `is_private` parameter to `True`. + +``` python +import os +from slack import WebClient +from time import time + +client = WebClient(token=os.environ["SLACK_API_TOKEN"]) +channel_name = f"my-private-channel-{round(time())}" +response = client.conversations_create( + name=channel_name, + is_private=True +) +channel_id = response["channel"]["id"] +response = client.conversations_archive(channel=channel_id) +``` + +See the [`conversations.create`](/reference/methods/conversations.create) API method for additional details. + +## Getting conversation information {#more-information} + +To retrieve a set of metadata about a channel (public, private, DM, or multi-party DM), use the `conversations.info` API method. The `channel` parameter is required and must be a valid channel ID. The optional `include_locale` boolean parameter will return locale data, which may be useful if you wish to return localized responses. The `include_num_members` boolean parameter will return the number of people in a channel. + +``` python +import os +from slack import WebClient + +client = WebClient(token=os.environ["SLACK_API_TOKEN"]) +response = client.conversations_info( + channel="C031415926", + include_num_members=1 +) +``` + +See the [`conversations.info`](/reference/methods/conversations.info) API method for more details. + +## Listing conversations {#listing-conversations} + +To get a list of all the conversations in a workspace, use the `conversations.list` API method. By default, only public conversations are returned. Use the `types` parameter specify which types of conversations you're interested in. Note that `types` is a string of comma-separated values. + +``` python +import os +from slack import WebClient + +client = WebClient(token=os.environ["SLACK_API_TOKEN"]) +response = client.conversations_list() +conversations = response["channels"] +``` + +Use the `types` parameter to request additional channels, including `public_channel`, `private_channel`, `mpdm`, and `dm`. This parameter is a string of comma-separated values. + +``` python +import os +from slack import WebClient + +client = WebClient(token=os.environ["SLACK_API_TOKEN"]) +response = client.conversations_list( + types="public_channel, private_channel" +) +``` + +See the [`conversations.list`](/reference/methods/conversations.list) API method for more details. + +## Getting members of a conversation {#get-members} + +To get a list of members for a conversation, use the `conversations.members` API method with the required `channel` parameter. + +``` python +import os +from slack import WebClient + +client = WebClient(token=os.environ["SLACK_API_TOKEN"]) +response = client.conversations_members(channel="C16180339") +user_ids = response["members"] +``` + +See the [`conversations.members`](/reference/methods/conversations.members) API method for more details. + +## Leaving a conversation {#leave-conversations} + +To leave a conversation, use the `conversations.leave` API method with the required `channel` parameter containing the ID of the channel to leave. + +``` python +import os +from slack import WebClient + +client = WebClient(token=os.environ["SLACK_API_TOKEN"]) +response = client.conversations_leave(channel="C27182818") +``` + +See the [`conversations.leave`](/reference/methods/conversations.leave) API method for more details. diff --git a/docs/english/legacy/faq.md b/docs/english/legacy/faq.md new file mode 100644 index 000000000..bc2d70fa0 --- /dev/null +++ b/docs/english/legacy/faq.md @@ -0,0 +1,70 @@ +# FAQs + +:::danger[The [`slackclient`](https://pypi.org/project/slackclient/) PyPI project is in maintenance mode and the [slack-sdk](https://pypi.org/project/slack-sdk/) project is its successor.] + +The v3 SDK provides additional features such as Socket Mode, OAuth flow, SCIM API, Audit Logs API, better async support, retry handlers, and more. + +::: + +## Why can't I install `slackclient`? + +We recommend using [virtualenv (venv)](https://docs.python.org/3/tutorial/venv.html) to set up your +Python runtime as follows: + +``` bash +# Create a dedicated virtual env for running your Python scripts +python -m venv env + +# Run env\Scripts\activate on Windows OS +source env/bin/activate + +# Install slackclient PyPI package +pip install "slackclient>=2.0" + +# Set your token as an env variable (`set` command for Windows OS) +export SLACK_API_TOKEN=xoxb-*** +``` + +Then, verify the following code works on the Python REPL (you can start it using just `python`): + +``` python +import os +import logging +from slack import WebClient +logging.basicConfig(level=logging.DEBUG) +client = WebClient(token=os.environ["SLACK_API_TOKEN"]) +res = client.api_test() +``` + +If you encounter an error saying +`AttributeError: module 'slack' has no attribute 'WebClient'`, run +`pip list`. If you find both `slackclient` and `slack` in the output, +try removing `slack` by `pip uninstall slack` and reinstalling +`slackclient`. + +## Should I go with `run_async`? + +For most cases, we recommend going with `run_async=False` mode. So, the default is `False`. + +If your application turns `run_async` on, the app should follow efficient ways to use [asyncio](https://docs.python.org/3/library/asyncio.html)'s non-blocking event loops and [aiohttp](https://docs.aiohttp.org/en/stable/). Also, consider using async frameworks and their appropriate runtime. Running event loops along with Flask or similar may not be a good fit. + +If you have to simultaneously run `WebClient` with `run_async=True` outside an event loop for some reason, sharing a single `WebClient` instance doesn't work for you. Create an instance every time you run the code. The `run_async=False` mode doesn't have such issues. + +## What if I found a bug? + +That's great! Thank you. Let us know by [creating an issue](https://github.com/slackapi/python-slack-sdk/issues/new/choose), or if you're feeling particularly ambitious, why not submit a pull request with a bug fix? Check out our contributor's guide [here](https://github.com/SlackAPI/python-slack-sdk/blob/main/.github/contributing.md). + +## What if I have a feature suggestion? + +There's always something more that could be added! Let us know by [creating an issue](https://github.com/slackapi/python-slack-sdk/issues/new/choose) to start a discussion around the proposed feature. If you're feeling particularly ambitious, why not write the feature yourself, and submit a pull request? We love feedback and we also love help from our amazing community of developers! + +## How do I contribute? + +What an excellent question. First of all, please have a look at our +contributor's guide [here](https://github.com/SlackAPI/python-slack-sdk/blob/main/.github/contributing.md). + +All done? Great! While we're super excited to incorporate your new feature, there are a couple of things we want to make sure you've given thought to: +* Please include unit tests for your new code. But don't just aim to increase the test coverage, rather, we expect you to have written thoughtful tests that ensure your new feature will continue to work as expected, and to help future contributors to ensure they don't break it! +* Please document your new feature. Think about concrete use cases for your feature, and add a section to the appropriate document, including a complete sample program that demonstrates your feature. + +Including these two items with your pull request will totally make our day - and, more importantly, your future users' days! diff --git a/docs/english/legacy/index.md b/docs/english/legacy/index.md new file mode 100644 index 000000000..e2ca20613 --- /dev/null +++ b/docs/english/legacy/index.md @@ -0,0 +1,57 @@ +# Overview + +:::danger[The [`slackclient`](https://pypi.org/project/slackclient/) PyPI project is in maintenance mode and the [slack-sdk](https://pypi.org/project/slack-sdk/) project is its successor.] + +The v3 SDK provides additional features such as Socket Mode, OAuth flow, SCIM API, Audit Logs API, better async support, retry handlers, and more. + +::: + +Refer to the [migration guide](/tools/python-slack-sdk/v3-migration) to learn how to smoothly migrate your existing code. + +Slack APIs allow anyone to build full featured integrations that extend and expand the capabilities of your Slack workspace. These APIs allow you to build applications that interact with Slack just like the people on your team. They can post messages, respond to events that happen, and build complex UIs for getting work done. + +To make it easier for Python programmers to build Slack applications, we've provided this open source SDK that will help you get started building Python apps as quickly as possible. The current version is built for Python 3.7 and higher. + +## Slack platform basics {#platform-basics} + +If you're new to the Slack platform, we have a general purpose [quickstart guide](/quickstart) that isn't specific to any language or framework. Its a great place to learn all about the concepts that go into building a great Slack app. + +Before you get started building on the Slack platform, you need to set up [your app's configuration](https://api.slack.com/apps/new). This is where you define things like your apps permissions and the endpoints that Slack should use for interacting with the backend you'll build using Python. + +The app configuration page is also where you'll acquire the OAuth token you'll use to call Slack API methods. Treat this token with care, just like you would a password, because it has access to your workspace and can potentially read and write data to and from it. + +## Installation {#installation} + +We recommend using [PyPI](https://pypi.python.org/pypi) to install as follows: + +``` bash +pip install slackclient +``` + +Of course, you can always pull the source code directly into your project like this: + +``` bash +git clone https://github.com/slackapi/python-slackclient.git +``` + +And then, save a few lines of code as `./test.py` like so: + +``` python +# test.py +import sys +# Load the local source directly +sys.path.insert(1, "./python-slackclient") +# Enable debug logging +import logging +logging.basicConfig(level=logging.DEBUG) +# Verify it works +from slack import WebClient +client = WebClient() +api_response = client.api_test() +``` + +Run the code as follows: + +``` bash +python test.py +``` diff --git a/docs/english/legacy/real_time_messaging.md b/docs/english/legacy/real_time_messaging.md new file mode 100644 index 000000000..3b16e3ecd --- /dev/null +++ b/docs/english/legacy/real_time_messaging.md @@ -0,0 +1,98 @@ +# Real Time Messaging (RTM) + +:::danger[The [`slackclient`](https://pypi.org/project/slackclient/) PyPI project is in maintenance mode and the [slack-sdk](https://pypi.org/project/slack-sdk/) project is its successor.] + +The v3 SDK provides additional features such as Socket Mode, OAuth flow, SCIM API, Audit Logs API, better async support, retry handlers, and more. + +::: + +The [Legacy Real Time Messaging (RTM) API](/legacy/legacy-rtm-api) is a WebSocket-based API that allows you to receive events from Slack in real time and to send messages as users. + +If you prefer events to be pushed to your app, we recommend using the HTTP-based [Events API](/apis/events-api) instead. The Events API contains some events that aren't supported in the Legacy RTM API (such as the [app_home_opened event](/reference/events/app_home_opened)), and it supports most of the event types in the Legacy RTM API. If you'd like to use the Events API, you can use the [Python Slack Events Adaptor](https://github.com/slackapi/python-slack-events-api). + +The RTMClient allows apps to communicate with the Legacy RTM API. + +The event-driven architecture of this client allows you to simply link callbacks to their corresponding events. When an event occurs, this client executes your callback while passing along any information it receives. We also give you the ability to call our web client from inside your callbacks. + +In our example below, we watch for a [message event](/reference/events/message) that contains \"Hello\" and if it's received, we call the `say_hello()` function. We then issue a call to the web client to post back to the channel saying \"Hi\" to the user. + +## Configuring the RTM API {#configuration} + +Events using the Legacy RTM API **must** use a Slack app with a plain `bot` scope. + +If you already have a Slack app with a plain `bot` scope, you can use those credentials. If you don't and need to use the Legacy RTM API, you can create a Slack app [here](https://api.slack.com/apps?new_classic_app=1). Even if the Slack app configuration pages encourage you to upgrade to a newer permission model, don't upgrade it and continue using the \"classic\" bot permission. + +## Connecting to the RTM API {#connecting} + +``` python +import os +from slack import RTMClient + +@RTMClient.run_on(event="message") +def say_hello(**payload): + data = payload['data'] + web_client = payload['web_client'] + + if 'Hello' in data['text']: + channel_id = data['channel'] + thread_ts = data['ts'] + user = data['user'] # This is not username but user ID (the format is either U*** or W***) + + web_client.chat_postMessage( + channel=channel_id, + text=f"Hi <@{user}>!", + thread_ts=thread_ts + ) + +slack_token = os.environ["SLACK_API_TOKEN"] +rtm_client = RTMClient(token=slack_token) +rtm_client.start() +``` + +## The `rtm.start` vs. `rtm.connect` API methods {#rtm-methods} + +By default, the RTM client uses the [`rtm.connect`](/reference/methods/rtm.connect) API method to establish a WebSocket connection with Slack. The response contains basic information about the team and WebSocket URL. + +If you'd rather use the [`rtm.start`](/reference/methods/rtm.start) API method to establish the connection, which provides more information about the conversations and users on the team, you can set the `connect_method` option to `rtm.start` when instantiating the RTM Client. Note that on larger teams, use of `rtm.start` can be slow and unreliable. + +``` python +import os +from slack import RTMClient + +@RTMClient.run_on(event="message") +def say_hello(**payload): + data = payload['data'] + web_client = payload['web_client'] + if 'text' in data and 'Hello' in data['text']: + channel_id = data['channel'] + thread_ts = data['ts'] + user = data['user'] # This is not username but user ID (the format is either U*** or W***) + + web_client.chat_postMessage( + channel=channel_id, + text=f"Hi <@{user}>!", + thread_ts=thread_ts + ) + +slack_token = os.environ["SLACK_API_TOKEN"] +rtm_client = RTMClient( + token=slack_token, + connect_method='rtm.start' +) +rtm_client.start() +``` + +See the [`rtm.connect`](/reference/methods/rtm.connect) and [`rtm.start`](/reference/methods/rtm.start) API methods for more details. + +## RTM events {#rtm-events} + +``` javascript +{ + 'type': 'message', + 'ts': '1358878749.000002', + 'user': 'U023BECGF', + 'text': 'Hello' +} +``` + +Refer to the [Legacy RTM API](/legacy/legacy-rtm-api) page for more information. diff --git a/docs/english/oauth.md b/docs/english/oauth.md new file mode 100644 index 000000000..61b9af98f --- /dev/null +++ b/docs/english/oauth.md @@ -0,0 +1,251 @@ +# OAuth modules + +This page explains how to handle the Slack OAuth flow. If you're looking for a much easier way to do this, check out [Bolt for Python](https://github.com/slackapi/bolt-python), a full-stack Slack app framework. With Bolt, you won't need to implement most of the following code on your own. + +Refer to the [Python document for this module](https://docs.slack.dev/tools/python-slack-sdk/reference) for more details. + +## App installation flow {#app-installation} + +OAuth allows a user in any Slack workspace to install your app. At the end of the OAuth flow, your app gains an access token. Refer to the [installing with OAuth](/authentication/installing-with-oauth) guide for details. + +The Python Slack SDK provides the necessary modules for building the OAuth flow. + +### Starting an OAuth flow {#oauth-flow} + +The first step of the OAuth flow is to redirect a Slack user to [authorize](https://slack.com/oauth/v2/authorize) with a valid `state` parameter. To implement this process, you can use the following modules. + +Module | What its for | Default Implementation +----------------------|-----------------------------------------|------------------------- +`InstallationStore` | Persist installation data and lookup it by IDs. | `FileInstallationStore` +`OAuthStateStore` | Issue and consume `state` parameter value on the server-side. | `FileOAuthStateStore` +`AuthorizeUrlGenerator` | Build https://slack.com/oauth/v2/authorize with sufficient query parameters | (same) + +The code snippet below demonstrates how to build it using [Flask](https://flask.palletsprojects.com/). + +``` python +import os +import html +from slack_sdk.oauth import AuthorizeUrlGenerator +from slack_sdk.oauth.installation_store import FileInstallationStore, Installation +from slack_sdk.oauth.state_store import FileOAuthStateStore + +# Issue and consume state parameter value on the server-side. +state_store = FileOAuthStateStore(expiration_seconds=300, base_dir="./data") +# Persist installation data and lookup it by IDs. +installation_store = FileInstallationStore(base_dir="./data") + +# Build https://slack.com/oauth/v2/authorize with sufficient query parameters +authorize_url_generator = AuthorizeUrlGenerator( + client_id=os.environ["SLACK_CLIENT_ID"], + scopes=["app_mentions:read", "chat:write"], + user_scopes=["search:read"], +) + +from flask import Flask, request, make_response +app = Flask(__name__) + +@app.route("/slack/install", methods=["GET"]) +def oauth_start(): + # Generate a random value and store it on the server-side + state = state_store.issue() + # https://slack.com/oauth/v2/authorize?state=(generated value)&client_id={client_id}&scope=app_mentions:read,chat:write&user_scope=search:read + url = authorize_url_generator.generate(state) + return f'' \ + f'' +``` + +When accessing `https://(your domain)/slack/install`, you will see an \"Add to Slack\" button on the page. You can start the app's installation flow by clicking the button. + +### Handling a callback request from Slack {#handling-callback-requests} + +If all is well, a user goes through the Slack app installation UI and accepts all the scopes your app requests. After that happens, Slack redirects the user back to your specified Redirect URL. + +The redirection gives you a `code` parameter. You can exchange the value for an access token by calling the [oauth.v2.access](/reference/methods/oauth.v2.access) API method. + +``` python +from slack_sdk.web import WebClient +client_secret = os.environ["SLACK_CLIENT_SECRET"] + +# Redirect URL +@app.route("/slack/oauth/callback", methods=["GET"]) +def oauth_callback(): + # Retrieve the auth code and state from the request params + if "code" in request.args: + # Verify the state parameter + if state_store.consume(request.args["state"]): + client = WebClient() # no prepared token needed for this + # Complete the installation by calling oauth.v2.access API method + oauth_response = client.oauth_v2_access( + client_id=client_id, + client_secret=client_secret, + redirect_uri=redirect_uri, + code=request.args["code"] + ) + installed_enterprise = oauth_response.get("enterprise") or {} + is_enterprise_install = oauth_response.get("is_enterprise_install") + installed_team = oauth_response.get("team") or {} + installer = oauth_response.get("authed_user") or {} + incoming_webhook = oauth_response.get("incoming_webhook") or {} + bot_token = oauth_response.get("access_token") + # NOTE: oauth.v2.access doesn't include bot_id in response + bot_id = None + enterprise_url = None + if bot_token is not None: + auth_test = client.auth_test(token=bot_token) + bot_id = auth_test["bot_id"] + if is_enterprise_install is True: + enterprise_url = auth_test.get("url") + + installation = Installation( + app_id=oauth_response.get("app_id"), + enterprise_id=installed_enterprise.get("id"), + enterprise_name=installed_enterprise.get("name"), + enterprise_url=enterprise_url, + team_id=installed_team.get("id"), + team_name=installed_team.get("name"), + bot_token=bot_token, + bot_id=bot_id, + bot_user_id=oauth_response.get("bot_user_id"), + bot_scopes=oauth_response.get("scope"), # comma-separated string + user_id=installer.get("id"), + user_token=installer.get("access_token"), + user_scopes=installer.get("scope"), # comma-separated string + incoming_webhook_url=incoming_webhook.get("url"), + incoming_webhook_channel=incoming_webhook.get("channel"), + incoming_webhook_channel_id=incoming_webhook.get("channel_id"), + incoming_webhook_configuration_url=incoming_webhook.get("configuration_url"), + is_enterprise_install=is_enterprise_install, + token_type=oauth_response.get("token_type"), + ) + + # Store the installation + installation_store.save(installation) + + return "Thanks for installing this app!" + else: + return make_response(f"Try the installation again (the state value is already expired)", 400) + + error = request.args["error"] if "error" in request.args else "" + return make_response(f"Something is wrong with the installation (error: {html.escape(error)})", 400) +``` + +## Token lookup {#token-lookup} + +Now that your Flask app can choose the right access token for incoming event requests, let's add the Slack event handler endpoint. You can use the same `InstallationStore` in the Slack event handler. + +``` python +import json +from slack_sdk.errors import SlackApiError + +from slack_sdk.signature import SignatureVerifier +signing_secret = os.environ["SLACK_SIGNING_SECRET"] +signature_verifier = SignatureVerifier(signing_secret=signing_secret) + +@app.route("/slack/events", methods=["POST"]) +def slack_app(): + # Verify incoming requests from Slack + # https://docs.slack.dev/authentication/verifying-requests-from-slack + if not signature_verifier.is_valid( + body=request.get_data(), + timestamp=request.headers.get("X-Slack-Request-Timestamp"), + signature=request.headers.get("X-Slack-Signature")): + return make_response("invalid request", 403) + + # Handle a slash command invocation + if "command" in request.form \ + and request.form["command"] == "/open-modal": + try: + # in the case where this app gets a request from an Enterprise Grid workspace + enterprise_id = request.form.get("enterprise_id") + # The workspace's ID + team_id = request.form["team_id"] + # Lookup the stored bot token for this workspace + bot = installation_store.find_bot( + enterprise_id=enterprise_id, + team_id=team_id, + ) + bot_token = bot.bot_token if bot else None + if not bot_token: + # The app may be uninstalled or be used in a shared channel + return make_response("Please install this app first!", 200) + + # Open a modal using the valid bot token + client = WebClient(token=bot_token) + trigger_id = request.form["trigger_id"] + response = client.views_open( + trigger_id=trigger_id, + view={ + "type": "modal", + "callback_id": "modal-id", + "title": { + "type": "plain_text", + "text": "Awesome Modal" + }, + "submit": { + "type": "plain_text", + "text": "Submit" + }, + "blocks": [ + { + "type": "input", + "block_id": "b-id", + "label": { + "type": "plain_text", + "text": "Input label", + }, + "element": { + "action_id": "a-id", + "type": "plain_text_input", + } + } + ] + } + ) + return make_response("", 200) + except SlackApiError as e: + code = e.response["error"] + return make_response(f"Failed to open a modal due to {code}", 200) + + elif "payload" in request.form: + # Data submission from the modal + payload = json.loads(request.form["payload"]) + if payload["type"] == "view_submission" \ + and payload["view"]["callback_id"] == "modal-id": + submitted_data = payload["view"]["state"]["values"] + print(submitted_data) # {'b-id': {'a-id': {'type': 'plain_text_input', 'value': 'your input'}}} + # You can use WebClient with a valid token here too + return make_response("", 200) + + # Indicate unsupported request patterns + return make_response("", 404) +``` + +## Sign in with Slack {#siws} + +[Sign in with Slack](/authentication/sign-in-with-slack) helps users log into your service using their Slack profile. The platform feature was upgraded to be compatible with the standard [OpenID Connect](https://openid.net/connect/) specification. With slack-sdk v3.9+, implementing the OAuth flow is much easier. + +When you create a new Slack app, set the following user scopes: + +``` yaml +oauth_config: + redirect_urls: + - https://{your-domain}/slack/oauth_redirect + scopes: + user: + - openid # required + - email # optional + - profile # optional +``` + +Check [the Flask app example](https://github.com/slackapi/python-slack-sdk/blob/main/integration_tests/samples/openid_connect/flask_example.py) to learn how to implement an app that handles the OpenID Connect flow with your end-users as follows: + +**Build the OpenID Connect authorize URL** + +- `slack_sdk.oauth.OpenIDConnectAuthorizeUrlGenerator` helps you do this. +- `slack_sdk.oauth.OAuthStateStore` is still available for generating the `state` parameter value. It's available for `nonce` management, too. + +**openid.connect.\* API calls** + +- `WebClient` can perform `openid.connect.token` API calls with given `code` parameter. + +If you want to know the way with asyncio, check [the Sanic app example](https://github.com/slackapi/python-slack-sdk/blob/main/integration_tests/samples/openid_connect/sanic_example.py) in the same directory. diff --git a/docs/english/rtm.md b/docs/english/rtm.md new file mode 100644 index 000000000..d609aab04 --- /dev/null +++ b/docs/english/rtm.md @@ -0,0 +1,91 @@ +# RTM API client + +The [Legacy Real Time Messaging (RTM) API](/legacy/legacy-rtm-api) is a WebSocket-based API that allows you to receive events from Slack in real time and to send messages as users. + +If you prefer events to be pushed to your app, we recommend using the HTTP-based [Events API](/apis/events-api) instead. The Events API contains some events that aren't supported in the Legacy RTM API (such as the [app_home_opened event](/reference/events/app_home_opened)), and it supports most of the event types in the Legacy RTM API. If you'd like to use the Events API, you can use the [Python Slack Events Adaptor](https://github.com/slackapi/python-slack-events-api). + +The RTMClient allows apps to communicate with the Legacy RTM API. + +The event-driven architecture of this client allows you to simply link callbacks to their corresponding events. When an event occurs, this client executes your callback while passing along any information it receives. We also give you the ability to call our web client from inside your callbacks. + +In our example below, we watch for a [message event](/reference/events/message) that contains \"Hello\" and if it's received, we call the `say_hello()` function. We then issue a call to the web client to post back to the channel saying \"Hi\" to the user. + +## Configuring the RTM API {#configuration} + +Events using the Legacy RTM API **must** use a Slack app with a plain `bot` scope. + +If you already have a Slack app with a plain `bot` scope, you can use those credentials. If you don't and need to use the Legacy RTM API, you can create a Slack app [here](https://api.slack.com/apps?new_classic_app=1). Even if the Slack app configuration pages encourage you to upgrade to a newer permission model, don't upgrade it and continue using the \"classic\" bot permission. + +## Connecting to the RTM API {#connecting} + +Note that the import here is not `from slack_sdk.rtm import RTMClient` but `from slack_sdk.rtm_v2 import RTMClient` (`_v2` is added in the latter one). + +``` python +import os +from slack_sdk.rtm_v2 import RTMClient + +rtm = RTMClient(token=os.environ["SLACK_BOT_TOKEN"]) + +@rtm.on("message") +def handle(client: RTMClient, event: dict): + if 'Hello' in event['text']: + channel_id = event['channel'] + thread_ts = event['ts'] + user = event['user'] # This is not username but user ID (the format is either U*** or W***) + + client.web_client.chat_postMessage( + channel=channel_id, + text=f"Hi <@{user}>!", + thread_ts=thread_ts + ) + +rtm.start() +``` + +## Connecting to the RTM API (v1 client) {#connecting-v1} + +Below is a code snippet that uses the legacy version of `RTMClient`. For new app development, we **do not recommend** using it as it contains issues that have been resolved in v2. Please refer to the [list of these issues](https://github.com/slackapi/python-slack-sdk/issues?q=is%3Aissue+is%3Aclosed+milestone%3A3.3.0+label%3Artm-client) for more details. + +``` python +import os +from slack_sdk.rtm import RTMClient + +@RTMClient.run_on(event="message") +def say_hello(**payload): + data = payload['data'] + web_client = payload['web_client'] + + if 'Hello' in data['text']: + channel_id = data['channel'] + thread_ts = data['ts'] + user = data['user'] # This is not username but user ID (the format is either U*** or W***) + + web_client.chat_postMessage( + channel=channel_id, + text=f"Hi <@{user}>!", + thread_ts=thread_ts + ) + +slack_token = os.environ["SLACK_BOT_TOKEN"] +rtm_client = RTMClient(token=slack_token) +rtm_client.start() +``` + +## The `rtm.start` vs. `rtm.connect` API methods (v1 client) {#rtm-methods} + +By default, the RTM client uses the [`rtm.connect`](/reference/methods/rtm.connect) API method to establish a WebSocket connection with Slack. The response contains basic information about the team and WebSocket URL. + +See the [`rtm.connect`](/reference/methods/rtm.connect) and [`rtm.start`](/reference/methods/rtm.start) API methods for more details. Note that `slack.rtm_v2.RTMClient` does not support `rtm.start`. + +## RTM events {#rtm-events} + +``` javascript +{ + 'type': 'message', + 'ts': '1358878749.000002', + 'user': 'U023BECGF', + 'text': 'Hello' +} +``` + +Refer to the [Legacy RTM events](/legacy/legacy-rtm-api#events) section for a complete list of events. diff --git a/docs/english/scim.md b/docs/english/scim.md new file mode 100644 index 000000000..78f696056 --- /dev/null +++ b/docs/english/scim.md @@ -0,0 +1,147 @@ +# SCIM API client + +[SCIM](http://www.simplecloud.info/) is supported by a myriad of services. The SCIM API is a set of APIs for provisioning and managing user accounts and groups. SCIM is used by Single Sign-On (SSO) services and identity providers to manage people across a variety of tools, including Slack. + +Refer to [using the Slack SCIM API](/admins/scim-api) for more details. + +View the [Python document for this module](https://docs.slack.dev/tools/python-slack-sdk/reference). + +## SCIMClient {#scimclient} + +An OAuth token with [the admin scope](/reference/scopes/admin) is required to access the SCIM API. To fetch provisioned user data, you can use the `search_users` method in the client. + +``` python +import os +from slack_sdk.scim import SCIMClient + +client = SCIMClient(token=os.environ["SLACK_ORG_ADMIN_USER_TOKEN"]) + +response = client.search_users( + start_index=1, + count=100, + filter="""filter=userName Eq "Carly"""", +) +response.users # List[User] +``` + +Check out [the class source code](https://github.com/slackapi/python-slack-sdk/blob/main/slack_sdk/scim/v1/user.py) to learn more about the structure of the `user` in `response.users`. + +Similarly, the `search_groups` method is available and the shape of the `Group` object can be [found here](https://github.com/slackapi/python-slack-sdk/blob/main/slack_sdk/scim/v1/group.py). + +``` python +response = client.search_groups( + start_index=1, + count=10, +) +response.groups # List[Group] +``` + +For creating, updating, and deleting users or groups: + +``` python +from slack_sdk.scim.v1.user import User, UserName, UserEmail + +# POST /Users +# Creates a user. Must include the user_name argument and at least one email address. +# You may provide an email address as the user_name value, +# but it will be automatically converted to a Slack-appropriate username. +user = User( + user_name="cal", + name=UserName(given_name="C", family_name="Henderson"), + emails=[UserEmail(value="your-unique-name@example.com")], +) +creation_result = client.create_user(user) + +# PATCH /Users/{user_id} +# Updates an existing user resource, overwriting values for specified attributes. +patch_result = client.patch_user( + id=creation_result.user.id, + partial_user=User(user_name="chenderson"), +) + +# PUT /Users/{user_id} +# Updates an existing user resource, overwriting all values for a user +# even if an attribute is empty or not provided. +user_to_update = patch_result.user +user_to_update.name = UserName(given_name="Cal", family_name="Henderson") +update_result = client.update_user(user=user_to_update) + +# DELETE /Users/{user_id} +# Sets a Slack user to deactivated. The value of the {id} +# should be the user's corresponding Slack ID, beginning with either U or W. +delete_result = client.delete_user(user_to_update.id) +``` + +## AsyncSCIMClient {#asyncscimclient} + +If you are keen to use asyncio for SCIM API calls, we offer `AsyncSCIMClient`. This client relies on the aiohttp library. + +``` python +import asyncio +import os +from slack_sdk.scim.async_client import AsyncSCIMClient + +client = AsyncSCIMClient(token=os.environ["SLACK_ORG_ADMIN_USER_TOKEN"]) + +async def main(): + response = await client.search_groups(start_index=1, count=2) + print(response.groups) + +asyncio.run(main()) +``` + +------------------------------------------------------------------------ + +## RetryHandler {#retryhandler} + +With the default settings, only `ConnectionErrorRetryHandler` with its default configuration (=only one retry in the manner of [exponential backoff and jitter](https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/)) is enabled. The retry handler retries if an API client encounters a connectivity-related failure (e.g., connection reset by peer). + +To use other retry handlers, you can pass a list of `RetryHandler` to the client constructor. For instance, you can add the built-in `RateLimitErrorRetryHandler` this way: + +``` python +import os +from slack_sdk.scim import SCIMClient +client = SCIMClient(token=os.environ["SLACK_ORG_ADMIN_USER_TOKEN"]) + +# This handler does retries when HTTP status 429 is returned +from slack_sdk.http_retry.builtin_handlers import RateLimitErrorRetryHandler +rate_limit_handler = RateLimitErrorRetryHandler(max_retry_count=1) + +# Enable rate limited error retries as well +client.retry_handlers.append(rate_limit_handler) +``` + +You can also create one on your own by defining a new class that inherits `slack_sdk.http_retry RetryHandler` (`AsyncRetryHandler` for asyncio apps) and implements required methods (internals of `can_retry` / `prepare_for_next_retry`). Check out the source code for the ones that are built in to learn how to properly implement them. + +``` python +import socket +from typing import Optional +from slack_sdk.http_retry import (RetryHandler, RetryState, HttpRequest, HttpResponse) +from slack_sdk.http_retry.builtin_interval_calculators import BackoffRetryIntervalCalculator +from slack_sdk.http_retry.jitter import RandomJitter + +class MyRetryHandler(RetryHandler): + def _can_retry( + self, + *, + state: RetryState, + request: HttpRequest, + response: Optional[HttpResponse] = None, + error: Optional[Exception] = None + ) -> bool: + # [Errno 104] Connection reset by peer + return error is not None and isinstance(error, socket.error) and error.errno == 104 + +client = SCIMClient( + token=os.environ["SLACK_ORG_ADMIN_USER_TOKEN"], + retry_handlers=[MyRetryHandler( + max_retry_count=1, + interval_calculator=BackoffRetryIntervalCalculator( + backoff_factor=0.5, + jitter=RandomJitter(), + ), + )], +) +``` + +For asyncio apps, `Async` prefixed corresponding modules are available. All the methods in those methods are async/await compatible. Check [the source code](https://github.com/slackapi/python-slack-sdk/blob/main/slack_sdk/http_retry/async_handler.py) for more details. diff --git a/docs/english/socket-mode.md b/docs/english/socket-mode.md new file mode 100644 index 000000000..a1bf9984e --- /dev/null +++ b/docs/english/socket-mode.md @@ -0,0 +1,222 @@ +# Socket Mode client + +Socket Mode is a method of connecting your app to the Slack APIs using WebSockets instead of HTTP. You can use `slack_sdk.socket_mode.SocketModeClient` for managing [Socket Mode](/apis/events-api/using-socket-mode) connections and performing interactions with Slack. + +## Using Socket Mode {#socket-mode} + +Let's start with enabling Socket Mode. Visit [app page](http://api.slack.com/apps), choose the app you're working on, and go to **Settings** on the left pane. There are a few things to do on this page. + +- Go to **Settings** \> **Basic Information**, then add a new **App-Level Token** with the `connections:write` scope. +- Go to **Settings** \> **Socket Mode**, then toggle on **Enable Socket Mode**. +- Go to **Features** \> **App Home**, look under **Show Tabs** \> **Messages Tab**, then toggle on **Allow users to send Slash commands and messages from the messages tab**. +- Go to **Features** \> **Event Subscriptions**, then toggle on **Enable Events**. +- On the same page, expand **Subscribe to bot events**, click **Add Bot User Event**, and select **message.im**. This will allow the bot to get events for messages that are sent in 1:1 direct messages with itself. +- Go to **Features** \> **Interactivity and Shortcuts**, look under *Shortcuts*\*, click **Create a New Shortcut**, then create a new Global shortcut with the following details: + > **Name**: Hello + + > **Short Description**: Receive a Greeting + + > **Callback ID**: hello-shortcut + +- Go to **Features** \> **OAuth & Permissions** under **Scopes** \> **Bot Token Scopes**, click **Add an OAuth Scope**, and select **reactions:write**. This will allow the bot to add emoji reactions (Reacjis) to messages. +- Go to **Features** \> **Oauth & Permissions** under **OAuth Tokens for Your Workspace** and click **Install to Workspace**. + +You will be using the app-level token that starts with `xapp-`. Note that the token here is not one that starts with either `xoxb-` or `xoxp-`. + +``` python +import os +from slack_sdk.web import WebClient +from slack_sdk.socket_mode import SocketModeClient + +# Initialize SocketModeClient with an app-level token + WebClient +client = SocketModeClient( + # This app-level token will be used only for establishing a connection + app_token=os.environ.get("SLACK_APP_TOKEN"), # xapp-A111-222-xyz + # You will be using this WebClient for performing Web API calls in listeners + web_client=WebClient(token=os.environ.get("SLACK_BOT_TOKEN")) # xoxb-111-222-xyz +) + +from slack_sdk.socket_mode.response import SocketModeResponse +from slack_sdk.socket_mode.request import SocketModeRequest + +def process(client: SocketModeClient, req: SocketModeRequest): + if req.type == "events_api": + # Acknowledge the request anyway + response = SocketModeResponse(envelope_id=req.envelope_id) + client.send_socket_mode_response(response) + + # Add a reaction to the message if it's a new message + if req.payload["event"]["type"] == "message" \ + and req.payload["event"].get("subtype") is None: + client.web_client.reactions_add( + name="eyes", + channel=req.payload["event"]["channel"], + timestamp=req.payload["event"]["ts"], + ) + if req.type == "interactive" \ + and req.payload.get("type") == "shortcut": + if req.payload["callback_id"] == "hello-shortcut": + # Acknowledge the request + response = SocketModeResponse(envelope_id=req.envelope_id) + client.send_socket_mode_response(response) + # Open a welcome modal + client.web_client.views_open( + trigger_id=req.payload["trigger_id"], + view={ + "type": "modal", + "callback_id": "hello-modal", + "title": { + "type": "plain_text", + "text": "Greetings!" + }, + "submit": { + "type": "plain_text", + "text": "Good Bye" + }, + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "Hello!" + } + } + ] + } + ) + + if req.type == "interactive" \ + and req.payload.get("type") == "view_submission": + if req.payload["view"]["callback_id"] == "hello-modal": + # Acknowledge the request and close the modal + response = SocketModeResponse(envelope_id=req.envelope_id) + client.send_socket_mode_response(response) + +# Add a new listener to receive messages from Slack +# You can add more listeners like this +client.socket_mode_request_listeners.append(process) +# Establish a WebSocket connection to the Socket Mode servers +client.connect() +# Just not to stop this process +from threading import Event +Event().wait() +``` + +--- + +## Supported libraries {#supported-libraries} + +This SDK offers its own WebSocket client covering only required features for Socket Mode. In addition, `SocketModeClient` is implemented with a few 3rd party open source libraries. If you prefer any of the following, you can use it over the built-in one. + +|PyPI Project | SocketModeClient +|--------------|------------------ +| [`slack_sdk`](https://pypi.org/project/slack-sdk/) | [`slack_sdk.socket_mode.SocketModeClient`](https://github.com/slackapi/python-slack-sdk/blob/main/slack_sdk/socket_mode/builtin) +| [`websocket_client`](https://pypi.org/project/websocket_client/) | [`slack_sdk.socket_mode.websocket_client.SocketModeClient`](https://github.com/slackapi/python-slack-sdk/blob/main/slack_sdk/socket_mode/websocket_client) +| [`aiohttp`](https://pypi.org/project/aiohttp/) (asyncio-based) | [`slack_sdk.socket_mode.aiohttp.SocketModeClient`](https://github.com/slackapi/python-slack-sdk/blob/main/slack_sdk/socket_mode/aiohttp) +| [`websockets`](https://pypi.org/project/websockets/) (asyncio-based) | [`slack_sdk.socket_mode.websockets.SocketModeClient`](https://github.com/slackapi/python-slack-sdk/blob/main/slack_sdk/socket_mode/websockets) + +To use the [`websocket_client`](https://pypi.org/project/websocket_client/) based-one, add the[`websocket_client`](https://pypi.org/project/websocket_client/) dependency and change the import as below. + +``` python +# Note that the pockage is different +from slack_sdk.socket_mode.websocket_client import SocketModeClient + +client = SocketModeClient( + app_token=os.environ.get("SLACK_APP_TOKEN"), # xapp-A111-222-xyz + web_client=WebClient(token=os.environ.get("SLACK_BOT_TOKEN")) # xoxb-111-222-xyz +) +``` + +You can pass a few additional arguments that are specific to the library. Apart from that, all the functionalities work in the same way as the built-in version. + +--- + +## Asyncio-based libraries {#asyncio-libraries} + +To use the asyncio-based ones such as aiohttp, your app needs to be compatible with asyncio's async/await programming model. The `SocketModeClient` only works with `AsyncWebClient` and async listeners. + +``` python +import asyncio +import os +from slack_sdk.web.async_client import AsyncWebClient +from slack_sdk.socket_mode.aiohttp import SocketModeClient + +# Use async method +async def main(): + from slack_sdk.socket_mode.response import SocketModeResponse + from slack_sdk.socket_mode.request import SocketModeRequest + + # Initialize SocketModeClient with an app-level token + AsyncWebClient + client = SocketModeClient( + # This app-level token will be used only for establishing a connection + app_token=os.environ.get("SLACK_APP_TOKEN"), # xapp-A111-222-xyz + # You will be using this AsyncWebClient for performing Web API calls in listeners + web_client=AsyncWebClient(token=os.environ.get("SLACK_BOT_TOKEN")) # xoxb-111-222-xyz + ) + + # Use async method + async def process(client: SocketModeClient, req: SocketModeRequest): + if req.type == "events_api": + # Acknowledge the request anyway + response = SocketModeResponse(envelope_id=req.envelope_id) + # Don't forget having await for method calls + await client.send_socket_mode_response(response) + + # Add a reaction to the message if it's a new message + if req.payload["event"]["type"] == "message" \ + and req.payload["event"].get("subtype") is None: + await client.web_client.reactions_add( + name="eyes", + channel=req.payload["event"]["channel"], + timestamp=req.payload["event"]["ts"], + ) + if req.type == "interactive" \ + and req.payload.get("type") == "shortcut": + if req.payload["callback_id"] == "hello-shortcut": + # Acknowledge the request + response = SocketModeResponse(envelope_id=req.envelope_id) + await client.send_socket_mode_response(response) + # Open a welcome modal + await client.web_client.views_open( + trigger_id=req.payload["trigger_id"], + view={ + "type": "modal", + "callback_id": "hello-modal", + "title": { + "type": "plain_text", + "text": "Greetings!" + }, + "submit": { + "type": "plain_text", + "text": "Good Bye" + }, + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "Hello!" + } + } + ] + } + ) + + if req.type == "interactive" \ + and req.payload.get("type") == "view_submission": + if req.payload["view"]["callback_id"] == "hello-modal": + # Acknowledge the request and close the modal + response = SocketModeResponse(envelope_id=req.envelope_id) + await client.send_socket_mode_response(response) + + # Add a new listener to receive messages from Slack + # You can add more listeners like this + client.socket_mode_request_listeners.append(process) + # Establish a WebSocket connection to the Socket Mode servers + await client.connect() + # Just not to stop this process + await asyncio.sleep(float("inf")) + +# You can go with other way to run it. This is just for easiness to try it out. +asyncio.run(main()) +``` diff --git a/docs/english/tutorial/understanding-oauth-approve.png b/docs/english/tutorial/understanding-oauth-approve.png new file mode 100644 index 000000000..8f0263e03 Binary files /dev/null and b/docs/english/tutorial/understanding-oauth-approve.png differ diff --git a/docs/english/tutorial/understanding-oauth-flow.png b/docs/english/tutorial/understanding-oauth-flow.png new file mode 100644 index 000000000..15ef9de6d Binary files /dev/null and b/docs/english/tutorial/understanding-oauth-flow.png differ diff --git a/docs/english/tutorial/understanding-oauth-scopes.md b/docs/english/tutorial/understanding-oauth-scopes.md new file mode 100644 index 000000000..fc1c96f9f --- /dev/null +++ b/docs/english/tutorial/understanding-oauth-scopes.md @@ -0,0 +1,163 @@ +# Understanding OAuth scopes for bots + +In this tutorial, we'll: + +* explore Slack app permissions and distribution using OAuth, and along the way, learn how to identify which scopes apps need and how to use OAuth to request them. +* build an app that sends a direct message to users joining a specific channel. Once installed in a workspace, it will create a new channel named **#the-welcome-channel** if it doesnโ€™t already exist. The channel will be used to thank users for joining the channel. We'll also share code snippets from the app, but the full source code is available on [GitHub](https://github.com/stevengill/slack-python-oauth-example). The code and implementation of OAuth is general enough that you should be able to follow along, even if Python isn't your preferred language. + +## Prerequisites {#prerequisites} + +Before we get started, ensure you have a development workspace with permissions to install apps. If you donโ€™t have one set up, go ahead and [create one](https://slack.com/create). You also need to [create a new app](https://api.slack.com/apps/new) if you havenโ€™t already. + +Letโ€™s get started! + +## Determining scopes {#determine-scopes} + +Scopes are used to grant your app permission to perform functions in Slack, such as calling Web API methods and receiving Events API events. As a user goes through your app's installation flow, they'll need to permit access to the scopes your app is requesting. + +To determine which scopes we need, we should take a closer look at what our app does. Instead of scouring the entire list of scopes that might make sense, we can look at what events or methods we need for the app, and build out our scope list as we go. + +1. After installation, our app checks to see if a channel exists (private or public since we canโ€™t create a new channel with the same name). A quick search through the list of methods leads us to the `conversations.list` API method, which we can use to get the names of public & private channels. It also shows us what scopes are needed to use this method. In our case, we need `channels:read` and `groups:read`. (We donโ€™t need `im:read` or `mpim:read`, as we arenโ€™t concerned about the names of direct messages.) + + ``` + import os + from slack_sdk import WebClient + + # verifies if "the-welcome-channel" already exists + def channel_exists(): + token = os.environ["SLACK_BOT_TOKEN"] + client = WebClient(token=token) + + # grab a list of all the channels in a workspace + clist = client.conversations_list() + exists = False + for k in clist["channels"]: + # look for the channel in the list of existing channels + if k['name'] == 'the-welcome-channel': + exists = True + break + if exists == False: + # create the channel since it doesn't exist + create_channel() + ``` + +2. If the channel doesnโ€™t already exist, we need to create it. Looking through the list of API methods leads us to the `conversations.create` API method, which needs the scope `channels:manage`. + + ``` + # creates a channel named "the-welcome-channel" + def create_channel(): + token = os.environ["SLACK_BOT_TOKEN"] + client = WebClient(token=token) + resp = client.conversations_create(name="the-welcome-channel") + ``` + +3. When a user joins our newly created channel, our app sends them a direct message. To see when a user joins our channel, we need to listen for an event. Looking at our list of events, we see that `member_joined_channel` is the event that we need (_Note: events need to be added to your appโ€™s configuration_). The scopes required for this event are `channels:read` and `groups:read` (same ones from step one). Now to send a direct message, we need to use the `chat.postMessage` API method, which requires the `chat:write` scope. + + ``` + # Create an event listener for "member_joined_channel" events + # Sends a DM to the user who joined the channel + @slack_events_adapter.on("member_joined_channel") + def member_joined_channel(event_data): + user = event_data['event']['user'] + token = os.environ["SLACK_BOT_TOKEN"] + client = WebClient(token=token) + msg = 'Welcome! Thanks for joining the-welcome-channel' + client.chat_postMessage(channel=user, text=msg) + ``` + +Our final list of scopes required are: +* `channels:read` +* `groups:read` +* `channels:manage` +* `chat:write` + +## Setting up OAuth and requesting scopes {#setup} + +If you want users to be able to install your app on additional workspaces or from the [Slack Marketplace](/slack-marketplace/slack-marketplace-review-guide), you'll need to implement an OAuth flow. + +We'll be following the general flow of OAuth with Slack, which is covered in the [installing with OAuth](/authentication/installing-with-oauth) guide and nicely illustrated in the image below: + +![OAuth flow](understanding-oauth-flow.png) + +1. **Requesting Scopes** + + This first step is sometimes also referred to as "redirect to Slack" or "Add to Slack button". In this step, we redirect to Slack and pass along our list of required scopes, client ID, and state as query parameters in the URL. You can get the client ID from the **Basic Information** section of your app. State is an optional value, but is recommended to prevent [CSRF attacks](https://en.wikipedia.org/wiki/Cross-site_request_forgery). + + ``` + https://slack.com/oauth/v2/authorize?scope=channels:read,groups:read,channels:manage,chat:write&client_id=YOUR_CLIENT_ID&state=STATE_STRING + ``` + + _It is also possible to pass in a `redirect\_uri` into your URL. A `redirect\_uri` is used for Slack to know where to send the request after the user has granted permission to your app. In our example, instead of passing one in the URL, we request that you add a Redirect URL in your appโ€™s configuration on [api.slack.com/apps](https://api.slack.com/apps) under the **OAuth and Permissions** section._ + + Next, we'll create a route in our app that contains an **Add to Slack** button using that URL above. + + ``` + # Grab client ID from your environment variables + client_id = os.environ["SLACK_CLIENT_ID"] + # Generate random string to use as state to prevent CSRF attacks + from uuid import uuid4 + state = str(uuid4()) + + # Route to kick off Oauth flow + @app.route("/begin_auth", methods=["GET"]) + def pre_install(): + return f'' + ``` + + Now when a user navigates to the route, they should see the **Add to Slack** button. + + Clicking the button will trigger the next step. + +2. **Waiting for user approval** + + The user will see the app installation UI (shown below) and will have the option to accept the permissions and allow the app to install to the workspace: + + ![Approve installation](understanding-oauth-approve.png) + +3. **Exchanging a temporary authorization code for an access token** + + After the user approves the app, Slack will redirect the user to your specified Redirect URL. As we mentioned earlier, we did not include a `redirect_uri` in our **Add to Slack** button, so our app will use our Redirect URL specified on the appโ€™s **OAuth and Permissions** page. + + Our Redirect URL function will have to parse the HTTP request for the `code` and `state` query parameters. We need to check that the `state` parameter was created by our app. If it is, we can now exchange the `code` for an access token. To do this, we need to call the `oauth.v2.access` API method with the `code`, `client_id`, and `client_secret`. This method will return the access token, which we can now save (preferably in a persistent database) and use for any of the Slack API method calls we make. (_Note: use this access token for all of the Slack API method calls we covered in the scopes section above_) + + ``` + # Grab client Secret from your environment variables + client_secret = os.environ["SLACK_CLIENT_SECRET"] + + # Route for Oauth flow to redirect to after user accepts scopes + @app.route("/finish_auth", methods=["GET", "POST"]) + def post_install(): + # Retrieve the auth code and state from the request params + auth_code = request.args['code'] + received_state = request.args['state'] + + # This request doesn't need a token so an empty string will suffice + client = WebClient(token="") + + # verify state received in params matches state we originally sent in auth request + if received_state == state: + # Request the auth tokens from Slack + response = client.oauth_v2_access( + client_id=client_id, + client_secret=client_secret, + code=auth_code + ) + else: + return "Invalid State" + + # Save the bot token to an environmental variable or to your data store + os.environ["SLACK_BOT_TOKEN"] = response['access_token'] + + # See if "the-welcome-channel" exists. Create it if it doesn't. + channel_exists() + + # Don't forget to let the user know that auth has succeeded! + return "Auth complete!" + ``` + +## Next steps {#next} + +At this point, you should feel more comfortable learning what scopes your app needs and using OAuth to request those scopes. A few resources you can check out next include: + +* [Slack-Python-OAuth-Example](https://github.com/stevengill/slack-python-oauth-example): we used code snippets from this app in this tutorial. The README contains more detailed information about running the app locally using ngrok, setting up a Redirect URL for OAuth, and setting up a request URL for events. +* Learn more about [installing with OAuth](/authentication/installing-with-oauth). diff --git a/docs/english/tutorial/upload-files-allow.png b/docs/english/tutorial/upload-files-allow.png new file mode 100644 index 000000000..bc7e533ad Binary files /dev/null and b/docs/english/tutorial/upload-files-allow.png differ diff --git a/docs/english/tutorial/upload-files-bot-token.png b/docs/english/tutorial/upload-files-bot-token.png new file mode 100644 index 000000000..5c29732c8 Binary files /dev/null and b/docs/english/tutorial/upload-files-bot-token.png differ diff --git a/docs/english/tutorial/upload-files-delete.png b/docs/english/tutorial/upload-files-delete.png new file mode 100644 index 000000000..cf86026f8 Binary files /dev/null and b/docs/english/tutorial/upload-files-delete.png differ diff --git a/docs/english/tutorial/upload-files-first-upload.png b/docs/english/tutorial/upload-files-first-upload.png new file mode 100644 index 000000000..a323b7a31 Binary files /dev/null and b/docs/english/tutorial/upload-files-first-upload.png differ diff --git a/docs/english/tutorial/upload-files-install.png b/docs/english/tutorial/upload-files-install.png new file mode 100644 index 000000000..85de64ff6 Binary files /dev/null and b/docs/english/tutorial/upload-files-install.png differ diff --git a/docs/english/tutorial/upload-files-invite-bot.gif b/docs/english/tutorial/upload-files-invite-bot.gif new file mode 100644 index 000000000..c34c9165c Binary files /dev/null and b/docs/english/tutorial/upload-files-invite-bot.gif differ diff --git a/docs/english/tutorial/upload-files-local-file.png b/docs/english/tutorial/upload-files-local-file.png new file mode 100644 index 000000000..3eec0f0c9 Binary files /dev/null and b/docs/english/tutorial/upload-files-local-file.png differ diff --git a/docs/english/tutorial/upload-files-with-channel.png b/docs/english/tutorial/upload-files-with-channel.png new file mode 100644 index 000000000..dbb9b3a00 Binary files /dev/null and b/docs/english/tutorial/upload-files-with-channel.png differ diff --git a/docs/english/tutorial/uploading-files.md b/docs/english/tutorial/uploading-files.md new file mode 100644 index 000000000..a9a624758 --- /dev/null +++ b/docs/english/tutorial/uploading-files.md @@ -0,0 +1,263 @@ +# Uploading files with Python + +This tutorial details how to use the [`slack-sdk` package for Python](https://pypi.org/project/slack-sdk/) to upload files to a channel in Slack with some code samples. In addition to looking at how to upload files, we'll also cover listing and deleting files via the Web API using the Python SDK. + +## Creating an app {#create-app} + +First, create a [Slack app](https://api.slack.com/apps/new). + +## Configuring your app's settings with an app manifest {#configuration} + +Creating your app using this method will include all the required settings for this tutorial, and you won't be bogged down with too many details - all you'll need to do is decide where this app will live. If you're curious about the inner workings of how this button works, refer to [App Manifests](/app-manifests) for more information. + +```yaml +_metadata: + major_version: 1 + minor_version: 1 +display_information: + name: File Writer App +features: + bot_user: + display_name: File Writer Bot +oauth_config: + scopes: + bot: + # Used to send messages to a channel + - chat:write + # This scope is not required if your app will just upload files. We've included it in order to use the `files.info` `files.list` methods. + - files:read + # Used to upload files to Slack + - files:write +``` + +## Installing your app in a workspace {#installing} + +Once you've created your app, you'll see an **Install to Workspace** button. Click it to install your app in your workspace. + +![Install to workspace](upload-files-install.png) + +Next, click **Allow** to authorize the app in your workspace. + +![Authorize app](upload-files-allow.png) + +Navigate to the **Install App** section under **Settings**. Here, you'll find your `Bot User OAuth Token`. + +![Get token](upload-files-bot-token.png) + +Set this token value as an environment variable called `SLACK_BOT_TOKEN` by using the following command: + +```bash +export SLACK_BOT_TOKEN= +``` + +With this, all your Slack app configuration is done. Let's start coding. + +## Using Python to upload a file {#upload-file-with-python} + +### Creating a new project {#create-new-project} + +First, ensure you're using Python version 3.7 or above. While the current standard is for the `python3` and `pip3` commands to use Python 3.7 or above, it's best to ensure your runtime is always using the latest version of Python. [pyenv](https://github.com/pyenv/pyenv) is a handy tool that can do this for you. + +We'll create a brand new virtual environment and install the required library dependencies using the following commands. + +```bash +echo 'slack-sdk>=3.12,<4' > requirements.txt +python3 -m venv .venv +source .venv/bin/activate +pip install -U pip +pip install -r requirements.txt +``` + +### Uploading a file {#upload-file} + +While it's possible to enter the following into the Python shell, we've gathered some code samples and wrote it in script form. + +For each of the code samples, make sure to add in the following to the top of your Python file if you're going to run it as a script - the examples won't run without it. + +```python +import logging, os + +# Sets the debug level. +# If you're using this in production, you can change this back to INFO and add extra log entries as needed. +logging.basicConfig(level=logging.DEBUG) + +# Initialize the Web API client. +# This expects that you've already set your SLACK_BOT_TOKEN as an environment variable. +# Try to resist the urge to put your token directly in your code; it is best practice not to. +from slack_sdk import WebClient +client = WebClient(os.environ["SLACK_BOT_TOKEN"]) +``` + +Let's make sure our token is correctly configured. + +```python +# Tests to see if the token is valid +auth_test = client.auth_test() +bot_user_id = auth_test["user_id"] +print(f"App's bot user: {bot_user_id}") +``` + +Once you run this code, you'll see something similar to the following within the logs: + +```bash +>>> auth_test = client.auth_test() +DEBUG:slack_sdk.web.base_client:Sending a request - url: https://www.slack.com/api/auth.test, query_params: {}, body_params: {}, files: {}, json_body: None, headers: {} +DEBUG:slack_sdk.web.base_client:Received the following response - status: 200, headers: {}, body: {"ok":true,"url":"https:\/\/example.slack.com\/","team":"Acme Corp","user":"file_writer_bot","team_id":"T1234567890","user_id":"U02PY3HA48G","bot_id":"B02P8CPE143","is_enterprise_install":false} + +>>> bot_user_id = auth_test["user_id"] +>>> print(f"App's bot user: {bot_user_id}") +App's bot user: U02PY3HA48G +``` + +Notice that your bot user's `user_id` can be found within these logs. Any files uploaded using a bot token will be associated with the bot user. + +At this point, while no files have been uploaded yet, you can call the `files.list` API method to confirm this fact. We'll do this again after we've uploaded some files to see what has changed. + +```python +>>> files = client.files_list(user=bot_user_id) +DEBUG:slack_sdk.web.base_client:Sending a request - url: https://www.slack.com/api/files.list, query_params: {}, body_params: {'user': 'U02PY3HA48G'}, files: {}, json_body: None, headers: {} +DEBUG:slack_sdk.web.base_client:Received the following response - status: 200, headers: {}, body: {"ok":true,"files":[],"paging":{"count":100,"total":0,"page":1,"pages":0}} +``` + +Let's try uploading a file using text supplied to the `content` parameter. This will upload a text file with the specified `content`. + +```python +new_file = client.files_upload_v2( + title="My Test Text File", + filename="test.txt", + content="Hi there! This is a text file!", +) +``` + +Doing this within the Python shell will display the following: + +```python +>>> new_file = client.files_upload_v2( +... title="My Test Text File", +... filename="test.txt", +... content="Hi there! This is a text file!", +... ) +DEBUG:slack_sdk.web.base_client:Sending a request - url: https://www.slack.com/api/files.getUploadURLExternal, query_params: {}, body_params: {'filename': 'test.txt', 'length': 30}, files: {}, json_body: None, headers: {'Content-Type': 'application/x-www-form-urlencoded', 'Authorization': '(redacted)', 'User-Agent': 'Python/3.11.6 slackclient/3.27.1 Darwin/23.3.0'} +DEBUG:slack_sdk.web.base_client:Received the following response - status: 200, headers: {'date': 'Fri, 08 Mar 2024 08:27:40 GMT', 'server': 'Apache', 'vary': 'Accept-Encoding', 'x-slack-req-id': '9rj4io2i10iawjdasdfas', 'x-content-type-options': 'nosniff', 'x-xss-protection': '0', 'pragma': 'no-cache', 'cache-control': 'private, no-cache, no-store, must-revalidate', 'expires': 'Sat, 26 Jul 1997 05:00:00 GMT', 'content-type': 'application/json; charset=utf-8', 'x-accepted-oauth-scopes': 'files:write', 'x-oauth-scopes': 'chat:write,files:read,files:write', 'access-control-expose-headers': 'x-slack-req-id, retry-after', 'access-control-allow-headers': 'slack-route, x-slack-version-ts, x-b3-traceid, x-b3-spanid, x-b3-parentspanid, x-b3-sampled, x-b3-flags', 'strict-transport-security': 'max-age=31536000; includeSubDomains; preload', 'referrer-policy': 'no-referrer', 'x-slack-unique-id': 'ZerL-asd3k201a0sdfa', 'x-slack-backend': 'r', 'access-control-allow-origin': '*', 'content-length': '257', 'via': '1.1 slack-prod.tinyspeck.com, envoy-www-iad-upnvxyvr, envoy-edge-nrt-ixozsome', 'x-envoy-attempt-count': '1', 'x-envoy-upstream-service-time': '195', 'x-backend': 'main_normal main_canary_with_overflow main_control_with_overflow', 'x-server': 'slack-www-hhvm-main-iad-bgpy', 'x-slack-shared-secret-outcome': 'no-match', 'x-edge-backend': 'envoy-www', 'x-slack-edge-shared-secret-outcome': 'no-match', 'connection': 'close'}, body: {"ok":true,"upload_url":"https:\/\/files.slack.com\/upload\/v1\/CwABAASWWgoAAZOR9CgFYdQZCgACF7q8rQ4fIhASAAAVDFERDNKSDNLCwACAAAAC1UwNk5GNjdGNUxNCwADAAAAC0YwNk40VDdGWk5LAAoABAAAAAAAAAAeAAsAAgAAABRmH2dkKc07lhAASAWWpZAA","file_id":"F2910294A"} +DEBUG:slack_sdk.web.base_client:('Received the following response - ', "status: 200, headers: {'Content-Type': 'text/plain; charset=utf-8', 'Content-Length': '7', 'Connection': 'close', 'x-backend': 'miata-prod-nrt-v2-5976497578-js557', 'date': 'Fri, 08 Mar 2024 08:27:40 GMT', 'x-envoy-upstream-service-time': '401', 'x-edge-backend': 'miata', 'x-slack-edge-shared-secret-outcome': 'shared-secret', 'server': 'envoy', 'via': 'envoy-edge-nwt-aaoskwwo, 1.1 f752a4d41a2512ine9asfa.cloudfront.net (CloudFront)', 'X-Cache': 'Miss from cloudfront', 'X-Amz-Cf-Pop': 'NRT51-C4', 'X-Amz-Cf-Id': 'jxcP2ao0fs4KXanisi9aiswiaKBiJQ==', 'Cross-Origin-Resource-Policy': 'cross-origin'}, body: OK - 30") +DEBUG:slack_sdk.web.base_client:Sending a request - url: https://www.slack.com/api/files.completeUploadExternal, query_params: {}, body_params: {'files': '[{"id": "F2910294A", "title": "My Test Text File"}]'}, files: {}, json_body: None, headers: {'Content-Type': 'application/x-www-form-urlencoded', 'Authorization': '(redacted)', 'User-Agent': 'Python/3.11.6 slackclient/3.27.1 Darwin/23.3.0'} +DEBUG:slack_sdk.web.base_client:Received the following response - status: 200, headers: {'date': 'Fri, 08 Mar 2024 08:27:41 GMT', ... body: {"ok":true,"files":[{"id":"F2910294A","created":1709886459,"timestamp":1709886459,"name":"test.txt","title":"My Test Text File","mimetype":"text\/plain","filetype":"text","pretty_type":"Plain Text","user":"U10AWOAW","user_team":"T12391022","editable":true,"size":30,"mode":"snippet","is_external":false,"external_type":"","is_public":false,"public_url_shared":false,"display_as_bot":false,"username":"","url_private":"https:\/\/files.slack.com\/files-pri\/T12391022-F2910294A\/test.txt","url_private_download":"https:\/\/files.slack.com\/files-pri\/T12391022-F2910294A\/download\/test.txt","permalink":"https:\/\/platform-ce.slack.com\/files\/U10AWOAW\/F2910294A\/test.txt","permalink_public":"https:\/\/slack-files.com\/T12391022-F2910294A-100e14d15f","edit_link":"https:\/\/platform-ce.slack.com\/files\/U10AWOAW\/F2910294A\/test.txt\/edit","preview":"Hi there! This is a text file!","preview_highlight":"
\n
\n
Hi there! This is a text file!<\/pre><\/div>\n<\/div>\n<\/div>\n","lines":1,"lines_more":0,"preview_is_truncated":false,"comments_count":0,"is_starred":false,"shares":{},"channels":[],"groups":[],"ims":[],"has_more_shares":false,"has_rich_preview":false,"file_access":"visible"}]}
+```
+
+We can confirm that a file has been uploaded using the `files.list` API method mentioned earlier. Wait a moment before calling this method, as there may be a bit of a lag before files are reflected within the results.
+
+```python
+>>> files = client.files_list(user=bot_user_id)
+DEBUG:slack_sdk.web.base_client:Sending a request - url: https://www.slack.com/api/files.list, query_params: {}, body_params: {'user': 'U02PY3HA48G'}, files: {}, json_body: None, headers: {}
+DEBUG:slack_sdk.web.base_client:Received the following response - status: 200, headers: {}, body: {"ok":true,"files":[{"id":"F02P5J88137","created":1638414790,"timestamp":1638414790,"name":"test.txt","title":"My Test Text File","mimetype":"text\/plain","filetype":"text","pretty_type":"Plain Text","user":"U02PY3HA48G","editable":true,"size":30,"mode":"snippet","is_external":false,"external_type":"","is_public":false,"public_url_shared":false,"display_as_bot":false,"username":"","url_private":"https:\/\/files.slack.com\/files-pri\/T03E94MJU-F02P5J88137\/test.txt","url_private_download":"https:\/\/files.slack.com\/files-pri\/T03E94MJU-F02P5J88137\/download\/test.txt","permalink":"https:\/\/seratch.slack.com\/files\/U02PY3HA48G\/F02P5J88137\/test.txt","permalink_public":"https:\/\/slack-files.com\/T03E94MJU-F02P5J88137-e3fda671e9","edit_link":"https:\/\/seratch.slack.com\/files\/U02PY3HA48G\/F02P5J88137\/test.txt\/edit","preview":"Hi there! This is a text file!","preview_highlight":"
\n
\n
Hi there! This is a text file!<\/pre><\/div>\n<\/div>\n<\/div>\n","lines":1,"lines_more":0,"preview_is_truncated":false,"channels":[],"groups":[],"ims":[],"comments_count":0}],"paging":{"count":100,"total":1,"page":1,"pages":1}}
+```
+
+Back in Slack, you'll notice that nothing has happened. How curious...
+
+### Sharing a file within a channel {#sharing}
+
+At this point, we have indeed uploaded a file to Slack, but only the bot user is able to view it.
+
+Let's share this file with other users within our workspace. To do so, we'll use the `chat.postMessage` API method to post a message.
+
+In this example, we've used the channel name `C123456789`, but you'll need to find the ID of the channel that you want to share your file to. When you're in your channel of choice, you can find the channel ID by clicking on the channel name at the top and then scrolling to the bottom of the screen that shows up.
+
+Just like in the image below, mention the File Writer Bot and invite it to the `#random` channel.
+
+![Invite to channel](upload-files-invite-bot.gif)
+
+Next, use the following code to retrieve the file's permalink and post it within a channel.
+
+```python
+file_url = new_file.get("file").get("permalink")
+new_message = client.chat_postMessage(
+    channel="C123456789",
+    text=f"Here is the file: {file_url}",
+)
+```
+
+By doing this, you'll be able to see the file within Slack.
+
+![Upload file](upload-files-first-upload.png)
+
+### Specifying a channel when uploading a file {#specifying-channel}
+
+While this exercise was very informative, having to do these two steps every time we upload a file is a bit cumbersome. Instead, we can specify the `channel` parameter to the function. This is the more common way of uploading a file from an app.
+
+```python
+upload_and_then_share_file = client.files_upload_v2(
+    channel="C123456789",
+    title="Test text data",
+    filename="test.txt",
+    content="Hi there! This is a text file!",
+    initial_comment="Here is the file:",
+)
+```
+
+By running the above code, you'll share the same file without having to send a message with the file URL.
+
+![Share file with message](upload-files-with-channel.png)
+
+### Uploading local files {#upload-local-files}
+
+If you have an image file lying around, feel free to use that but for simplicity's sake. We'll continue using a text file here. You can create a local file by using the following command:
+
+```bash
+echo 'Hi there! This is a text file!' > test.txt
+```
+
+Next, within the same directory, execute the following code. We'll specify the file path as the `file` parameter.
+
+```python
+upload_text_file = client.files_upload_v2(
+    channel="C123456789",
+    title="Test text data",
+    file="./test.txt",
+    initial_comment="Here is the file:",
+)
+```
+
+Again, we'll see that the file has been uploaded to Slack and shared within the `#random` channel.
+
+![File uploaded](upload-files-local-file.png)
+
+## Deleting a file {#deleting}
+
+Next, we'll cover how to delete a file.
+
+We've just uploaded 3 different files above (even though they may look the same). We can confirm that again using the `files.list` method.
+
+```python
+file_ids = []
+# The Python SDK will automatically paginate for you within a for-loop.
+for page in client.files_list(user=bot_user_id):
+    for file in page.get("files", []):
+        file_ids.append(file["id"])
+
+print(file_ids)
+```
+
+```python
+>>> file_ids
+['F02P5J88137', 'F02P8H5BN9G', 'F02P5K7T8SZ']
+```
+
+Let's remove these files from our Slack workspace.
+
+```python
+for page in client.files_list(user=bot_user_id):
+    for file in page.get("files", []):
+        client.files_delete(file=file["id"])
+```
+
+Once we run this, the `files` array should be empty. The count for files found within the `paging` object may take a moment to reflect the actual number of files. You'll also notice within Slack that there are several `This file was deleted.` messages being shown.
+
+![Delete a file](upload-files-delete.png)
+
+## Next steps {#next}
+
+This tutorial summarized how to use the Slack API to upload files and share them within a channel, using the Python SDK. The same principles apply to other languages as well, so if Python isn't your fancy, feel free to try out our other SDKs:
+
+* [Java Slack SDK](/tools/java-slack-sdk/)
+* [Node Slack SDK](/tools/node-slack-sdk/)
+* [Deno Slack SDK](/tools/deno-slack-sdk/)
diff --git a/docs/english/v3-migration.md b/docs/english/v3-migration.md
new file mode 100644
index 000000000..d599ac28a
--- /dev/null
+++ b/docs/english/v3-migration.md
@@ -0,0 +1,224 @@
+---
+sidebar_label: Migrating from slackclient
+---
+
+# Migrating from v2.x to v3.x {#migrating}
+
+You may still view the legacy `slackclient` v2 [documentation](/tools/python-slack-sdk/legacy/). However, the **slackclient** project is in maintenance mode and this **slack_sdk** project is the successor.
+
+## From `slackclient` 2.x {#fromv2}
+
+There are a few changes introduced in v3.0:
+
+-   The PyPI project has been renamed from `slackclient` to `slack_sdk`.
+-   Importing `slack_sdk.*` is recommended. You can still use `slack.*` with deprecation warnings.
+-   `slack_sdk` has no required dependencies. This means `aiohttp` is no longer automatically resolved.
+-   `WebClient` no longer has `run_async` and `aiohttp` specific options. If you still need the option or other `aiohttp` specific options, use `LegacyWebClient` (`slackclient` v2 compatible) or `AsyncWebClient`.
+
+We're sorry for the inconvenience.
+
+------------------------------------------------------------------------
+
+**Change:** The PyPI project has been renamed from `slackclient` to `slack_sdk`.
+
+**Action**: Remove `slackclient`, add `slack_sdk` in `requirements.txt`.
+
+Since v3, the PyPI project name is [slack_sdk](https://pypi.org/project/slack_sdk/) (technically `slack-sdk` also works).
+
+The biggest reason for the renaming is the feature coverage in v3 and newer. The SDK v3 provides not only API clients, but also other modules. As the first step, it starts supporting OAuth flow out-of-the-box. The secondary reason is to make the names more consistent. The renaming addresses the long-lived confusion between the PyPI project and package names.
+
+------------------------------------------------------------------------
+
+**Change:** Importing `slack_sdk.*` is recommended. You can still use `slack.*` with deprecation warnings.
+
+**Action**: Replace `from slack import`, `import slack`, etc. in your source code.
+
+Most imports can be simply replaced by `find your_app -name '*.py' | xargs sed -i '' 's/from slack /from slack_sdk /g'` or similar. If you use `slack.web.classes.*`, the conversion is not so simple that we recommend manually replacing imports for those.
+
+That said, all existing code can be migrated to v3 without any code changes. If you don't have time for it, you can use the `slack` package with deprecation warnings saying `UserWarning: slack package is deprecated. Please use slack_sdk.web/webhook/rtm package instead. For more info, go to https://tools slack.dev/python-slack-sdk/v3-migration/` for a while.
+
+------------------------------------------------------------------------
+
+**Change:** `slack_sdk` has no required dependencies. This means `aiohttp` is no longer automatically resolved.
+
+**Action**: Add `aiohttp` to `requirements.txt` if you use any of `AsyncWebClient`, `AsyncWebhookClient`, and `LegacyWebClient`.
+
+If you use some modules that require `aiohttp`, your `requirements.txt` needs to explicitly list `aiohttp`. The `slack_sdk` dependency doesn't resolve it for you, unlike `slackclient` v2.
+
+------------------------------------------------------------------------
+
+**Change:** `WebClient` no longer has `run_async` and `aiohttp` specific options.
+
+**Action:** If you still need the option or other `aiohttp` specific options, use `LegacyWebClient` (`slackclient` v2 compatible) or `AsyncWebClient`.
+
+The new `slack_sdk.web.WebClient` doesn't rely on `aiohttp` internally at all. The class provides only the synchronous way to call Web APIs. If you need a v2 compatible one, you can use `LegacyWebClient`. Apart from the name, there is no breaking change in the class.
+
+If you're using `run_async=True` option, we highly recommend switching to `AsyncWebClient`. `AsyncWebClient` is a straight-forward async HTTP client. You can expect the class properly works in the nature of `async/await` provided by the standard `asyncio` library.
+
+---
+
+## Migration from v1.x to v2.x {#fromv1}
+
+If you're migrating from v1.x of `slackclient` to v2.x, here's what you need to change to ensure your app continues working after updating.
+
+:::info[We have completely rewritten this library and you should only upgrade once you have fully tested it in your development environment.] 
+
+If you don't wish to upgrade yet, be sure to pin your module for the Python `slackclient` to `1.3.1`.
+
+:::
+
+### Minimum Python versions {#minimum-versions}
+
+`slackclient` v2.x requires Python 3.7 (or higher).
+
+Client v1 support:
+- Python 2: Python 2.7 was supported in the 1.x version of the client up until Dec 31st, 2019.
+- Weโ€™ll continue to add support for any new Slack features that are released as they become available on the platform. Support for token rotation is an example of a Slack feature.
+- We will no longer be adding any new client-specific functionality to v1. Support for โ€œasynchronous programmingโ€ is an example of a client feature. Another example is storing additional data on the client.
+- We are no longer addressing bug or security fixes.
+- Github branching: The `master` branch is used for v2 code. The `v1` branch is used for v1 code.
+
+### Import changes {#import-changes}
+
+ The goal of this project is to provide a set of tools that ease the creation of Python Slack apps. To better align with this goal, weโ€™re renaming the main module to `slack`. From `slack`, developers can import various tools. 
+
+```Python
+# Before:
+# import slackclient
+
+# After:
+from slack import WebClient
+```
+
+### RTM API changes {#RTM-changes}
+
+An RTMClient allows apps to communicate with the Slack platform's Legacy RTM API. This client allows you to link callbacks to their corresponding events. When an event occurs, this client executes your callback while passing along any information it receives.
+
+Example app in v1:
+
+Here's a simple example app that replies "Hi \<@userid\>!" in a thread if you send it a message containing "Hello".
+
+```Python
+from slackclient import SlackClient
+
+slack_token = os.environ["SLACK_API_TOKEN"]
+client = SlackClient(slack_token)
+
+def say_hello(data):
+    if 'Hello' in data['text']:
+        channel_id = data['channel']
+        thread_ts = data['ts']
+        user = data['user']
+
+        client.api_call('chat.postMessage',
+            channel=channel_id,
+            text="Hi <@{}>!".format(user),
+            thread_ts=thread_ts
+        )
+
+if client.rtm_connect():
+    while client.server.connected is True:
+        for data in client.rtm_read():
+            if "type" in data and data["type"] == "message":
+                say_hello(data)
+else:
+    print "Connection Failed"
+```
+
+Example App in v2:
+
+Here's that same example app that replies "Hi \<\@userid\>!" in a thread if you send it a message containing "Hello".
+
+```Python
+import slack
+
+slack_token = os.environ["SLACK_API_TOKEN"]
+rtmclient = slack.RTMClient(token=slack_token)
+
+@slack.RTMClient.run_on(event='message')
+def say_hello(**payload):
+    data = payload['data']
+    if 'Hello' in data['text']:
+        channel_id = data['channel']
+        thread_ts = data['ts']
+        user = data['user']
+
+        webclient = payload['web_client']
+        webclient.chat_postMessage(
+            channel=channel_id,
+            text="Hi <@{}>!".format(user),
+            thread_ts=thread_ts
+        )
+
+rtmclient.start()
+```
+
+**We no longer store any team data.** In the current 1.x version of the client, we store some channel and user information internally on [`Server.py`](https://github.com/slackapi/python-slackclient/blob/master/slackclient/server.py) in `client`. This data will now be available in the open event for consumption. Developers are then free to store any information they choose. Here's an example:
+
+```Python
+# Retrieving the team domain.
+# Before:
+# team_domain = client.server.login_data["team"]["domain"]
+
+# After:
+@slack.RTMClient.run_on(event='open')
+def get_team_data(**payload):
+    team_domain = payload['data']['team']['domain']
+```
+
+RTM usage has been completely redesigned.
+
+For new projects, we recommend using [Events API](/apis/events-api). This package `slackclient` v2 doesn't have any supports for Events API but you can try https://github.com/slackapi/python-slack-events-api that works as an enhancement of Flask web framework.
+
+In the near future, we'll be providing better supports for Events API in the official SDK.
+
+### Web Client API changes {#web-client-changes}
+
+**Token refresh removed**: 
+
+This feature originally shipped as a part of workspace tokens. Since we've [gone in a new direction](https://medium.com/slack-developer-blog/the-latest-with-app-tokens-fe878d44130c) it's safe to remove this along with any related attributes stored on the client.
+
+- ~refresh_token~
+- ~token_update_callback~
+- ~client_id~
+- ~client_secret~
+
+**`#api_call()`**:
+
+- `timeout` param has been removed. Timeout is passed at the client level now.
+- `kwargs` param has been removed. You must specify where the data you pass belongs in the request. e.g. 'data' vs 'params' vs 'files'...etc
+```Python
+# Before:
+# from slackclient import SlackClient
+#
+# client = SlackClient(os.environ["SLACK_API_TOKEN"])
+# client.api_call('chat.postMessage',
+#     timeout=30,
+#     channel='C0123456',
+#     text="Hi!")
+
+# After:
+
+import slack
+
+client = slack.WebClient(os.environ["SLACK_API_TOKEN"], timeout=30)
+client.api_call('chat.postMessage', json={
+    'channel': 'C0123456',
+    'text': 'Hi!'})
+
+# Note: That while the above is allowed, the more efficient way to call that API is like this:
+client.chat_postMessage(
+    channel='C0123456',
+    text='Hi!')
+```
+
+The WebClient provides built-in methods for the Slack Web API. These methods act as helpers, enabling you to focus less on how the request is constructed. Here are a few things this provides:
+
+- Basic information about each method through the docstring.
+- Easy file uploads: You can pass in the location of a file and the library will handle opening and retrieving the file object to be transmitted.
+- Token type validation: This gives you better error messaging when you're attempting to consume an API method your token doesn't have access to.
+- Constructs requests using Slack preferred HTTP methods and content-types.
+
+### Error handling changes {#error-handling-changes}
+
+In version 1.x, a failed API call would return the error payload to you and expect you to handle the error. In version 2.x, a failed API call will throw an exception. To handle this in your code, you will have to wrap API calls with a `try except` block.
diff --git a/docs/english/web.md b/docs/english/web.md
new file mode 100644
index 000000000..b776ce5fd
--- /dev/null
+++ b/docs/english/web.md
@@ -0,0 +1,793 @@
+# Web client
+
+The Slack Web API allows you to build applications that interact with Slack in more complex ways than the integrations we provide out of the box.
+
+Accessing Slack API methods requires an OAuth token โ€” read more about [installing with OAuth](/authentication/installing-with-oauth).
+
+Each of these [API methods](/reference/methods) is fully documented on our developer site at [docs.slack.dev](/).
+
+## Sending a message {#sending-messages}
+
+One of the primary uses of Slack is posting messages to a channel using the channel ID, or as a DM to another person using their user ID. This method will handle either a channel ID or a user ID passed to the `channel` parameter.
+
+Your app's bot user needs to be in the channel (otherwise, you will get either `not_in_channel` or `channel_not_found` error code). If your app has the [chat:write.public](/reference/scopes/chat.write.public) scope, your app can post messages without joining a channel as long as the channel is public. See the [chat.postMessage](/reference/methods/chat.postMessage) API method for more info.
+
+``` python
+import logging
+logging.basicConfig(level=logging.DEBUG)
+
+import os
+from slack_sdk import WebClient
+from slack_sdk.errors import SlackApiError
+
+slack_token = os.environ["SLACK_BOT_TOKEN"]
+client = WebClient(token=slack_token)
+
+try:
+    response = client.chat_postMessage(
+        channel="C0XXXXXX",
+        text="Hello from your app! :tada:"
+    )
+except SlackApiError as e:
+    # You will get a SlackApiError if "ok" is False
+    assert e.response["error"]    # str like 'invalid_auth', 'channel_not_found'
+```
+
+### Sending ephemeral messages
+
+Sending an ephemeral message, which is only visible to an assigned user in a specified channel, is nearly the same as sending a regular message but with an additional `user` parameter.
+
+``` python
+import os
+from slack_sdk import WebClient
+
+slack_token = os.environ["SLACK_BOT_TOKEN"]
+client = WebClient(token=slack_token)
+
+response = client.chat_postEphemeral(
+    channel="C0XXXXXX",
+    text="Hello silently from your app! :tada:",
+    user="U0XXXXXXX"
+)
+```
+
+See the [`chat.postEphemeral`](/reference/methods/chat.postEphemeral) API method for more details.
+
+### Sending streaming messages {#sending-streaming-messages}
+
+You can have your app's messages stream in to replicate conventional AI chatbot behavior. This is done through three Web API methods:
+
+* [`chat_startStream`](/reference/methods/chat.startStream)
+* [`chat_appendStream`](/reference/methods/chat.appendStream)
+* [`chat_stopStream`](/reference/methods/chat.stopStream)
+
+:::tip[The Python Slack SDK provides a [`chat_stream()`](https://docs.slack.dev/tools/python-slack-sdk/reference/web/client.html#slack_sdk.web.client.WebClient.chat_stream) helper utility to streamline calling these methods.]
+
+See the [_Streaming messages_](/tools/bolt-python/concepts/message-sending#streaming-messages) section of the Bolt for Python docs for implementation instructions. 
+
+:::
+
+#### Starting the message stream {#starting-stream}
+
+First you need to begin the message stream:
+
+```python
+# Example: Stream a response to any message
+@app.message()
+def handle_message(message, client):
+    channel_id = event.get("channel")
+    team_id = event.get("team")
+    thread_ts = event.get("thread_ts") or event.get("ts")
+    user_id = event.get("user")
+    
+    # Start a new message stream
+    stream_response = client.chat_startStream(
+        channel=channel_id,
+        recipient_team_id=team_id,
+        recipient_user_id=user_id,
+        thread_ts=thread_ts,
+    )
+    stream_ts = stream_response["ts"]
+```
+
+#### Appending content to the message stream {#appending-stream}
+
+With the stream started, you can then append text to it in chunks to convey a streaming effect.
+
+The structure of the text coming in will depend on your source. The following code snippet uses OpenAI's response structure as an example:
+
+```python
+# continued from above
+    for event in returned_message:
+        if event.type == "response.output_text.delta":
+            client.chat_appendStream(
+                channel=channel_id, 
+                ts=stream_ts, 
+                markdown_text=f"{event.delta}"
+            )
+        else:
+            continue
+```
+
+#### Stopping the message stream {#stopping-stream}
+
+Your app can then end the stream with the `chat_stopStream` method:
+
+```python
+# continued from above
+    client.chat_stopStream(
+        channel=channel_id, 
+        ts=stream_ts
+    )
+```
+
+The method also provides you an opportunity to request user feedback on your app's responses using the [feedback buttons](/reference/block-kit/block-elements/feedback-buttons-element) block element within the [context actions](/reference/block-kit/blocks/context-actions-block) block. The user will be presented with thumbs up and thumbs down buttons which send an action to your app when pressed.
+
+```python
+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
+
+@app.message()
+def handle_message(message, client):
+    # ... previous streaming code ...
+    
+    # Stop the stream and add interactive elements
+    feedback_block = create_feedback_block()
+    client.chat_stopStream(
+        channel=channel_id, 
+        ts=stream_ts, 
+        blocks=feedback_block
+    )
+```
+
+See [Formatting messages with Block Kit](#block-kit) below for more details on using Block Kit with messages.
+
+## Formatting messages with Block Kit {#block-kit}
+
+Messages posted from apps can contain more than just text; they can also include full user interfaces composed of blocks using [Block Kit](/block-kit).
+
+The [`chat.postMessage method`](/reference/methods/chat.postMessage) takes an optional blocks argument that allows you to customize the layout of a message. Blocks can be specified
+in a single array of either dict values or [slack_sdk.models.blocks.Block](https://docs.slack.dev/tools/python-slack-sdk/reference/models/blocks/index.html) objects.
+
+To send a message to a channel, use the channel's ID. For DMs, use the user's ID.
+
+``` python
+client.chat_postMessage(
+    channel="C0XXXXXX",
+    blocks=[
+        {
+            "type": "section",
+            "text": {
+                "type": "mrkdwn",
+                "text": "Danny Torrence left the following review for your property:"
+            }
+        },
+        {
+            "type": "section",
+            "text": {
+                "type": "mrkdwn",
+                "text": " \n :star: \n Doors had too many axe holes, guest in room " +
+                    "237 was far too rowdy, whole place felt stuck in the 1920s."
+            },
+            "accessory": {
+                "type": "image",
+                "image_url": "https://images.pexels.com/photos/750319/pexels-photo-750319.jpeg",
+                "alt_text": "Haunted hotel image"
+            }
+        },
+        {
+            "type": "section",
+            "fields": [
+                {
+                    "type": "mrkdwn",
+                    "text": "*Average Rating*\n1.0"
+                }
+            ]
+        }
+    ]
+)
+```
+
+:::tip[You can use [Block Kit Builder](https://app.slack.com/block-kit-builder/) to prototype your message's look and feel.]
+
+:::
+
+## Threading messages {#threading-messages}
+
+Threaded messages are a way of grouping messages together to provide greater context. You can reply to a thread or start a new threaded conversation by simply passing the original message's `ts` ID in the `thread_ts` attribute when posting a message. If you're replying to a threaded message, you'll pass the `thread_ts` ID of the message you're replying to.
+
+A channel or DM conversation is a nearly linear timeline of messages exchanged between people, bots, and apps. When one of these messages is replied to, it becomes the parent of a thread. By default, threaded replies do not appear directly in the channel, but are instead relegated to a kind of forked timeline descending from the parent message.
+
+``` python
+response = client.chat_postMessage(
+    channel="C0XXXXXX",
+    thread_ts="1476746830.000003",
+    text="Hello from your app! :tada:"
+)
+```
+
+By default, the `reply_broadcast` parameter is set to `False`. To indicate your reply is germane to all members of a channel and therefore a notification of the reply should be posted in-channel, set the `reply_broadcast` parameter to `True`.
+
+``` python
+response = client.chat_postMessage(
+    channel="C0XXXXXX",
+    thread_ts="1476746830.000003",
+    text="Hello from your app! :tada:",
+    reply_broadcast=True
+)
+```
+:::info[While threaded messages may contain attachments and message buttons, when your reply is broadcast to the channel, it'll actually be a reference to your reply and not the reply itself.] 
+
+When appearing in the channel, it won't contain any attachments or message buttons. Updates and deletion of threaded replies works the same as regular messages.
+
+:::
+
+Refer to the [threading messages](/messaging#threading) page for more information.
+
+## Updating a message {#updating-messages}
+
+Let's say you have a bot that posts the status of a request. When that request changes, you'll want to update the message to reflect it's state.
+
+``` python
+response = client.chat_update(
+    channel="C0XXXXXX",
+    ts="1476746830.000003",
+    text="updates from your app! :tada:"
+)
+```
+
+See the [`chat.update`](/reference/methods/chat.update) API method for formatting options and some special considerations when calling this with a bot user.
+
+## Deleting a message {#deleting-messages}
+
+Sometimes you need to delete things.
+
+``` python
+response = client.chat_delete(
+    channel="C0XXXXXX",
+    ts="1476745373.000002"
+)
+```
+
+See the [`chat.delete`](/reference/methods/chat.delete) API method for more
+details.
+
+## Conversations {#conversations}
+
+The Slack Conversations API provides your app with a unified interface to work with all the channel-like things encountered in Slack: public channels, private channels, direct messages, group direct messages, and shared channels.
+
+Refer to [using the Conversations API](/apis/web-api/using-the-conversations-api) for more information.
+
+### Direct messages {#direct-messages}
+
+The `conversations.open` API method opens either a 1:1 direct message with a single user or a multi-person direct message, depending on the number of users supplied to the `users` parameter. (For public or private channels, use the `conversations.create` API method.)
+
+Provide a `users` parameter as an array with 1-8 user IDs to open or resume a conversation. Providing only 1 ID will create a direct message. providing more IDs will create a new multi-party direct message or will resume an existing conversation.
+
+Subsequent calls with the same set of users will return the already existing conversation.
+
+``` python
+import os
+from slack_sdk import WebClient
+
+client = WebClient(token=os.environ["SLACK_BOT_TOKEN"])
+response = client.conversations_open(users=["W123456789", "U987654321"])
+```
+
+See the [`conversations.open`](/reference/methods/conversations.open) API method for additional details.
+
+### Creating channels {#creating-channels}
+
+Creates a new channel, either public or private. The `name` parameter is required and may contain numbers, letters, hyphens, or underscores, and must contain fewer than 80 characters. To make the channel private, set the optional `is_private` parameter to `True`.
+
+``` python
+import os
+from slack_sdk import WebClient
+from time import time
+
+client = WebClient(token=os.environ["SLACK_BOT_TOKEN"])
+channel_name = f"my-private-channel-{round(time())}"
+response = client.conversations_create(
+    name=channel_name,
+    is_private=True
+)
+channel_id = response["channel"]["id"]
+response = client.conversations_archive(channel=channel_id)
+```
+
+See the [`conversations.create`](/reference/methods/conversations.create) API method for additional details.
+
+### Getting conversation information {#getting-conversation-info}
+
+To retrieve a set of metadata about a channel (public, private, DM, or multi-party DM), use the `conversations.info` API method. The `channel` parameter is required and must be a valid channel ID. The optional `include_locale` boolean parameter will return locale data, which may be useful if you wish to return localized responses. The `include_num_members` boolean parameter will return the number of people in a channel.
+
+``` python
+import os
+from slack_sdk import WebClient
+
+client = WebClient(token=os.environ["SLACK_BOT_TOKEN"])
+response = client.conversations_info(
+    channel="C031415926",
+    include_num_members=1
+)
+```
+
+See the [`conversations.info`](/reference/methods/conversations.info) API method for more details.
+
+### Listing conversations {#listing-conversations}
+
+To get a list of all the conversations in a workspace, use the `conversations.list` API method. By default, only public conversations are returned. Use the `types` parameter specify which types of conversations you're interested in. Note that `types` is a string of comma-separated values.
+
+``` python
+import os
+from slack_sdk import WebClient
+
+client = WebClient(token=os.environ["SLACK_BOT_TOKEN"])
+response = client.conversations_list()
+conversations = response["channels"]
+```
+
+Use the `types` parameter to request additional channels, including `public_channel`, `private_channel`, `mpdm`, and `dm`. This parameter is a string of comma-separated values.
+
+``` python
+import os
+from slack_sdk import WebClient
+
+client = WebClient(token=os.environ["SLACK_BOT_TOKEN"])
+response = client.conversations_list(
+    types="public_channel, private_channel"
+)
+```
+
+Archived channels are included by default. You can exclude them by passing `exclude_archived=True` to your request.
+
+``` python
+response = client.conversations_list(exclude_archived=True)
+```
+
+See the [`conversations.list`](/reference/methods/conversations.list) API method for more details.
+
+### Getting members of a conversation {#getting-conversation-members}
+
+To get a list of members for a conversation, use the `conversations.members` API method with the required `channel` parameter.
+
+``` python
+import os
+from slack_sdk import WebClient
+
+client = WebClient(token=os.environ["SLACK_BOT_TOKEN"])
+response = client.conversations_members(channel="C16180339")
+user_ids = response["members"]
+```
+
+See the [`conversations.members`](/reference/methods/conversations.members) API method for more details.
+
+### Joining a conversation {#joining-conversations}
+
+Channels are the social hub of most Slack teams. Here's how you hop into one:
+
+``` python
+response = client.conversations_join(channel="C0XXXXXXY")
+```
+
+If you are already in the channel, the response is slightly different. The `already_in_channel` attribute will be true, and a limited `channel` object will be returned. Bot users cannot join a channel on their own, they need to be invited by another user.
+
+See the [`conversations.join`](/reference/methods/conversations.join) API method for more details.
+
+### Leaving a conversation {#leaving-conversations}
+
+To leave a conversation, use the `conversations.leave` API method with the required `channel` parameter containing the ID of the channel to leave.
+
+``` python
+import os
+from slack_sdk import WebClient
+
+client = WebClient(token=os.environ["SLACK_BOT_TOKEN"])
+response = client.conversations_leave(channel="C27182818")
+```
+
+See the [`conversations.leave`](/reference/methods/conversations.leave) API method for more details.
+
+## Opening a modal {#opening-modals}
+
+Modals allow you to collect data from users and display dynamic information in a focused surface. Modals use the same blocks that compose messages, with the addition of an `input` block.
+
+``` python
+from slack_sdk.signature import SignatureVerifier
+signature_verifier = SignatureVerifier(os.environ["SLACK_SIGNING_SECRET"])
+
+from flask import Flask, request, make_response, jsonify
+app = Flask(__name__)
+
+@app.route("/slack/events", methods=["POST"])
+def slack_app():
+    if not signature_verifier.is_valid_request(request.get_data(), request.headers):
+        return make_response("invalid request", 403)
+
+    if "payload" in request.form:
+        payload = json.loads(request.form["payload"])
+        if payload["type"] == "shortcut" and payload["callback_id"] == "test-shortcut":
+            # Open a new modal by a global shortcut
+            try:
+                api_response = client.views_open(
+                    trigger_id=payload["trigger_id"],
+                    view={
+                        "type": "modal",
+                        "callback_id": "modal-id",
+                        "title": {
+                            "type": "plain_text",
+                            "text": "Awesome Modal"
+                        },
+                        "submit": {
+                            "type": "plain_text",
+                            "text": "Submit"
+                        },
+                        "blocks": [
+                            {
+                                "type": "input",
+                                "block_id": "b-id",
+                                "label": {
+                                    "type": "plain_text",
+                                    "text": "Input label",
+                                },
+                                "element": {
+                                    "action_id": "a-id",
+                                    "type": "plain_text_input",
+                                }
+                            }
+                        ]
+                    }
+                )
+                return make_response("", 200)
+            except SlackApiError as e:
+                code = e.response["error"]
+                return make_response(f"Failed to open a modal due to {code}", 200)
+
+        if (
+            payload["type"] == "view_submission"
+            and payload["view"]["callback_id"] == "modal-id"
+        ):
+            # Handle a data submission request from the modal
+            submitted_data = payload["view"]["state"]["values"]
+            print(submitted_data)    # {'b-id': {'a-id': {'type': 'plain_text_input', 'value': 'your input'}}}
+
+            # Close this modal with an empty response body
+            return make_response("", 200)
+
+    return make_response("", 404)
+
+if __name__ == "__main__":
+    # export SLACK_SIGNING_SECRET=***
+    # export SLACK_BOT_TOKEN=xoxb-***
+    # export FLASK_ENV=development
+    # python3 app.py
+    app.run("localhost", 3000)
+```
+
+See the [`views.open`](/reference/methods/views.open) API method more details and additional parameters.
+
+Also, to run the above example, the following [Slack app
+configurations](https://api.slack.com/apps) are required.
+
+To run the above example, the following [app configurations](https://api.slack.com/apps) are required:
+
+* Enable **Interactivity** with a valid Request URL: `https://{your-public-domain}/slack/events`
+* Add a global shortcut with the callback ID: `open-modal-shortcut`
+
+## Updating and pushing modals {#updating-pushing-modals}
+
+In response to `view_submission` requests, you can tell Slack to update the current modal view by having `"response_action": update` and an updated view. There are also other `response_action` types, such as `errors` and `push`. Refer to the [modals](/surfaces/modals) page for more details.
+
+``` python
+if (
+    payload["type"] == "view_submission"
+    and payload["view"]["callback_id"] == "modal-id"
+):
+    # Handle a data submission request from the modal
+    submitted_data = payload["view"]["state"]["values"]
+    print(submitted_data)    # {'b-id': {'a-id': {'type': 'plain_text_input', 'value': 'your input'}}}
+
+    # Update the modal with a new view
+    return make_response(
+        jsonify(
+            {
+                "response_action": "update",
+                "view": {
+                    "type": "modal",
+                    "title": {"type": "plain_text", "text": "Accepted"},
+                    "close": {"type": "plain_text", "text": "Close"},
+                    "blocks": [
+                        {
+                            "type": "section",
+                            "text": {
+                                "type": "plain_text",
+                                "text": "Thanks for submitting the data!",
+                            },
+                        }
+                    ],
+                },
+            }
+        ),
+        200,
+    )
+```
+
+If your app modifies the current modal view when receiving `block_actions` requests from Slack, you can call the `views.update` API method with the given view ID.
+
+``` python
+private_metadata = "any str data you want to store"
+response = client.views_update(
+    view_id=payload["view"]["id"],
+    hash=payload["view"]["hash"],
+    view={
+        "type": "modal",
+        "callback_id": "modal-id",
+        "private_metadata": private_metadata,
+        "title": {
+            "type": "plain_text",
+            "text": "Awesome Modal"
+        },
+        "submit": {
+            "type": "plain_text",
+            "text": "Submit"
+        },
+        "close": {
+            "type": "plain_text",
+            "text": "Cancel"
+        },
+        "blocks": [
+            {
+                "type": "input",
+                "block_id": "b-id",
+                "label": {
+                    "type": "plain_text",
+                    "text": "Input label",
+                },
+                "element": {
+                    "action_id": "a-id",
+                    "type": "plain_text_input",
+                }
+            }
+        ]
+    }
+)
+```
+
+See the [`views.update`](/reference/methods/views.update) API method for more details.
+
+If you want to push a new view onto the modal instead of updating an existing view, see the [`views.push`](/reference/methods/views.push) API method.
+
+## Emoji reactions {#emoji}
+
+You can quickly respond to any message on Slack with an emoji reaction. Reactions can be used for any purpose: voting, checking off to-do items, showing excitement, or just for fun.
+
+This method adds a reaction (emoji) to an item (`file`, `file comment`, `channel message`, `group message`, or `direct message`). One of `file`, `file_comment`, or the combination of `channel` and `timestamp` must be specified. Note that your app's bot user needs to be in the channel (otherwise, you will get either a `not_in_channel` or `channel_not_found` error code).
+
+``` python
+response = client.reactions_add(
+    channel="C0XXXXXXX",
+    name="thumbsup",
+    timestamp="1234567890.123456"
+)
+```
+
+Removing an emoji reaction is basically the same format, but you'll use the `reactions.remove` API method instead of the `reactions.add` API method.
+
+``` python
+response = client.reactions_remove(
+    channel="C0XXXXXXX",
+    name="thumbsup",
+    timestamp="1234567890.123456"
+)
+```
+
+See the [`reactions.add`](/reference/methods/reactions.add) and [`reactions.remove`](/reference/methods/reactions.remove) API methods for more details.
+
+## Uploading files {#upload-files}
+
+You can upload files to Slack and share them with people in channels. Note that your app's bot user needs to be in the channel (otherwise, you will get either `not_in_channel` or `channel_not_found` error code).
+
+``` python
+response = client.files_upload_v2(
+    file="test.pdf",
+    title="Test upload",
+    channel="C3UKJTQAC",
+    initial_comment="Here is the latest version of the file!",
+)
+```
+
+If you want to share files within a thread, you can pass `thread_ts` in addition to `channel_id` as shown below:
+
+``` python
+response = client.files_upload_v2(
+    file="test.pdf",
+    title="Test upload",
+    channel="C3UKJTQAC",
+    thread_ts="1731398999.934122",
+    initial_comment="Here is the latest version of the file!",
+)
+```
+
+See the [`files.upload`](/reference/methods/files.upload) API method for more details.
+
+## Adding a remote file {#adding-remote-files}
+
+You can add a file information that is stored in an external storage rather than in Slack.
+
+``` python
+response = client.files_remote_add(
+    external_id="the-all-hands-deck-12345",
+    external_url="https://{your domain}/files/the-all-hands-deck-12345",
+    title="The All-hands Deck",
+    preview_image="./preview.png" # will be displayed in channels
+)
+```
+
+See the [files.remote.add](/reference/methods/files.remote.add) API method for more details.
+
+## Calling API methods {#calling-API-methods}
+
+This library covers all the public endpoints as the methods in `WebClient`. That said, you may see a bit of a delay with the library release. When you're in a hurry, you can directly use the `api_call` method as below.
+
+``` python
+import os
+from slack_sdk import WebClient
+
+client = WebClient(token=os.environ['SLACK_BOT_TOKEN'])
+response = client.api_call(
+    api_method='chat.postMessage',
+    params={'channel': '#random','text': "Hello world!"}
+)
+assert response["message"]["text"] == "Hello world!"
+```
+
+## AsyncWebClient {#asyncwebclient}
+
+The webhook client is available in asynchronous programming using the standard [asyncio](https://docs.python.org/3/library/asyncio.html) library. You use `AsyncWebhookClient` instead. `AsyncWebhookClient` internally relies on the [AIOHTTP](https://docs.aiohttp.org/en/stable/) library, but it is an optional dependency. To use this class, run `pip install aiohttp` beforehand.
+
+``` python
+import asyncio
+import os
+# requires: pip install aiohttp
+from slack_sdk.web.async_client import AsyncWebClient
+from slack_sdk.errors import SlackApiError
+
+client = AsyncWebClient(token=os.environ['SLACK_API_TOKEN'])
+
+# This must be an async method
+async def post_message():
+    try:
+        # Don't forget `await` keyword here
+        response = await client.chat_postMessage(
+            channel='#random',
+            text="Hello world!"
+        )
+        assert response["message"]["text"] == "Hello world!"
+    except SlackApiError as e:
+        assert e.response["ok"] is False
+        assert e.response["error"]  # str like 'invalid_auth', 'channel_not_found'
+        print(f"Got an error: {e.response['error']}")
+
+# This is the simplest way to run the async method
+# but you can go with any ways to run it
+asyncio.run(post_message())
+```
+
+## RetryHandler {#retryhandler}
+
+With the default settings, only `ConnectionErrorRetryHandler` with its default configuration (=only one retry in the manner of [exponential backoff and jitter](https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/)) is enabled. The retry handler retries if an API client encounters a connectivity-related failure (e.g., connection reset by peer).
+
+To use other retry handlers, you can pass a list of `RetryHandler` to the client constructor. For instance, you can add the built-in `RateLimitErrorRetryHandler` this way:
+
+``` python
+import os
+from slack_sdk.web import WebClient
+client = WebClient(token=os.environ["SLACK_BOT_TOKEN"])
+
+# This handler does retries when HTTP status 429 is returned
+from slack_sdk.http_retry.builtin_handlers import RateLimitErrorRetryHandler
+rate_limit_handler = RateLimitErrorRetryHandler(max_retry_count=1)
+
+# Enable rate limited error retries as well
+client.retry_handlers.append(rate_limit_handler)
+```
+
+You can also create one on your own by defining a new class that inherits `slack_sdk.http_retry RetryHandler` (`AsyncRetryHandler` for asyncio apps) and implements required methods (internals of `can_retry` / `prepare_for_next_retry`). Check out the source code for the ones that are built in to learn how to properly implement them.
+
+``` python
+import socket
+from typing import Optional
+from slack_sdk.http_retry import (RetryHandler, RetryState, HttpRequest, HttpResponse)
+from slack_sdk.http_retry.builtin_interval_calculators import BackoffRetryIntervalCalculator
+from slack_sdk.http_retry.jitter import RandomJitter
+
+class MyRetryHandler(RetryHandler):
+    def _can_retry(
+        self,
+        *,
+        state: RetryState,
+        request: HttpRequest,
+        response: Optional[HttpResponse] = None,
+        error: Optional[Exception] = None
+    ) -> bool:
+        # [Errno 104] Connection reset by peer
+        return error is not None and isinstance(error, socket.error) and error.errno == 104
+
+client = WebClient(
+    token=os.environ["SLACK_BOT_TOKEN"],
+    retry_handlers=[MyRetryHandler(
+        max_retry_count=1,
+        interval_calculator=BackoffRetryIntervalCalculator(
+            backoff_factor=0.5,
+            jitter=RandomJitter(),
+        ),
+    )],
+)
+```
+
+For asyncio apps, `Async` prefixed corresponding modules are available. All the methods in those methods are async/await compatible. Check [the source code](https://github.com/slackapi/python-slack-sdk/blob/main/slack_sdk/http_retry/async_handler.py) for more details.
+
+## Rate limits {#rate-limits}
+
+When posting messages to a channel, Slack allows apps to send no more than one message per channel per second. We allow bursts over that limit for short periods; however, if your app continues to exceed the limit over a longer period of time, it will be rate limited. Different API methods have other limits โ€” be sure to check the [rate limits](/apis/web-api/rate-limits) and test that your app has a graceful fallback if it should hit those limits.
+
+If you go over these limits, Slack will begin returning *HTTP 429 Too Many Requests* errors, a JSON object containing the number of calls you have been making, and a *Retry-After* header containing the number of seconds until you can retry.
+
+Here's an example of how you might handle rate limited requests:
+
+``` python
+import os
+import time
+from slack_sdk import WebClient
+from slack_sdk.errors import SlackApiError
+
+client = WebClient(token=os.environ["SLACK_BOT_TOKEN"])
+
+# Simple wrapper for sending a Slack message
+def send_slack_message(channel, message):
+    return client.chat_postMessage(
+        channel=channel,
+        text=message
+    )
+
+# Make the API call and save results to `response`
+channel = "#random"
+message = "Hello, from Python!"
+# Do until being rate limited
+while True:
+    try:
+        response = send_slack_message(channel, message)
+    except SlackApiError as e:
+        if e.response.status_code == 429:
+            # The `Retry-After` header will tell you how long to wait before retrying
+            delay = int(e.response.headers['Retry-After'])
+            print(f"Rate limited. Retrying in {delay} seconds")
+            time.sleep(delay)
+            response = send_slack_message(channel, message)
+        else:
+            # other errors
+            raise e
+```
+
+Since v3.9.0, the built-in `RateLimitErrorRetryHandler` is available as an easier way to do retries for rate limited errors. Refer to the [RetryHandler](#retryhandler) section for more details.
+
+Refer to the [rate limits](/apis/web-api/rate-limits) page for more information.
diff --git a/docs/english/webhook.md b/docs/english/webhook.md
new file mode 100644
index 000000000..feaa4c7a9
--- /dev/null
+++ b/docs/english/webhook.md
@@ -0,0 +1,152 @@
+# Webhook client
+
+## Incoming webhooks {#incoming-webhooks}
+
+You can use `slack_sdk.webhook.WebhookClient` for [incoming webhooks](/messaging/sending-messages-using-incoming-webhooks) and message responses using [`response_url`](/interactivity/handling-user-interaction#message_responses) in payloads.
+
+To use [incoming webhooks](/messaging/sending-messages-using-incoming-webhooks), calling the `WebhookClient(url)#send(payload)` method works for you. The call posts a message in a channel associated with the webhook URL.
+
+``` python
+from slack_sdk.webhook import WebhookClient
+url = "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX"
+webhook = WebhookClient(url)
+
+response = webhook.send(text="Hello!")
+assert response.status_code == 200
+assert response.body == "ok"
+```
+
+It's also possible to use `blocks` using [Block Kit](/block-kit).
+
+``` python
+from slack_sdk.webhook import WebhookClient
+url = "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX"
+webhook = WebhookClient(url)
+response = webhook.send(
+    text="fallback",
+    blocks=[
+        {
+            "type": "section",
+            "text": {
+                "type": "mrkdwn",
+                "text": "You have a new request:\n**"
+            }
+        }
+    ]
+)
+```
+
+## The `response_url`
+
+User actions in channels generates a [`response_url`](/interactivity/handling-user-interaction#message_responses) and includes the URL in its payload. You can use `WebhookClient` to send a message via the `response_url`.
+
+``` python
+import os
+from slack_sdk.signature import SignatureVerifier
+signature_verifier = SignatureVerifier(
+    signing_secret=os.environ["SLACK_SIGNING_SECRET"]
+)
+
+from slack_sdk.webhook import WebhookClient
+
+from flask import Flask, request, make_response
+app = Flask(__name__)
+
+@app.route("/slack/events", methods=["POST"])
+def slack_app():
+    # Verify incoming requests from Slack
+    # https://docs.slack.dev/authentication/verifying-requests-from-slack
+    if not signature_verifier.is_valid(
+        body=request.get_data(),
+        timestamp=request.headers.get("X-Slack-Request-Timestamp"),
+        signature=request.headers.get("X-Slack-Signature")):
+        return make_response("invalid request", 403)
+
+    # Handle a slash command invocation
+    if "command" in request.form \
+        and request.form["command"] == "/reply-this":
+        response_url = request.form["response_url"]
+        text = request.form["text"]
+        webhook = WebhookClient(response_url)
+        # Send a reply in the channel
+        response = webhook.send(text=f"You said '{text}'")
+        # Acknowledge this request
+        return make_response("", 200)
+
+    return make_response("", 404)
+```
+
+## AsyncWebhookClient {#asyncwebhookclient}
+
+The webhook client is available in asynchronous programming using the standard [asyncio](https://docs.python.org/3/library/asyncio.html) library. You use `AsyncWebhookClient` instead. `AsyncWebhookClient` internally relies on the [AIOHTTP](https://docs.aiohttp.org/en/stable/) library, but it is an optional dependency. To use this class, run `pip install aiohttp` beforehand.
+
+``` python
+import asyncio
+# requires: pip install aiohttp
+from slack_sdk.webhook.async_client import AsyncWebhookClient
+url = "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX"
+
+async def send_message_via_webhook(url: str):
+    webhook = AsyncWebhookClient(url)
+    response = await webhook.send(text="Hello!")
+    assert response.status_code == 200
+    assert response.body == "ok"
+
+# This is the simplest way to run the async method
+# but you can go with any ways to run it
+asyncio.run(send_message_via_webhook(url))
+```
+
+## RetryHandler {#retryhandler}
+
+With the default settings, only `ConnectionErrorRetryHandler` with its default configuration (=only one retry in the manner of [exponential backoff and jitter](https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/)) is enabled. The retry handler retries if an API client encounters a connectivity-related failure (e.g., connection reset by peer).
+
+To use other retry handlers, you can pass a list of `RetryHandler` to the client constructor. For instance, you can add the built-in `RateLimitErrorRetryHandler` this way:
+
+``` python
+from slack_sdk.webhook import WebhookClient
+url = "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX"
+webhook = WebhookClient(url=url)
+
+# This handler does retries when HTTP status 429 is returned
+from slack_sdk.http_retry.builtin_handlers import RateLimitErrorRetryHandler
+rate_limit_handler = RateLimitErrorRetryHandler(max_retry_count=1)
+
+# Enable rate limited error retries as well
+client.retry_handlers.append(rate_limit_handler)
+```
+
+You can also create one on your own by defining a new class that inherits `slack_sdk.http_retry RetryHandler` (`AsyncRetryHandler` for asyncio apps) and implements required methods (internals of `can_retry` / `prepare_for_next_retry`). Check out the source code for the ones that are built in to learn how to properly implement them.
+
+``` python
+import socket
+from typing import Optional
+from slack_sdk.http_retry import (RetryHandler, RetryState, HttpRequest, HttpResponse)
+from slack_sdk.http_retry.builtin_interval_calculators import BackoffRetryIntervalCalculator
+from slack_sdk.http_retry.jitter import RandomJitter
+
+class MyRetryHandler(RetryHandler):
+    def _can_retry(
+        self,
+        *,
+        state: RetryState,
+        request: HttpRequest,
+        response: Optional[HttpResponse] = None,
+        error: Optional[Exception] = None
+    ) -> bool:
+        # [Errno 104] Connection reset by peer
+        return error is not None and isinstance(error, socket.error) and error.errno == 104
+
+webhook = WebhookClient(
+    url=url,
+    retry_handlers=[MyRetryHandler(
+        max_retry_count=1,
+        interval_calculator=BackoffRetryIntervalCalculator(
+            backoff_factor=0.5,
+            jitter=RandomJitter(),
+        ),
+    )],
+)
+```
+
+For asyncio apps, `Async` prefixed corresponding modules are available. All the methods in those methods are async/await compatible. Check [the source code](https://github.com/slackapi/python-slack-sdk/blob/main/slack_sdk/http_retry/async_handler.py) for more details.
diff --git a/docs/faq.html b/docs/faq.html
deleted file mode 100644
index 5dad6697a..000000000
--- a/docs/faq.html
+++ /dev/null
@@ -1,301 +0,0 @@
-
-
-    
-        
-        
-        FAQ — Python Slack SDK
-
-        
-        
-        
-        
-        
-        
-        
-        
-        
-        
-        
-    
-
-    
-        
-        
-        
-        
- - - - - - - Python Slack SDK - - -
- - -
-
- - - - - -
- -
-
-

FAQยถ

-
-

Python Documentsยถ

-

The Python module documents are available at https://slack.dev/python-slack-sdk/api-docs/slack_sdk/

-
-
-

Installation Issuesยถ

-

We recommend using virtualenv (venv) to set up your Python runtime.

-
# Create a dedicated virtual env for running your Python scripts
-python -m venv .venv
-
-# Run .venv\Scripts\activate on Windows OS
-source .venv/bin/activate
-
-# Install slack_sdk PyPI package
-pip install "slack_sdk>=3.0"
-
-# Set your token as an env variable (`set` command for Windows OS)
-export SLACK_BOT_TOKEN=xoxb-***
-
-
-

Then, verify the following code works on the Python REPL (you can start it by just python).

-
import os
-import logging
-from slack_sdk import WebClient
-logging.basicConfig(level=logging.DEBUG)
-client = WebClient(token=os.environ["SLACK_BOT_TOKEN"])
-res = client.api_test()
-
-
-

As slack package is deprecated, we recommend switching to slack_sdk package. That being said, the code youโ€™re working on may be still using the old package. If you encounter an error saying AttributeError: module 'slack' has no attribute 'WebClient', run pip list. If you find both slack_sdk and slack in the output, try removing slack by pip uninstall slack and reinstalling slack_sdk.

-
-
-

Bug Reportยถ

-

Thatโ€™s great! Thank you. Let us know on the Issue Tracker. If youโ€™re feeling particularly ambitious, why not submit a pull request with a bug fix?

-
-
-

Feature Requestsยถ

-

Thereโ€™s always something more that could be added! You can let us know in the Issue Tracker to start a discussion around the proposed feature, thatโ€™s a good start. If youโ€™re feeling particularly ambitious, why not write the feature yourself, and submit a pull request! We love feedback and we love help and we donโ€™t bite. Much.

-
-
-

Contributionsยถ

-

What an excellent question. First of all, please have a look at our general contributing guidelines.

-

All done? Great! While weโ€™re super excited to incorporate your new feature, there are a couple of things we want to make sure youโ€™ve given thought to.

-
    -
  • Please write unit tests for your new code. But donโ€™t just aim to increase the test coverage, rather, we expect you to have written thoughtful tests that ensure your new feature will continue to work as expected, and to help future contributors to ensure they donโ€™t break it!

  • -
  • Please document your new feature. Think about concrete use cases for your feature, and add a section to the appropriate document, including a complete sample program that demonstrates your feature. Donโ€™t forget to update the changelog in changelog.rst!

  • -
-

Including these two items with your pull request will totally make our dayโ€”and, more importantly, your future usersโ€™ days!

-

On that noteโ€ฆ

-
-
-

Documentationยถ

-

This projectโ€™s documentation is generated with Sphinx. If you are editing one of the many reStructuredText files in the docs-src folder, youโ€™ll need to rebuild the documentation. It is recommended to run the following steps inside a virtualenv environment.

-
./docs-v3.sh
-
-
-

Do be sure to add the docs-v3 folder and its contents to your pull request!

-
-
- - -
-
-
- -
-
- -
-

- ยฉ 2015- Slack Technologies, LLC and contributors -

-
- - - - - \ No newline at end of file diff --git a/docs/genindex.html b/docs/genindex.html deleted file mode 100644 index 1a2bcd61c..000000000 --- a/docs/genindex.html +++ /dev/null @@ -1,246 +0,0 @@ - - - - - - Index — Python Slack SDK - - - - - - - - - - - - - - - - - - -
- - - - - - - Python Slack SDK - - -
- - - - -
-

- ยฉ 2015- Slack Technologies, LLC and contributors -

-
- - - - - \ No newline at end of file diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index 0ef5a3091..000000000 --- a/docs/index.html +++ /dev/null @@ -1,339 +0,0 @@ - - - - - - Python Slack SDK — Python Slack SDK - - - - - - - - - - - - - - - - - - -
- - - - - - - Python Slack SDK - - -
- - -
-
- - - - - -
- -
-
-
-
-

Python Slack SDKยถ

-

The Slack platform offers several APIs to build apps. Each Slack API delivers part of the capabilities from the platform, so that you can pick just those that fit for your needs. This SDK offers a corresponding package for each of Slackโ€™s APIs. They are small and powerful when used independently, and work seamlessly when used together, too.

- ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Feature

What its for

Package

Web API

Send data to or query data from Slack using any of over 200 methods.

slack_sdk.web -slack_sdk.web.async_client

Webhooks / response_url

Send a message using Incoming Webhooks or response_url

slack_sdk.webhook -slack_sdk.webhook.async_client

Socket Mode

Receive and send messages over Socket Mode connections.

slack_sdk.socket_mode

OAuth

Setup the authentication flow using V2 OAuth, OpenID Connect for Slack apps.

slack_sdk.oauth

Audit Logs API

Receive audit logs API data.

slack_sdk.audit_logs

SCIM API

Utilize the SCIM APIs for provisioning and managing user accounts and groups.

slack_sdk.scim

RTM API

Listen for incoming messages and a limited set of events happening in Slack, using WebSocket.

slack_sdk.rtm_v2

Request Signature Verification

Verify incoming requests from the Slack API servers.

slack_sdk.signature

UI Builders

Construct UI components using easy-to-use builders.

slack_sdk.models

-

The Python module documents are available at https://slack.dev/python-slack-sdk/api-docs/slack_sdk/

-
-

Installationยถ

-

This package supports Python 3.6 and higher. We recommend using PyPI to install Python Slack SDK

-
pip install slack_sdk
-
-
-

Of course, you can always pull the source code directly into your project:

-
git clone https://github.com/slackapi/python-slack-sdk.git
-cd python-slack-sdk
-python3 -m venv .venv
-source .venv/bin/activate
-pip install -U pip
-pip install -e .  # install the SDK project into the virtual env
-
-
-

And then, save a few lines of code as ./test.py.

-
# test.py
-import sys
-# Enable debug logging
-import logging
-logging.basicConfig(level=logging.DEBUG)
-# Verify it works
-from slack_sdk import WebClient
-client = WebClient()
-api_response = client.api_test()
-
-
-

You can run the code this way.

-
python test.py
-
-
-

Itโ€™s also good to try on the Python REPL.

-
-
-

Getting Helpยถ

-

If you get stuck, weโ€™re here to help. The following are the best ways to get assistance working through your issue:

-
    -
  • GitHub Issue Tracker for questions, feature requests, bug reports and general discussion related to this package.

  • -
  • Visit the Slack Developer Community for getting help using Python Slack SDK or just generally bond with your fellow Slack developers.

  • -
-
-
- - -
-
-
- -
-
- -
-

- ยฉ 2015- Slack Technologies, LLC and contributors -

-
- - - - - \ No newline at end of file diff --git a/docs/installation/index.html b/docs/installation/index.html deleted file mode 100644 index 7092d42ec..000000000 --- a/docs/installation/index.html +++ /dev/null @@ -1,350 +0,0 @@ - - - - - - Installation — Python Slack SDK - - - - - - - - - - - - - - - - - - -
- - - - - - - Python Slack SDK - - -
- - -
-
- - - - - -
- -
-
-

Installationยถ

-
-

Access Tokensยถ

-

Keeping access tokens safe

-

The OAuth token you use to call the Slack API has access to the data on the workspace where it is installed.

-

Depending on the scopes granted to the token, it potentially has the ability to read and write data. Treat these tokens just as you would a password โ€“ donโ€™t publish them, donโ€™t check them into source code, donโ€™t share them with others.

-

๐ŸšซAvoid this:

-
token = 'xoxb-111-222-xxxxx'
-
-
-

We recommend you pass tokens in as environment variables, or persist them in a database that is accessed at runtime. You can add a token to the environment by starting your app as:

-
SLACK_BOT_TOKEN="xoxb-111-222-xxxxx" python myapp.py
-
-
-

Then retrieve the key with:

-
import os
-SLACK_BOT_TOKEN = os.environ["SLACK_BOT_TOKEN"]
-
-
-

For additional information, please see our Safely Storing Credentials page.

-
-
-

Workspace Installationsยถ

-

Single Workspace Install

-

If youโ€™re building an application for a single Slack workspace, thereโ€™s no need to build out the entire OAuth flow.

-

Once youโ€™ve setup your features, click on the Install App to Team button found on the Install App page. -If you add new permission scopes or Slack app features after an app has been installed, you must reinstall the app to -your workspace for changes to take effect.

-

For additional information, see the Installing Apps of our Building Slack apps page.

-

Multiple Workspace Install

-

If you intend for an app to be installed on multiple Slack workspaces, you will need to handle this installation via the industry-standard OAuth protocol. You can read more about how Slack handles Oauth.

-

(The OAuth exchange is facilitated via HTTP and requires a webserver; in this example, weโ€™ll use Flask.)

-

To configure your app for OAuth, youโ€™ll need a client ID, a client secret, and a set of one or more scopes that will be applied to the token once it is granted. The client ID and client secret are available from your appโ€™s configuration page. The scopes are determined by the functionality of the app โ€“ every method you wish to access has a corresponding scope and your app will need to request that scope in order to be able to access the method. Review Slackโ€™s full list of OAuth scopes.

-
import os
-from slack_sdk import WebClient
-from flask import Flask, request
-
-client_id = os.environ["SLACK_CLIENT_ID"]
-client_secret = os.environ["SLACK_CLIENT_SECRET"]
-oauth_scope = os.environ["SLACK_SCOPES"]
-
-app = Flask(__name__)
-
-
-

The OAuth initiation link

-

To begin the OAuth flow that will install your app on a workspace, youโ€™ll need to provide the user with a link to Slackโ€™s OAuth page. This can be a simple link to https://slack.com/oauth/v2/authorize with scope and client_id query parameters, or you can use our pre-built Add to Slack button to do all the work for you.

-

This link directs the user to Slackโ€™s OAuth acceptance page, where the user will review and accept or refuse the permissions your app is requesting as defined by the scope(s).

-
@app.route("/slack/install", methods=["GET"])
-def pre_install():
-    state = "randomly-generated-one-time-value"
-    return '<a href="https://slack.com/oauth/v2/authorize?' \
-        f'scope={oauth_scope}&client_id={client_id}&state={state}">' \
-        'Add to Slack</a>'
-
-
-

The OAuth completion page

-

Once the user has agreed to the permissions youโ€™ve requested, Slack will redirect the user to your auth completion page, which includes a code query string param. Youโ€™ll use the code param to call the oauth.v2.access endpoint that will finally grant you the token.

-
@app.route("/slack/oauth_redirect", methods=["GET"])
-def post_install():
-    # Verify the "state" parameter
-
-    # Retrieve the auth code from the request params
-    code_param = request.args['code']
-
-    # An empty string is a valid token for this request
-    client = WebClient()
-
-    # Request the auth tokens from Slack
-    response = client.oauth_v2_access(
-        client_id=client_id,
-        client_secret=client_secret,
-        code=code_param
-    )
-
-
-

A successful request to oauth.v2.access will yield a JSON payload with at least one token, a bot token that begins with xoxb.

-
@app.route("/slack/oauth_redirect", methods=["GET"])
-def post_install():
-    # Verify the "state" parameter
-
-    # Retrieve the auth code from the request params
-    code_param = request.args['code']
-
-    # An empty string is a valid token for this request
-    client = WebClient()
-
-    # Request the auth tokens from Slack
-    response = client.oauth_v2_access(
-        client_id=client_id,
-        client_secret=client_secret,
-        code=code_param
-    )
-    print(response)
-
-    # Save the bot token to an environmental variable or to your data store
-    # for later use
-    os.environ["SLACK_BOT_TOKEN"] = response['access_token']
-
-    # Don't forget to let the user know that OAuth has succeeded!
-    return "Installation is completed!"
-
-if __name__ == "__main__":
-    app.run("localhost", 3000)
-
-
-

Once your user has completed the OAuth flow, youโ€™ll be able to use the provided tokens to call any of Slackโ€™s API methods that require an access token.

-

See the Basic Usage section of this documentation for usage examples.

-
-
- - -
-
-
- -
-
- -
-

- ยฉ 2015- Slack Technologies, LLC and contributors -

-
- - - - - \ No newline at end of file diff --git a/docs/metadata.html b/docs/metadata.html deleted file mode 100644 index 4767a34c4..000000000 --- a/docs/metadata.html +++ /dev/null @@ -1,240 +0,0 @@ - - - - - - <no title> — Python Slack SDK - - - - - - - - - - - - - - - - - - -
- - - - - - - Python Slack SDK - - -
- - - - -
-

- ยฉ 2015- Slack Technologies, LLC and contributors -

-
- - - - - \ No newline at end of file diff --git a/docs/oauth/index.html b/docs/oauth/index.html deleted file mode 100644 index 4fc289d2e..000000000 --- a/docs/oauth/index.html +++ /dev/null @@ -1,497 +0,0 @@ - - - - - - OAuth Modules — Python Slack SDK - - - - - - - - - - - - - - - - - - -
- - - - - - - Python Slack SDK - - -
- - -
-
- - - - - -
- -
-
-

OAuth Modulesยถ

-

This section explains the details about how to handle Slackโ€™s OAuth flow.

-

If youโ€™re looking for a much easier way to do the same, check Bolt for Python, which is a full-stack Slack App framework. With Bolt, you donโ€™t need to implement most of the following code on your own.

-

The Python document for this module is available at https://slack.dev/python-slack-sdk/api-docs/slack_sdk/

-
-

App Installation Flowยถ

-

OAuth lets a user in any Slack workspace install your app. At the end of OAuth, your app gains an access token. Refer to Installing with OAuth for details.

-

Python Slack SDK provides the necessary modules for building the OAuth flow.

-

Starting an OAuth flow

-

The first step of Slack OAuth flow is to redirect a Slack user to https://slack.com/oauth/v2/authorize with a valida state parameter. To implement this process, you can use the following modules.

- ----- - - - - - - - - - - - - - - - - - - -

Module

What its for

Default Implementation

InstallationStore

Persist installation data and lookup it by IDs.

FileInstallationStore

OAuthStateStore

Issue and consume state parameter value on the server-side.

FileOAuthStateStore

AuthorizeUrlGenerator

Build https://slack.com/oauth/v2/authorize with sufficient query parameters

(same)

-

The code snippet below demonstrates how to build it using Flask.

-
import os
-from slack_sdk.oauth import AuthorizeUrlGenerator
-from slack_sdk.oauth.installation_store import FileInstallationStore, Installation
-from slack_sdk.oauth.state_store import FileOAuthStateStore
-
-# Issue and consume state parameter value on the server-side.
-state_store = FileOAuthStateStore(expiration_seconds=300, base_dir="./data")
-# Persist installation data and lookup it by IDs.
-installation_store = FileInstallationStore(base_dir="./data")
-
-# Build https://slack.com/oauth/v2/authorize with sufficient query parameters
-authorize_url_generator = AuthorizeUrlGenerator(
-    client_id=os.environ["SLACK_CLIENT_ID"],
-    scopes=["app_mentions:read", "chat:write"],
-    user_scopes=["search:read"],
-)
-
-from flask import Flask, request, make_response
-app = Flask(__name__)
-
-@app.route("/slack/install", methods=["GET"])
-def oauth_start():
-    # Generate a random value and store it on the server-side
-    state = state_store.issue()
-    # https://slack.com/oauth/v2/authorize?state=(generated value)&client_id={client_id}&scope=app_mentions:read,chat:write&user_scope=search:read
-    url = authorize_url_generator.generate(state)
-    return f'<a href="{url}">' \
-           f'<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>'
-
-
-

When accessing https://(your domain)/slack/install, you will see โ€œAdd to Slackโ€ button in the webpage. You can start the appโ€™s installation flow by clicking the button.

-

Handling a callback request from Slack

-

If allโ€™s well, a user goes through the Slack app installation UI and okays your app with all the scopes that it requests. After that happens, Slack redirects the user back to your specified Redirect URL.

-

The redirection gives you a code parameter. You can exchange the value for an access token by calling oauth.v2.access API method.

-
from slack_sdk.web import WebClient
-client_secret = os.environ["SLACK_CLIENT_SECRET"]
-
-# Redirect URL
-@app.route("/slack/oauth/callback", methods=["GET"])
-def oauth_callback():
-    # Retrieve the auth code and state from the request params
-    if "code" in request.args:
-        # Verify the state parameter
-        if state_store.consume(request.args["state"]):
-            client = WebClient()  # no prepared token needed for this
-            # Complete the installation by calling oauth.v2.access API method
-            oauth_response = client.oauth_v2_access(
-                client_id=client_id,
-                client_secret=client_secret,
-                redirect_uri=redirect_uri,
-                code=request.args["code"]
-            )
-
-            installed_enterprise = oauth_response.get("enterprise", {})
-            is_enterprise_install = oauth_response.get("is_enterprise_install")
-            installed_team = oauth_response.get("team", {})
-            installer = oauth_response.get("authed_user", {})
-            incoming_webhook = oauth_response.get("incoming_webhook", {})
-
-            bot_token = oauth_response.get("access_token")
-            # NOTE: oauth.v2.access doesn't include bot_id in response
-            bot_id = None
-            enterprise_url = None
-            if bot_token is not None:
-                auth_test = client.auth_test(token=bot_token)
-                bot_id = auth_test["bot_id"]
-                if is_enterprise_install is True:
-                    enterprise_url = auth_test.get("url")
-
-            installation = Installation(
-                app_id=oauth_response.get("app_id"),
-                enterprise_id=installed_enterprise.get("id"),
-                enterprise_name=installed_enterprise.get("name"),
-                enterprise_url=enterprise_url,
-                team_id=installed_team.get("id"),
-                team_name=installed_team.get("name"),
-                bot_token=bot_token,
-                bot_id=bot_id,
-                bot_user_id=oauth_response.get("bot_user_id"),
-                bot_scopes=oauth_response.get("scope"),  # comma-separated string
-                user_id=installer.get("id"),
-                user_token=installer.get("access_token"),
-                user_scopes=installer.get("scope"),  # comma-separated string
-                incoming_webhook_url=incoming_webhook.get("url"),
-                incoming_webhook_channel=incoming_webhook.get("channel"),
-                incoming_webhook_channel_id=incoming_webhook.get("channel_id"),
-                incoming_webhook_configuration_url=incoming_webhook.get("configuration_url"),
-                is_enterprise_install=is_enterprise_install,
-                token_type=oauth_response.get("token_type"),
-            )
-
-            # Store the installation
-            installation_store.save(installation)
-
-            return "Thanks for installing this app!"
-        else:
-            return make_response(f"Try the installation again (the state value is already expired)", 400)
-
-    error = request.args["error"] if "error" in request.args else ""
-    return make_response(f"Something is wrong with the installation (error: {error})", 400)
-
-
-
-
-

Token Lookupยถ

-

Now that your Flask app can choose the right access token for incoming event requests, letโ€™s add the Slack event handler endpoint.

-

You can use the same InstallationStore in the Slack event handler.

-
import json
-from slack_sdk.errors import SlackApiError
-
-from slack_sdk.signature import SignatureVerifier
-signing_secret = os.environ["SLACK_SIGNING_SECRET"]
-signature_verifier = SignatureVerifier(signing_secret=signing_secret)
-
-@app.route("/slack/events", methods=["POST"])
-def slack_app():
-    # Verify incoming requests from Slack
-    # https://api.slack.com/authentication/verifying-requests-from-slack
-    if not signature_verifier.is_valid(
-        body=request.get_data(),
-        timestamp=request.headers.get("X-Slack-Request-Timestamp"),
-        signature=request.headers.get("X-Slack-Signature")):
-        return make_response("invalid request", 403)
-
-    # Handle a slash command invocation
-    if "command" in request.form \
-        and request.form["command"] == "/open-modal":
-        try:
-            # in the case where this app gets a request from an Enterprise Grid workspace
-            enterprise_id = request.form.get("enterprise_id")
-            # The workspace's ID
-            team_id = request.form["team_id"]
-            # Lookup the stored bot token for this workspace
-            bot = installation_store.find_bot(
-                enterprise_id=enterprise_id,
-                team_id=team_id,
-            )
-            bot_token = bot.bot_token if bot else None
-            if not bot_token:
-                # The app may be uninstalled or be used in a shared channel
-                return make_response("Please install this app first!", 200)
-
-            # Open a modal using the valid bot token
-            client = WebClient(token=bot_token)
-            trigger_id = request.form["trigger_id"]
-            response = client.views_open(
-                trigger_id=trigger_id,
-                view={
-                    "type": "modal",
-                    "callback_id": "modal-id",
-                    "title": {
-                        "type": "plain_text",
-                        "text": "Awesome Modal"
-                    },
-                    "submit": {
-                        "type": "plain_text",
-                        "text": "Submit"
-                    },
-                    "blocks": [
-                        {
-                            "type": "input",
-                            "block_id": "b-id",
-                            "label": {
-                                "type": "plain_text",
-                                "text": "Input label",
-                            },
-                            "element": {
-                                "action_id": "a-id",
-                                "type": "plain_text_input",
-                            }
-                        }
-                    ]
-                }
-            )
-            return make_response("", 200)
-        except SlackApiError as e:
-            code = e.response["error"]
-            return make_response(f"Failed to open a modal due to {code}", 200)
-
-    elif "payload" in request.form:
-        # Data submission from the modal
-        payload = json.loads(request.form["payload"])
-        if payload["type"] == "view_submission" \
-            and payload["view"]["callback_id"] == "modal-id":
-            submitted_data = payload["view"]["state"]["values"]
-            print(submitted_data)  # {'b-id': {'a-id': {'type': 'plain_text_input', 'value': 'your input'}}}
-            # You can use WebClient with a valid token here too
-            return make_response("", 200)
-
-    # Indicate unsupported request patterns
-    return make_response("", 404)
-
-
-

Again, if youโ€™re looking for an easier solution, take a look at Bolt for Python. With Bolt, you donโ€™t need to implement most of the above code on your own.

-
-
-

Sign in with Slackยถ

-

Sign in with Slack helps users log into your service using their Slack profile. The platform feature was recently upgraded to be compatible with the standard OpenID Connect specification. With slack-sdk v3.9+, implementing the auth flow is much easier.

-

When you create a new Slack app, set the following user scopes:

-
oauth_config:
-  redirect_urls:
-    - https://{your-domain}/slack/oauth_redirect
-  scopes:
-    user:
-      - openid   # required
-      - email    # optional
-      - profile  # optional
-
-
-

Check the Flask app example to learn how to implement your Web app that handles the OpenID Connect flow with end-users. It does the following:

-

Build the OpenID Connect authorize URL

-
    -
  • slack_sdk.oauth.OpenIDConnectAuthorizeUrlGenerator helps you easily do this

  • -
  • slack_sdk.oauth.OAuthStateStore is still available for generating state parameter value. Itโ€™s available for nonce management too.

  • -
-

openid.connect.* API calls

-

WebClient can perform openid.connect.token API calls with given code parameter

-

If you want to know the way with asyncio, check the Sanic app example in the same directory.

-
-
- - -
-
-
- -
-
- -
-

- ยฉ 2015- Slack Technologies, LLC and contributors -

-
- - - - - \ No newline at end of file diff --git a/docs/objects.inv b/docs/objects.inv deleted file mode 100644 index 21cf948cf..000000000 Binary files a/docs/objects.inv and /dev/null differ diff --git a/docs/real_time_messaging.html b/docs/real_time_messaging.html deleted file mode 100644 index 8814ea9d7..000000000 --- a/docs/real_time_messaging.html +++ /dev/null @@ -1,344 +0,0 @@ - - - - - - RTM Client — Python Slack SDK - - - - - - - - - - - - - - - - - - -
- - - - - - - Python Slack SDK - - -
- - -
-
- - - - - -
- -
-
-

RTM Clientยถ

-
-

Real Time Messaging (RTM)ยถ

-

The Real Time Messaging (RTM) API is a WebSocket-based API that allows you to receive events from Slack in real time and send messages as users.

-

If you prefer events to be pushed to your app, we recommend using the HTTP-based Events API along with Socket Mode instead. The Events API contains some events that arenโ€™t supported in the RTM API (like app_home_opened event), and it supports most of the event types in the RTM API. If youโ€™d like to use the Events API, you can use the Python Slack Events Adaptor.

-

The RTMClient allows apps to communicate with the Slack Platformโ€™s RTM API.

-

The event-driven architecture of this client allows you to simply link callbacks to their corresponding events. When an event occurs this client executes your callback while passing along any information it receives. We also give you the ability to call our web client from inside your callbacks.

-

In our example below, we watch for a message event that contains โ€œHelloโ€ and if its received, we call the say_hello() function. We then issue a call to the web client to post back to the channel saying โ€œHiโ€ to the user.

-

Configuring the RTM API

-

Events using the RTM API must use a classic Slack app (with a plain bot scope).

-

If you already have a classic Slack app, you can use those credentials. If you donโ€™t and need to use the RTM API, you can create a classic Slack app. You can learn more in the API documentation.

-

Also, even if the Slack app configuration pages encourage you to upgrade to the newer permission model, donโ€™t upgrade it and keep using the โ€œclassicโ€ bot permission.

-

Connecting to the RTM API

-

Note that the import here is not from slack_sdk.rtm import RTMClient but from slack_sdk.rtm_v2 import RTMClient (_v2 is added in the latter one). If you would like to use the legacy version of the client, go to the next section.

-
import os
-from slack_sdk.rtm_v2 import RTMClient
-
-rtm = RTMClient(token=os.environ["SLACK_BOT_TOKEN"])
-
-@rtm.on("message")
-def handle(client: RTMClient, event: dict):
-    if 'Hello' in event['text']:
-        channel_id = event['channel']
-        thread_ts = event['ts']
-        user = event['user'] # This is not username but user ID (the format is either U*** or W***)
-
-        client.web_client.chat_postMessage(
-            channel=channel_id,
-            text=f"Hi <@{user}>!",
-            thread_ts=thread_ts
-        )
-
-rtm.start()
-
-
-

Connecting to the RTM API (v1 client)

-

Below is a code snippet that uses the legacy version of RTMClient. For new app development, we do not recommend using it as it contains issues that have been resolved in v2. Please refer to the list of these issues for more details.

-
import os
-from slack_sdk.rtm import RTMClient
-
-@RTMClient.run_on(event="message")
-def say_hello(**payload):
-    data = payload['data']
-    web_client = payload['web_client']
-
-    if 'Hello' in data['text']:
-        channel_id = data['channel']
-        thread_ts = data['ts']
-        user = data['user'] # This is not username but user ID (the format is either U*** or W***)
-
-        web_client.chat_postMessage(
-            channel=channel_id,
-            text=f"Hi <@{user}>!",
-            thread_ts=thread_ts
-        )
-
-slack_token = os.environ["SLACK_BOT_TOKEN"]
-rtm_client = RTMClient(token=slack_token)
-rtm_client.start()
-
-
-

rtm.start vs rtm.connect (v1 client)

-

By default, the RTM client uses rtm.connect to establish a WebSocket connection with Slack. The response contains basic information about the team and WebSocket url.

-

If youโ€™d rather use rtm.start to establish the connection, which provides more information about the conversations and users on the team, you can set the connect_method option to rtm.start when instantiating the RTM Client. Note that on larger teams, use of rtm.start can be slow and unreliable.

-
import os
-from slack_sdk.rtm import RTMClient
-
-@RTMClient.run_on(event="message")
-def say_hello(**payload):
-    data = payload['data']
-    web_client = payload['web_client']
-    if 'text' in data and 'Hello' in data['text']:
-        channel_id = data['channel']
-        thread_ts = data['ts']
-        user = data['user'] # This is not username but user ID (the format is either U*** or W***)
-
-        web_client.chat_postMessage(
-            channel=channel_id,
-            text=f"Hi <@{user}>!",
-            thread_ts=thread_ts
-        )
-
-slack_token = os.environ["SLACK_BOT_TOKEN"]
-rtm_client = RTMClient(
-    token=slack_token,
-    connect_method='rtm.start'
-)
-rtm_client.start()
-
-
-

Read the rtm.connect docs and the rtm.start docs for more details. Also, note that slack.rtm_v2.RTMClient does not support rtm.start.

-

RTM Events

-
{
-    'type': 'message',
-    'ts': '1358878749.000002',
-    'user': 'U023BECGF',
-    'text': 'Hello'
-}
-
-
-

See RTM Events for a complete list of events.

-
-
- - -
-
-
- -
-
- -
-

- ยฉ 2015- Slack Technologies, LLC and contributors -

-
- - - - - \ No newline at end of file diff --git a/docs/reference/aiohttp_version_checker.html b/docs/reference/aiohttp_version_checker.html new file mode 100644 index 000000000..9430e24fa --- /dev/null +++ b/docs/reference/aiohttp_version_checker.html @@ -0,0 +1,100 @@ + + + + + + +slack_sdk.aiohttp_version_checker API documentation + + + + + + + + + + + +
+
+
+

Module slack_sdk.aiohttp_version_checker

+
+
+

Internal module for checking aiohttp compatibility of async modules

+
+
+
+
+
+
+

Functions

+
+
+def validate_aiohttp_version(aiohttp_version:ย str,
print_warning:ย Callable[[str],ย None]ย =ย <function _print_warning_log>)
+
+
+
+ +Expand source code + +
def validate_aiohttp_version(
+    aiohttp_version: str,
+    print_warning: Callable[[str], None] = _print_warning_log,
+):
+    if aiohttp_version is not None:
+        elements = aiohttp_version.split(".")
+        if len(elements) >= 3:
+            # patch version can be a non-numeric value
+            major, minor, patch = int(elements[0]), int(elements[1]), elements[2]
+            if major <= 2 or (major == 3 and (minor == 6 or (minor == 7 and patch == "0"))):
+                print_warning(
+                    "We highly recommend upgrading aiohttp to 3.7.3 or higher versions."
+                    "An older version of the library may not work with the Slack server-side in the future."
+                )
+
+
+
+
+
+
+
+
+ +
+ + + diff --git a/docs/reference/audit_logs/async_client.html b/docs/reference/audit_logs/async_client.html new file mode 100644 index 000000000..600bb9c35 --- /dev/null +++ b/docs/reference/audit_logs/async_client.html @@ -0,0 +1,736 @@ + + + + + + +slack_sdk.audit_logs.async_client API documentation + + + + + + + + + + + +
+
+
+

Module slack_sdk.audit_logs.async_client

+
+
+
+
+
+
+
+
+
+
+

Classes

+
+
+class AsyncAuditLogsClient +(token:ย str,
timeout:ย intย =ย 30,
ssl:ย ssl.SSLContextย |ย Noneย =ย None,
proxy:ย strย |ย Noneย =ย None,
base_url:ย strย =ย 'https://api.slack.com/audit/v1/',
session:ย aiohttp.client.ClientSessionย |ย Noneย =ย None,
trust_env_in_session:ย boolย =ย False,
auth:ย aiohttp.helpers.BasicAuthย |ย Noneย =ย None,
default_headers:ย Dict[str,ย str]ย |ย Noneย =ย None,
user_agent_prefix:ย strย |ย Noneย =ย None,
user_agent_suffix:ย strย |ย Noneย =ย None,
logger:ย logging.Loggerย |ย Noneย =ย None,
retry_handlers:ย List[slack_sdk.http_retry.async_handler.AsyncRetryHandler]ย |ย Noneย =ย None)
+
+
+
+ +Expand source code + +
class AsyncAuditLogsClient:
+    BASE_URL = "https://api.slack.com/audit/v1/"
+
+    token: str
+    timeout: int
+    ssl: Optional[SSLContext]
+    proxy: Optional[str]
+    base_url: str
+    session: Optional[ClientSession]
+    trust_env_in_session: bool
+    auth: Optional[BasicAuth]
+    default_headers: Dict[str, str]
+    logger: logging.Logger
+    retry_handlers: List[AsyncRetryHandler]
+
+    def __init__(
+        self,
+        token: str,
+        timeout: int = 30,
+        ssl: Optional[SSLContext] = None,
+        proxy: Optional[str] = None,
+        base_url: str = BASE_URL,
+        session: Optional[ClientSession] = None,
+        trust_env_in_session: bool = False,
+        auth: Optional[BasicAuth] = None,
+        default_headers: Optional[Dict[str, str]] = None,
+        user_agent_prefix: Optional[str] = None,
+        user_agent_suffix: Optional[str] = None,
+        logger: Optional[logging.Logger] = None,
+        retry_handlers: Optional[List[AsyncRetryHandler]] = None,
+    ):
+        """API client for Audit Logs API
+        See https://docs.slack.dev/admins/audit-logs-api/ for more details
+
+        Args:
+            token: An admin user's token, which starts with `xoxp-`
+            timeout: Request timeout (in seconds)
+            ssl: `ssl.SSLContext` to use for requests
+            proxy: Proxy URL (e.g., `localhost:9000`, `http://localhost:9000`)
+            base_url: The base URL for API calls
+            session: `aiohttp.ClientSession` instance
+            trust_env_in_session: True/False for `aiohttp.ClientSession`
+            auth: Basic auth info for `aiohttp.ClientSession`
+            default_headers: Request headers to add to all requests
+            user_agent_prefix: Prefix for User-Agent header value
+            user_agent_suffix: Suffix for User-Agent header value
+            logger: Custom logger
+            retry_handlers: Retry handlers
+        """
+        self.token = token
+        self.timeout = timeout
+        self.ssl = ssl
+        self.proxy = proxy
+        self.base_url = base_url
+        self.session = session
+        self.trust_env_in_session = trust_env_in_session
+        self.auth = auth
+        self.default_headers = default_headers if default_headers else {}
+        self.default_headers["User-Agent"] = get_user_agent(user_agent_prefix, user_agent_suffix)
+        self.logger = logger if logger is not None else logging.getLogger(__name__)
+        self.retry_handlers = retry_handlers if retry_handlers is not None else async_default_handlers()
+
+        if self.proxy is None or len(self.proxy.strip()) == 0:
+            env_variable = load_http_proxy_from_env(self.logger)
+            if env_variable is not None:
+                self.proxy = env_variable
+
+    async def schemas(
+        self,
+        *,
+        query_params: Optional[Dict[str, Any]] = None,
+        headers: Optional[Dict[str, str]] = None,
+    ) -> AuditLogsResponse:
+        """Returns information about the kind of objects which the Audit Logs API
+        returns as a list of all objects and a short description.
+        Authentication not required.
+
+        Args:
+            query_params: Set any values if you want to add query params
+            headers: Additional request headers
+        Returns:
+            API response
+        """
+        return await self.api_call(
+            path="schemas",
+            query_params=query_params,
+            headers=headers,
+        )
+
+    async def actions(
+        self,
+        *,
+        query_params: Optional[Dict[str, Any]] = None,
+        headers: Optional[Dict[str, str]] = None,
+    ) -> AuditLogsResponse:
+        """Returns information about the kind of actions that the Audit Logs API
+        returns as a list of all actions and a short description of each.
+        Authentication not required.
+
+        Args:
+            query_params: Set any values if you want to add query params
+            headers: Additional request headers
+
+        Returns:
+            API response
+        """
+        return await self.api_call(
+            path="actions",
+            query_params=query_params,
+            headers=headers,
+        )
+
+    async def logs(
+        self,
+        *,
+        latest: Optional[int] = None,
+        oldest: Optional[int] = None,
+        limit: Optional[int] = None,
+        action: Optional[str] = None,
+        actor: Optional[str] = None,
+        entity: Optional[str] = None,
+        cursor: Optional[str] = None,
+        additional_query_params: Optional[Dict[str, Any]] = None,
+        headers: Optional[Dict[str, str]] = None,
+    ) -> AuditLogsResponse:
+        """This is the primary endpoint for retrieving actual audit events from your organization.
+        It will return a list of actions that have occurred on the installed workspace or grid organization.
+        Authentication required.
+
+        The following filters can be applied in order to narrow the range of actions returned.
+        Filters are added as query string parameters and can be combined together.
+        Multiple filter parameters are additive (a boolean AND) and are separated
+        with an ampersand (&) in the query string. Filtering is entirely optional.
+
+        Args:
+            latest: Unix timestamp of the most recent audit event to include (inclusive).
+            oldest: Unix timestamp of the least recent audit event to include (inclusive).
+                Data is not available prior to March 2018.
+            limit: Number of results to optimistically return, maximum 9999.
+            action: Name of the action.
+            actor: User ID who initiated the action.
+            entity: ID of the target entity of the action (such as a channel, workspace, organization, file).
+            cursor: The next page cursor of pagination
+            additional_query_params: Add anything else if you need to use the ones this library does not support
+            headers: Additional request headers
+
+        Returns:
+            API response
+        """
+        query_params = {
+            "latest": latest,
+            "oldest": oldest,
+            "limit": limit,
+            "action": action,
+            "actor": actor,
+            "entity": entity,
+            "cursor": cursor,
+        }
+        if additional_query_params is not None:
+            query_params.update(additional_query_params)
+        query_params = {k: v for k, v in query_params.items() if v is not None}
+        return await self.api_call(
+            path="logs",
+            query_params=query_params,
+            headers=headers,
+        )
+
+    async def api_call(
+        self,
+        *,
+        http_verb: str = "GET",
+        path: str,
+        query_params: Optional[Dict[str, Any]] = None,
+        body_params: Optional[Dict[str, Any]] = None,
+        headers: Optional[Dict[str, str]] = None,
+    ) -> AuditLogsResponse:
+        url = f"{self.base_url}{path}"
+        return await self._perform_http_request(
+            http_verb=http_verb,
+            url=url,
+            query_params=query_params,
+            body_params=body_params,
+            headers=_build_request_headers(
+                token=self.token,
+                default_headers=self.default_headers,
+                additional_headers=headers,
+            ),
+        )
+
+    async def _perform_http_request(
+        self,
+        *,
+        http_verb: str,
+        url: str,
+        query_params: Optional[Dict[str, Any]],
+        body_params: Optional[Dict[str, Any]],
+        headers: Dict[str, str],
+    ) -> AuditLogsResponse:
+        if body_params is not None:
+            body_params = json.dumps(body_params)  # type: ignore[assignment]
+        headers["Content-Type"] = "application/json;charset=utf-8"
+
+        session: Optional[ClientSession] = None
+        use_running_session = self.session and not self.session.closed
+        if use_running_session:
+            session = self.session
+        else:
+            session = aiohttp.ClientSession(
+                timeout=aiohttp.ClientTimeout(total=self.timeout),
+                auth=self.auth,
+                trust_env=self.trust_env_in_session,
+            )
+
+        last_error = None
+        resp: Optional[AuditLogsResponse] = None
+        try:
+            request_kwargs = {
+                "headers": headers,
+                "params": query_params,
+                "data": body_params,
+                "ssl": self.ssl,
+                "proxy": self.proxy,
+            }
+            retry_request = RetryHttpRequest(
+                method=http_verb,
+                url=url,
+                headers=headers,  # type: ignore[arg-type]
+                body_params=body_params,
+            )
+
+            retry_state = RetryState()
+            counter_for_safety = 0
+            while counter_for_safety < 100:
+                counter_for_safety += 1
+                # If this is a retry, the next try started here. We can reset the flag.
+                retry_state.next_attempt_requested = False
+                retry_response: Optional[RetryHttpResponse] = None
+                response_body = ""
+
+                if self.logger.level <= logging.DEBUG:
+                    headers_for_logging = {
+                        k: "(redacted)" if k.lower() == "authorization" else v for k, v in headers.items()
+                    }
+                    self.logger.debug(
+                        f"Sending a request - "
+                        f"url: {url}, "
+                        f"params: {query_params}, "
+                        f"body: {body_params}, "
+                        f"headers: {headers_for_logging}"
+                    )
+
+                try:
+                    async with session.request(http_verb, url, **request_kwargs) as res:  # type: ignore[arg-type, union-attr] # noqa: E501
+                        try:
+                            response_body = await res.text()
+                            retry_response = RetryHttpResponse(
+                                status_code=res.status,
+                                headers=res.headers,  # type: ignore[arg-type]
+                                data=response_body.encode("utf-8") if response_body is not None else None,
+                            )
+                        except aiohttp.ContentTypeError:
+                            self.logger.debug(f"No response data returned from the following API call: {url}.")
+                            retry_response = RetryHttpResponse(
+                                status_code=res.status,
+                                headers=res.headers,  # type: ignore[arg-type]
+                            )
+                        except json.decoder.JSONDecodeError as e:
+                            message = f"Failed to parse the response body: {str(e)}"
+                            raise SlackApiError(message, res)
+
+                        if res.status == 429:
+                            for handler in self.retry_handlers:
+                                if await handler.can_retry_async(
+                                    state=retry_state,
+                                    request=retry_request,
+                                    response=retry_response,
+                                ):
+                                    if self.logger.level <= logging.DEBUG:
+                                        self.logger.info(
+                                            f"A retry handler found: {type(handler).__name__} "
+                                            f"for {http_verb} {url} - rate_limited"
+                                        )
+                                    await handler.prepare_for_next_attempt_async(
+                                        state=retry_state,
+                                        request=retry_request,
+                                        response=retry_response,
+                                    )
+                                    break
+
+                        if retry_state.next_attempt_requested is False:
+                            resp = AuditLogsResponse(
+                                url=url,
+                                status_code=res.status,
+                                raw_body=response_body,
+                                headers=res.headers,  # type: ignore[arg-type]
+                            )
+                            _debug_log_response(self.logger, resp)
+                            return resp
+
+                except Exception as e:
+                    last_error = e
+                    for handler in self.retry_handlers:
+                        if await handler.can_retry_async(
+                            state=retry_state,
+                            request=retry_request,
+                            response=retry_response,
+                            error=e,
+                        ):
+                            if self.logger.level <= logging.DEBUG:
+                                self.logger.info(
+                                    f"A retry handler found: {type(handler).__name__} " f"for {http_verb} {url} - {e}"
+                                )
+                            await handler.prepare_for_next_attempt_async(
+                                state=retry_state,
+                                request=retry_request,
+                                response=retry_response,
+                                error=e,
+                            )
+                            break
+
+                    if retry_state.next_attempt_requested is False:
+                        raise last_error
+
+            if resp is not None:
+                return resp
+            raise last_error  # type: ignore[misc]
+
+        finally:
+            if not use_running_session:
+                await session.close()  # type: ignore[union-attr]
+
+        return resp
+
+

API client for Audit Logs API +See https://docs.slack.dev/admins/audit-logs-api/ for more details

+

Args

+
+
token
+
An admin user's token, which starts with xoxp-
+
timeout
+
Request timeout (in seconds)
+
ssl
+
ssl.SSLContext to use for requests
+
proxy
+
Proxy URL (e.g., localhost:9000, http://localhost:9000)
+
base_url
+
The base URL for API calls
+
session
+
aiohttp.ClientSession instance
+
trust_env_in_session
+
True/False for aiohttp.ClientSession
+
auth
+
Basic auth info for aiohttp.ClientSession
+
default_headers
+
Request headers to add to all requests
+
user_agent_prefix
+
Prefix for User-Agent header value
+
user_agent_suffix
+
Suffix for User-Agent header value
+
logger
+
Custom logger
+
retry_handlers
+
Retry handlers
+
+

Class variables

+
+
var BASE_URL
+
+

The type of the None singleton.

+
+
var auth :ย aiohttp.helpers.BasicAuthย |ย None
+
+

The type of the None singleton.

+
+
var base_url :ย str
+
+

The type of the None singleton.

+
+
var default_headers :ย Dict[str,ย str]
+
+

The type of the None singleton.

+
+
var logger :ย logging.Logger
+
+

The type of the None singleton.

+
+
var proxy :ย strย |ย None
+
+

The type of the None singleton.

+
+
var retry_handlers :ย List[slack_sdk.http_retry.async_handler.AsyncRetryHandler]
+
+

The type of the None singleton.

+
+
var session :ย aiohttp.client.ClientSessionย |ย None
+
+

The type of the None singleton.

+
+
var ssl :ย ssl.SSLContextย |ย None
+
+

The type of the None singleton.

+
+
var timeout :ย int
+
+

The type of the None singleton.

+
+
var token :ย str
+
+

The type of the None singleton.

+
+
var trust_env_in_session :ย bool
+
+

The type of the None singleton.

+
+
+

Methods

+
+
+async def actions(self,
*,
query_params:ย Dict[str,ย Any]ย |ย Noneย =ย None,
headers:ย Dict[str,ย str]ย |ย Noneย =ย None) โ€‘>ย AuditLogsResponse
+
+
+
+ +Expand source code + +
async def actions(
+    self,
+    *,
+    query_params: Optional[Dict[str, Any]] = None,
+    headers: Optional[Dict[str, str]] = None,
+) -> AuditLogsResponse:
+    """Returns information about the kind of actions that the Audit Logs API
+    returns as a list of all actions and a short description of each.
+    Authentication not required.
+
+    Args:
+        query_params: Set any values if you want to add query params
+        headers: Additional request headers
+
+    Returns:
+        API response
+    """
+    return await self.api_call(
+        path="actions",
+        query_params=query_params,
+        headers=headers,
+    )
+
+

Returns information about the kind of actions that the Audit Logs API +returns as a list of all actions and a short description of each. +Authentication not required.

+

Args

+
+
query_params
+
Set any values if you want to add query params
+
headers
+
Additional request headers
+
+

Returns

+

API response

+
+
+async def api_call(self,
*,
http_verb:ย strย =ย 'GET',
path:ย str,
query_params:ย Dict[str,ย Any]ย |ย Noneย =ย None,
body_params:ย Dict[str,ย Any]ย |ย Noneย =ย None,
headers:ย Dict[str,ย str]ย |ย Noneย =ย None) โ€‘>ย AuditLogsResponse
+
+
+
+ +Expand source code + +
async def api_call(
+    self,
+    *,
+    http_verb: str = "GET",
+    path: str,
+    query_params: Optional[Dict[str, Any]] = None,
+    body_params: Optional[Dict[str, Any]] = None,
+    headers: Optional[Dict[str, str]] = None,
+) -> AuditLogsResponse:
+    url = f"{self.base_url}{path}"
+    return await self._perform_http_request(
+        http_verb=http_verb,
+        url=url,
+        query_params=query_params,
+        body_params=body_params,
+        headers=_build_request_headers(
+            token=self.token,
+            default_headers=self.default_headers,
+            additional_headers=headers,
+        ),
+    )
+
+
+
+
+async def logs(self,
*,
latest:ย intย |ย Noneย =ย None,
oldest:ย intย |ย Noneย =ย None,
limit:ย intย |ย Noneย =ย None,
action:ย strย |ย Noneย =ย None,
actor:ย strย |ย Noneย =ย None,
entity:ย strย |ย Noneย =ย None,
cursor:ย strย |ย Noneย =ย None,
additional_query_params:ย Dict[str,ย Any]ย |ย Noneย =ย None,
headers:ย Dict[str,ย str]ย |ย Noneย =ย None) โ€‘>ย AuditLogsResponse
+
+
+
+ +Expand source code + +
async def logs(
+    self,
+    *,
+    latest: Optional[int] = None,
+    oldest: Optional[int] = None,
+    limit: Optional[int] = None,
+    action: Optional[str] = None,
+    actor: Optional[str] = None,
+    entity: Optional[str] = None,
+    cursor: Optional[str] = None,
+    additional_query_params: Optional[Dict[str, Any]] = None,
+    headers: Optional[Dict[str, str]] = None,
+) -> AuditLogsResponse:
+    """This is the primary endpoint for retrieving actual audit events from your organization.
+    It will return a list of actions that have occurred on the installed workspace or grid organization.
+    Authentication required.
+
+    The following filters can be applied in order to narrow the range of actions returned.
+    Filters are added as query string parameters and can be combined together.
+    Multiple filter parameters are additive (a boolean AND) and are separated
+    with an ampersand (&) in the query string. Filtering is entirely optional.
+
+    Args:
+        latest: Unix timestamp of the most recent audit event to include (inclusive).
+        oldest: Unix timestamp of the least recent audit event to include (inclusive).
+            Data is not available prior to March 2018.
+        limit: Number of results to optimistically return, maximum 9999.
+        action: Name of the action.
+        actor: User ID who initiated the action.
+        entity: ID of the target entity of the action (such as a channel, workspace, organization, file).
+        cursor: The next page cursor of pagination
+        additional_query_params: Add anything else if you need to use the ones this library does not support
+        headers: Additional request headers
+
+    Returns:
+        API response
+    """
+    query_params = {
+        "latest": latest,
+        "oldest": oldest,
+        "limit": limit,
+        "action": action,
+        "actor": actor,
+        "entity": entity,
+        "cursor": cursor,
+    }
+    if additional_query_params is not None:
+        query_params.update(additional_query_params)
+    query_params = {k: v for k, v in query_params.items() if v is not None}
+    return await self.api_call(
+        path="logs",
+        query_params=query_params,
+        headers=headers,
+    )
+
+

This is the primary endpoint for retrieving actual audit events from your organization. +It will return a list of actions that have occurred on the installed workspace or grid organization. +Authentication required.

+

The following filters can be applied in order to narrow the range of actions returned. +Filters are added as query string parameters and can be combined together. +Multiple filter parameters are additive (a boolean AND) and are separated +with an ampersand (&) in the query string. Filtering is entirely optional.

+

Args

+
+
latest
+
Unix timestamp of the most recent audit event to include (inclusive).
+
oldest
+
Unix timestamp of the least recent audit event to include (inclusive). +Data is not available prior to March 2018.
+
limit
+
Number of results to optimistically return, maximum 9999.
+
action
+
Name of the action.
+
actor
+
User ID who initiated the action.
+
entity
+
ID of the target entity of the action (such as a channel, workspace, organization, file).
+
cursor
+
The next page cursor of pagination
+
additional_query_params
+
Add anything else if you need to use the ones this library does not support
+
headers
+
Additional request headers
+
+

Returns

+

API response

+
+
+async def schemas(self,
*,
query_params:ย Dict[str,ย Any]ย |ย Noneย =ย None,
headers:ย Dict[str,ย str]ย |ย Noneย =ย None) โ€‘>ย AuditLogsResponse
+
+
+
+ +Expand source code + +
async def schemas(
+    self,
+    *,
+    query_params: Optional[Dict[str, Any]] = None,
+    headers: Optional[Dict[str, str]] = None,
+) -> AuditLogsResponse:
+    """Returns information about the kind of objects which the Audit Logs API
+    returns as a list of all objects and a short description.
+    Authentication not required.
+
+    Args:
+        query_params: Set any values if you want to add query params
+        headers: Additional request headers
+    Returns:
+        API response
+    """
+    return await self.api_call(
+        path="schemas",
+        query_params=query_params,
+        headers=headers,
+    )
+
+

Returns information about the kind of objects which the Audit Logs API +returns as a list of all objects and a short description. +Authentication not required.

+

Args

+
+
query_params
+
Set any values if you want to add query params
+
headers
+
Additional request headers
+
+

Returns

+

API response

+
+
+
+
+
+
+ +
+ + + diff --git a/docs/reference/audit_logs/index.html b/docs/reference/audit_logs/index.html new file mode 100644 index 000000000..940d34e1c --- /dev/null +++ b/docs/reference/audit_logs/index.html @@ -0,0 +1,828 @@ + + + + + + +slack_sdk.audit_logs API documentation + + + + + + + + + + + +
+
+
+

Module slack_sdk.audit_logs

+
+
+

Audit Logs API is a set of APIs for monitoring whatโ€™s happening in your Enterprise Grid organization.

+

Refer to https://docs.slack.dev/tools/python-slack-sdk/audit-logs for details.

+
+
+

Sub-modules

+
+
slack_sdk.audit_logs.async_client
+
+
+
+
slack_sdk.audit_logs.v1
+
+

Audit Logs API is a set of APIs for monitoring whatโ€™s happening in your Enterprise Grid organization โ€ฆ

+
+
+
+
+
+
+
+
+

Classes

+
+
+class AuditLogsClient +(token:ย str,
timeout:ย intย =ย 30,
ssl:ย ssl.SSLContextย |ย Noneย =ย None,
proxy:ย strย |ย Noneย =ย None,
base_url:ย strย =ย 'https://api.slack.com/audit/v1/',
default_headers:ย Dict[str,ย str]ย |ย Noneย =ย None,
user_agent_prefix:ย strย |ย Noneย =ย None,
user_agent_suffix:ย strย |ย Noneย =ย None,
logger:ย logging.Loggerย |ย Noneย =ย None,
retry_handlers:ย List[RetryHandler]ย |ย Noneย =ย None)
+
+
+
+ +Expand source code + +
class AuditLogsClient:
+    BASE_URL = "https://api.slack.com/audit/v1/"
+
+    token: str
+    timeout: int
+    ssl: Optional[SSLContext]
+    proxy: Optional[str]
+    base_url: str
+    default_headers: Dict[str, str]
+    logger: logging.Logger
+    retry_handlers: List[RetryHandler]
+
+    def __init__(
+        self,
+        token: str,
+        timeout: int = 30,
+        ssl: Optional[SSLContext] = None,
+        proxy: Optional[str] = None,
+        base_url: str = BASE_URL,
+        default_headers: Optional[Dict[str, str]] = None,
+        user_agent_prefix: Optional[str] = None,
+        user_agent_suffix: Optional[str] = None,
+        logger: Optional[logging.Logger] = None,
+        retry_handlers: Optional[List[RetryHandler]] = None,
+    ):
+        """API client for Audit Logs API
+        See https://docs.slack.dev/admins/audit-logs-api/ for more details
+
+        Args:
+            token: An admin user's token, which starts with `xoxp-`
+            timeout: Request timeout (in seconds)
+            ssl: `ssl.SSLContext` to use for requests
+            proxy: Proxy URL (e.g., `localhost:9000`, `http://localhost:9000`)
+            base_url: The base URL for API calls
+            default_headers: Request headers to add to all requests
+            user_agent_prefix: Prefix for User-Agent header value
+            user_agent_suffix: Suffix for User-Agent header value
+            logger: Custom logger
+            retry_handlers: Retry handlers
+        """
+        self.token = token
+        self.timeout = timeout
+        self.ssl = ssl
+        self.proxy = proxy
+        self.base_url = base_url
+        self.default_headers = default_headers if default_headers else {}
+        self.default_headers["User-Agent"] = get_user_agent(user_agent_prefix, user_agent_suffix)
+        self.logger = logger if logger is not None else logging.getLogger(__name__)
+        self.retry_handlers = retry_handlers if retry_handlers is not None else default_retry_handlers()
+
+        if self.proxy is None or len(self.proxy.strip()) == 0:
+            env_variable = load_http_proxy_from_env(self.logger)
+            if env_variable is not None:
+                self.proxy = env_variable
+
+    def schemas(
+        self,
+        *,
+        query_params: Optional[Dict[str, Any]] = None,
+        headers: Optional[Dict[str, str]] = None,
+    ) -> AuditLogsResponse:
+        """Returns information about the kind of objects which the Audit Logs API
+        returns as a list of all objects and a short description.
+        Authentication not required.
+
+        Args:
+            query_params: Set any values if you want to add query params
+            headers: Additional request headers
+        Returns:
+            API response
+        """
+        return self.api_call(
+            path="schemas",
+            query_params=query_params,
+            headers=headers,
+        )
+
+    def actions(
+        self,
+        *,
+        query_params: Optional[Dict[str, Any]] = None,
+        headers: Optional[Dict[str, str]] = None,
+    ) -> AuditLogsResponse:
+        """Returns information about the kind of actions that the Audit Logs API
+        returns as a list of all actions and a short description of each.
+        Authentication not required.
+
+        Args:
+            query_params: Set any values if you want to add query params
+            headers: Additional request headers
+
+        Returns:
+            API response
+        """
+        return self.api_call(
+            path="actions",
+            query_params=query_params,
+            headers=headers,
+        )
+
+    def logs(
+        self,
+        *,
+        latest: Optional[int] = None,
+        oldest: Optional[int] = None,
+        limit: Optional[int] = None,
+        action: Optional[str] = None,
+        actor: Optional[str] = None,
+        entity: Optional[str] = None,
+        cursor: Optional[str] = None,
+        additional_query_params: Optional[Dict[str, Any]] = None,
+        headers: Optional[Dict[str, str]] = None,
+    ) -> AuditLogsResponse:
+        """This is the primary endpoint for retrieving actual audit events from your organization.
+        It will return a list of actions that have occurred on the installed workspace or grid organization.
+        Authentication required.
+
+        The following filters can be applied in order to narrow the range of actions returned.
+        Filters are added as query string parameters and can be combined together.
+        Multiple filter parameters are additive (a boolean AND) and are separated
+        with an ampersand (&) in the query string. Filtering is entirely optional.
+
+        Args:
+            latest: Unix timestamp of the most recent audit event to include (inclusive).
+            oldest: Unix timestamp of the least recent audit event to include (inclusive).
+                Data is not available prior to March 2018.
+            limit: Number of results to optimistically return, maximum 9999.
+            action: Name of the action.
+            actor: User ID who initiated the action.
+            entity: ID of the target entity of the action (such as a channel, workspace, organization, file).
+            cursor: The next page cursor of pagination
+            additional_query_params: Add anything else if you need to use the ones this library does not support
+            headers: Additional request headers
+
+        Returns:
+            API response
+        """
+        query_params = {
+            "latest": latest,
+            "oldest": oldest,
+            "limit": limit,
+            "action": action,
+            "actor": actor,
+            "entity": entity,
+            "cursor": cursor,
+        }
+        if additional_query_params is not None:
+            query_params.update(additional_query_params)
+        query_params = {k: v for k, v in query_params.items() if v is not None}
+        return self.api_call(
+            path="logs",
+            query_params=query_params,
+            headers=headers,
+        )
+
+    def api_call(
+        self,
+        *,
+        http_verb: str = "GET",
+        path: str,
+        query_params: Optional[Dict[str, Any]] = None,
+        body_params: Optional[Dict[str, Any]] = None,
+        headers: Optional[Dict[str, str]] = None,
+    ) -> AuditLogsResponse:
+        """Performs a Slack API request and returns the result."""
+        url = f"{self.base_url}{path}"
+        query = _build_query(query_params)
+        if len(query) > 0:
+            url += f"?{query}"
+
+        return self._perform_http_request(
+            http_verb=http_verb,
+            url=url,
+            body=body_params,
+            headers=_build_request_headers(
+                token=self.token,
+                default_headers=self.default_headers,
+                additional_headers=headers,
+            ),
+        )
+
+    def _perform_http_request(
+        self,
+        *,
+        http_verb: str = "GET",
+        url: str,
+        body: Optional[Dict[str, Any]] = None,
+        headers: Dict[str, str],
+    ) -> AuditLogsResponse:
+        if body is not None:
+            body = json.dumps(body)  # type: ignore[assignment]
+        headers["Content-Type"] = "application/json;charset=utf-8"
+
+        if self.logger.level <= logging.DEBUG:
+            headers_for_logging = {k: "(redacted)" if k.lower() == "authorization" else v for k, v in headers.items()}
+            self.logger.debug(f"Sending a request - url: {url}, body: {body}, headers: {headers_for_logging}")
+
+        # NOTE: Intentionally ignore the `http_verb` here
+        # Slack APIs accepts any API method requests with POST methods
+        req = Request(
+            method=http_verb,
+            url=url,
+            data=body.encode("utf-8") if body is not None else None,  # type: ignore[attr-defined]
+            headers=headers,
+        )
+        resp = None
+        last_error = None
+
+        retry_state = RetryState()
+        counter_for_safety = 0
+        while counter_for_safety < 100:
+            counter_for_safety += 1
+            # If this is a retry, the next try started here. We can reset the flag.
+            retry_state.next_attempt_requested = False
+
+            try:
+                resp = self._perform_http_request_internal(url, req)
+                # The resp is a 200 OK response
+                return resp
+
+            except HTTPError as e:
+                # read the response body here
+                charset = e.headers.get_content_charset() or "utf-8"
+                response_body: str = e.read().decode(charset)
+                # As adding new values to HTTPError#headers can be ignored, building a new dict object here
+                response_headers = dict(e.headers.items())
+                resp = AuditLogsResponse(
+                    url=url,
+                    status_code=e.code,
+                    raw_body=response_body,
+                    headers=response_headers,
+                )
+                if e.code == 429:
+                    # for backward-compatibility with WebClient (v.2.5.0 or older)
+                    if "retry-after" not in resp.headers and "Retry-After" in resp.headers:
+                        resp.headers["retry-after"] = resp.headers["Retry-After"]
+                    if "Retry-After" not in resp.headers and "retry-after" in resp.headers:
+                        resp.headers["Retry-After"] = resp.headers["retry-after"]
+                _debug_log_response(self.logger, resp)
+
+                # Try to find a retry handler for this error
+                retry_request = RetryHttpRequest.from_urllib_http_request(req)
+                retry_response = RetryHttpResponse(
+                    status_code=e.code,
+                    headers={k: [v] for k, v in e.headers.items()},
+                    data=response_body.encode("utf-8") if response_body is not None else None,
+                )
+                for handler in self.retry_handlers:
+                    if handler.can_retry(
+                        state=retry_state,
+                        request=retry_request,
+                        response=retry_response,
+                        error=e,
+                    ):
+                        if self.logger.level <= logging.DEBUG:
+                            self.logger.info(
+                                f"A retry handler found: {type(handler).__name__} for {req.method} {req.full_url} - {e}"
+                            )
+                        handler.prepare_for_next_attempt(
+                            state=retry_state,
+                            request=retry_request,
+                            response=retry_response,
+                            error=e,
+                        )
+                        break
+
+                if retry_state.next_attempt_requested is False:
+                    return resp
+
+            except Exception as err:
+                last_error = err
+                self.logger.error(f"Failed to send a request to Slack API server: {err}")
+
+                # Try to find a retry handler for this error
+                retry_request = RetryHttpRequest.from_urllib_http_request(req)
+                for handler in self.retry_handlers:
+                    if handler.can_retry(
+                        state=retry_state,
+                        request=retry_request,
+                        response=None,
+                        error=err,
+                    ):
+                        if self.logger.level <= logging.DEBUG:
+                            self.logger.info(
+                                f"A retry handler found: {type(handler).__name__} for {req.method} {req.full_url} - {err}"
+                            )
+                        handler.prepare_for_next_attempt(
+                            state=retry_state,
+                            request=retry_request,
+                            response=None,
+                            error=err,
+                        )
+                        self.logger.info(f"Going to retry the same request: {req.method} {req.full_url}")
+                        break
+
+                if retry_state.next_attempt_requested is False:
+                    raise err
+
+        if resp is not None:
+            return resp
+        raise last_error  # type: ignore[misc]
+
+    def _perform_http_request_internal(self, url: str, req: Request) -> AuditLogsResponse:
+        opener: Optional[OpenerDirector] = None
+        # for security (BAN-B310)
+        if url.lower().startswith("http"):
+            if self.proxy is not None:
+                if isinstance(self.proxy, str):
+                    opener = urllib.request.build_opener(
+                        ProxyHandler({"http": self.proxy, "https": self.proxy}),
+                        HTTPSHandler(context=self.ssl),
+                    )
+                else:
+                    raise SlackRequestError(f"Invalid proxy detected: {self.proxy} must be a str value")
+        else:
+            raise SlackRequestError(f"Invalid URL detected: {url}")
+
+        http_resp: HTTPResponse
+        if opener:
+            http_resp = opener.open(req, timeout=self.timeout)
+        else:
+            http_resp = urlopen(req, context=self.ssl, timeout=self.timeout)
+        charset: str = http_resp.headers.get_content_charset() or "utf-8"
+        response_body: str = http_resp.read().decode(charset)
+        resp = AuditLogsResponse(
+            url=url,
+            status_code=http_resp.status,
+            raw_body=response_body,
+            headers=http_resp.headers,  # type: ignore[arg-type]
+        )
+        _debug_log_response(self.logger, resp)
+        return resp
+
+

API client for Audit Logs API +See https://docs.slack.dev/admins/audit-logs-api/ for more details

+

Args

+
+
token
+
An admin user's token, which starts with xoxp-
+
timeout
+
Request timeout (in seconds)
+
ssl
+
ssl.SSLContext to use for requests
+
proxy
+
Proxy URL (e.g., localhost:9000, http://localhost:9000)
+
base_url
+
The base URL for API calls
+
default_headers
+
Request headers to add to all requests
+
user_agent_prefix
+
Prefix for User-Agent header value
+
user_agent_suffix
+
Suffix for User-Agent header value
+
logger
+
Custom logger
+
retry_handlers
+
Retry handlers
+
+

Class variables

+
+
var BASE_URL
+
+

The type of the None singleton.

+
+
var base_url :ย str
+
+

The type of the None singleton.

+
+
var default_headers :ย Dict[str,ย str]
+
+

The type of the None singleton.

+
+
var logger :ย logging.Logger
+
+

The type of the None singleton.

+
+
var proxy :ย strย |ย None
+
+

The type of the None singleton.

+
+
var retry_handlers :ย List[RetryHandler]
+
+

The type of the None singleton.

+
+
var ssl :ย ssl.SSLContextย |ย None
+
+

The type of the None singleton.

+
+
var timeout :ย int
+
+

The type of the None singleton.

+
+
var token :ย str
+
+

The type of the None singleton.

+
+
+

Methods

+
+
+def actions(self,
*,
query_params:ย Dict[str,ย Any]ย |ย Noneย =ย None,
headers:ย Dict[str,ย str]ย |ย Noneย =ย None) โ€‘>ย AuditLogsResponse
+
+
+
+ +Expand source code + +
def actions(
+    self,
+    *,
+    query_params: Optional[Dict[str, Any]] = None,
+    headers: Optional[Dict[str, str]] = None,
+) -> AuditLogsResponse:
+    """Returns information about the kind of actions that the Audit Logs API
+    returns as a list of all actions and a short description of each.
+    Authentication not required.
+
+    Args:
+        query_params: Set any values if you want to add query params
+        headers: Additional request headers
+
+    Returns:
+        API response
+    """
+    return self.api_call(
+        path="actions",
+        query_params=query_params,
+        headers=headers,
+    )
+
+

Returns information about the kind of actions that the Audit Logs API +returns as a list of all actions and a short description of each. +Authentication not required.

+

Args

+
+
query_params
+
Set any values if you want to add query params
+
headers
+
Additional request headers
+
+

Returns

+

API response

+
+
+def api_call(self,
*,
http_verb:ย strย =ย 'GET',
path:ย str,
query_params:ย Dict[str,ย Any]ย |ย Noneย =ย None,
body_params:ย Dict[str,ย Any]ย |ย Noneย =ย None,
headers:ย Dict[str,ย str]ย |ย Noneย =ย None) โ€‘>ย AuditLogsResponse
+
+
+
+ +Expand source code + +
def api_call(
+    self,
+    *,
+    http_verb: str = "GET",
+    path: str,
+    query_params: Optional[Dict[str, Any]] = None,
+    body_params: Optional[Dict[str, Any]] = None,
+    headers: Optional[Dict[str, str]] = None,
+) -> AuditLogsResponse:
+    """Performs a Slack API request and returns the result."""
+    url = f"{self.base_url}{path}"
+    query = _build_query(query_params)
+    if len(query) > 0:
+        url += f"?{query}"
+
+    return self._perform_http_request(
+        http_verb=http_verb,
+        url=url,
+        body=body_params,
+        headers=_build_request_headers(
+            token=self.token,
+            default_headers=self.default_headers,
+            additional_headers=headers,
+        ),
+    )
+
+

Performs a Slack API request and returns the result.

+
+
+def logs(self,
*,
latest:ย intย |ย Noneย =ย None,
oldest:ย intย |ย Noneย =ย None,
limit:ย intย |ย Noneย =ย None,
action:ย strย |ย Noneย =ย None,
actor:ย strย |ย Noneย =ย None,
entity:ย strย |ย Noneย =ย None,
cursor:ย strย |ย Noneย =ย None,
additional_query_params:ย Dict[str,ย Any]ย |ย Noneย =ย None,
headers:ย Dict[str,ย str]ย |ย Noneย =ย None) โ€‘>ย AuditLogsResponse
+
+
+
+ +Expand source code + +
def logs(
+    self,
+    *,
+    latest: Optional[int] = None,
+    oldest: Optional[int] = None,
+    limit: Optional[int] = None,
+    action: Optional[str] = None,
+    actor: Optional[str] = None,
+    entity: Optional[str] = None,
+    cursor: Optional[str] = None,
+    additional_query_params: Optional[Dict[str, Any]] = None,
+    headers: Optional[Dict[str, str]] = None,
+) -> AuditLogsResponse:
+    """This is the primary endpoint for retrieving actual audit events from your organization.
+    It will return a list of actions that have occurred on the installed workspace or grid organization.
+    Authentication required.
+
+    The following filters can be applied in order to narrow the range of actions returned.
+    Filters are added as query string parameters and can be combined together.
+    Multiple filter parameters are additive (a boolean AND) and are separated
+    with an ampersand (&) in the query string. Filtering is entirely optional.
+
+    Args:
+        latest: Unix timestamp of the most recent audit event to include (inclusive).
+        oldest: Unix timestamp of the least recent audit event to include (inclusive).
+            Data is not available prior to March 2018.
+        limit: Number of results to optimistically return, maximum 9999.
+        action: Name of the action.
+        actor: User ID who initiated the action.
+        entity: ID of the target entity of the action (such as a channel, workspace, organization, file).
+        cursor: The next page cursor of pagination
+        additional_query_params: Add anything else if you need to use the ones this library does not support
+        headers: Additional request headers
+
+    Returns:
+        API response
+    """
+    query_params = {
+        "latest": latest,
+        "oldest": oldest,
+        "limit": limit,
+        "action": action,
+        "actor": actor,
+        "entity": entity,
+        "cursor": cursor,
+    }
+    if additional_query_params is not None:
+        query_params.update(additional_query_params)
+    query_params = {k: v for k, v in query_params.items() if v is not None}
+    return self.api_call(
+        path="logs",
+        query_params=query_params,
+        headers=headers,
+    )
+
+

This is the primary endpoint for retrieving actual audit events from your organization. +It will return a list of actions that have occurred on the installed workspace or grid organization. +Authentication required.

+

The following filters can be applied in order to narrow the range of actions returned. +Filters are added as query string parameters and can be combined together. +Multiple filter parameters are additive (a boolean AND) and are separated +with an ampersand (&) in the query string. Filtering is entirely optional.

+

Args

+
+
latest
+
Unix timestamp of the most recent audit event to include (inclusive).
+
oldest
+
Unix timestamp of the least recent audit event to include (inclusive). +Data is not available prior to March 2018.
+
limit
+
Number of results to optimistically return, maximum 9999.
+
action
+
Name of the action.
+
actor
+
User ID who initiated the action.
+
entity
+
ID of the target entity of the action (such as a channel, workspace, organization, file).
+
cursor
+
The next page cursor of pagination
+
additional_query_params
+
Add anything else if you need to use the ones this library does not support
+
headers
+
Additional request headers
+
+

Returns

+

API response

+
+
+def schemas(self,
*,
query_params:ย Dict[str,ย Any]ย |ย Noneย =ย None,
headers:ย Dict[str,ย str]ย |ย Noneย =ย None) โ€‘>ย AuditLogsResponse
+
+
+
+ +Expand source code + +
def schemas(
+    self,
+    *,
+    query_params: Optional[Dict[str, Any]] = None,
+    headers: Optional[Dict[str, str]] = None,
+) -> AuditLogsResponse:
+    """Returns information about the kind of objects which the Audit Logs API
+    returns as a list of all objects and a short description.
+    Authentication not required.
+
+    Args:
+        query_params: Set any values if you want to add query params
+        headers: Additional request headers
+    Returns:
+        API response
+    """
+    return self.api_call(
+        path="schemas",
+        query_params=query_params,
+        headers=headers,
+    )
+
+

Returns information about the kind of objects which the Audit Logs API +returns as a list of all objects and a short description. +Authentication not required.

+

Args

+
+
query_params
+
Set any values if you want to add query params
+
headers
+
Additional request headers
+
+

Returns

+

API response

+
+
+
+
+class AuditLogsResponse +(*, url:ย str, status_code:ย int, raw_body:ย strย |ย None, headers:ย dict) +
+
+
+ +Expand source code + +
class AuditLogsResponse:
+    url: str
+    status_code: int
+    headers: Dict[str, Any]
+    raw_body: Optional[str]
+    body: Optional[Dict[str, Any]]
+    typed_body: Optional[LogsResponse]
+
+    @property  # type: ignore[no-redef]
+    def typed_body(self) -> Optional[LogsResponse]:
+        if self.body is None:
+            return None
+        return LogsResponse(**self.body)
+
+    def __init__(
+        self,
+        *,
+        url: str,
+        status_code: int,
+        raw_body: Optional[str],
+        headers: dict,
+    ):
+        self.url = url
+        self.status_code = status_code
+        self.headers = headers
+        self.raw_body = raw_body
+        self.body = json.loads(raw_body) if raw_body is not None and raw_body.startswith("{") else None
+
+
+

Class variables

+
+
var body :ย Dict[str,ย Any]ย |ย None
+
+

The type of the None singleton.

+
+
var headers :ย Dict[str,ย Any]
+
+

The type of the None singleton.

+
+
var raw_body :ย strย |ย None
+
+

The type of the None singleton.

+
+
var status_code :ย int
+
+

The type of the None singleton.

+
+
var url :ย str
+
+

The type of the None singleton.

+
+
+

Instance variables

+
+
prop typed_body :ย LogsResponseย |ย None
+
+
+ +Expand source code + +
@property  # type: ignore[no-redef]
+def typed_body(self) -> Optional[LogsResponse]:
+    if self.body is None:
+        return None
+    return LogsResponse(**self.body)
+
+
+
+
+
+
+
+
+ +
+ + + diff --git a/docs/api-docs/slack_sdk/audit_logs/v1/async_client.html b/docs/reference/audit_logs/v1/async_client.html similarity index 55% rename from docs/api-docs/slack_sdk/audit_logs/v1/async_client.html rename to docs/reference/audit_logs/v1/async_client.html index 1a8759cb7..e3f01fafc 100644 --- a/docs/api-docs/slack_sdk/audit_logs/v1/async_client.html +++ b/docs/reference/audit_logs/v1/async_client.html @@ -2,18 +2,32 @@ - - + + slack_sdk.audit_logs.v1.async_client API documentation - - - - - - + + + + + + - - + +
@@ -23,40 +37,27 @@

Module slack_sdk.audit_logs.v1.async_client

Audit Logs API is a set of APIs for monitoring whatโ€™s happening in your Enterprise Grid organization.

-

Refer to https://slack.dev/python-slack-sdk/audit-logs/ for details.

+

Refer to https://docs.slack.dev/tools/python-slack-sdk/audit-logs for details.

+
+
+
+
+
+
+
+
+

Classes

+
+
+class AsyncAuditLogsClient +(token:ย str,
timeout:ย intย =ย 30,
ssl:ย ssl.SSLContextย |ย Noneย =ย None,
proxy:ย strย |ย Noneย =ย None,
base_url:ย strย =ย 'https://api.slack.com/audit/v1/',
session:ย aiohttp.client.ClientSessionย |ย Noneย =ย None,
trust_env_in_session:ย boolย =ย False,
auth:ย aiohttp.helpers.BasicAuthย |ย Noneย =ย None,
default_headers:ย Dict[str,ย str]ย |ย Noneย =ย None,
user_agent_prefix:ย strย |ย Noneย =ย None,
user_agent_suffix:ย strย |ย Noneย =ย None,
logger:ย logging.Loggerย |ย Noneย =ย None,
retry_handlers:ย List[slack_sdk.http_retry.async_handler.AsyncRetryHandler]ย |ย Noneย =ย None)
+
+
Expand source code -
"""Audit Logs API is a set of APIs for monitoring whatโ€™s happening in your Enterprise Grid organization.
-
-Refer to https://slack.dev/python-slack-sdk/audit-logs/ for details.
-"""
-import json
-import logging
-from ssl import SSLContext
-from typing import Any, List
-from typing import Dict, Optional
-
-import aiohttp
-from aiohttp import BasicAuth, ClientSession
-
-from slack_sdk.errors import SlackApiError
-from .internal_utils import (
-    _build_request_headers,
-    _debug_log_response,
-    get_user_agent,
-)
-from .response import AuditLogsResponse
-from slack_sdk.http_retry.async_handler import AsyncRetryHandler
-from slack_sdk.http_retry.builtin_async_handlers import async_default_handlers
-from slack_sdk.http_retry.request import HttpRequest as RetryHttpRequest
-from slack_sdk.http_retry.response import HttpResponse as RetryHttpResponse
-from slack_sdk.http_retry.state import RetryState
-from ...proxy_env_variable_loader import load_http_proxy_from_env
-
-
-class AsyncAuditLogsClient:
+
class AsyncAuditLogsClient:
     BASE_URL = "https://api.slack.com/audit/v1/"
 
     token: str
@@ -88,7 +89,7 @@ 

Module slack_sdk.audit_logs.v1.async_client

retry_handlers: Optional[List[AsyncRetryHandler]] = None, ): """API client for Audit Logs API - See https://api.slack.com/admins/audit-logs for more details + See https://docs.slack.dev/admins/audit-logs-api/ for more details Args: token: An admin user's token, which starts with `xoxp-` @@ -114,13 +115,9 @@

Module slack_sdk.audit_logs.v1.async_client

self.trust_env_in_session = trust_env_in_session self.auth = auth self.default_headers = default_headers if default_headers else {} - self.default_headers["User-Agent"] = get_user_agent( - user_agent_prefix, user_agent_suffix - ) + self.default_headers["User-Agent"] = get_user_agent(user_agent_prefix, user_agent_suffix) self.logger = logger if logger is not None else logging.getLogger(__name__) - self.retry_handlers = ( - retry_handlers if retry_handlers is not None else async_default_handlers() - ) + self.retry_handlers = retry_handlers if retry_handlers is not None else async_default_handlers() if self.proxy is None or len(self.proxy.strip()) == 0: env_variable = load_http_proxy_from_env(self.logger) @@ -130,7 +127,7 @@

Module slack_sdk.audit_logs.v1.async_client

async def schemas( self, *, - query_params: Optional[Dict[str, any]] = None, + query_params: Optional[Dict[str, Any]] = None, headers: Optional[Dict[str, str]] = None, ) -> AuditLogsResponse: """Returns information about the kind of objects which the Audit Logs API @@ -152,7 +149,7 @@

Module slack_sdk.audit_logs.v1.async_client

async def actions( self, *, - query_params: Optional[Dict[str, any]] = None, + query_params: Optional[Dict[str, Any]] = None, headers: Optional[Dict[str, str]] = None, ) -> AuditLogsResponse: """Returns information about the kind of actions that the Audit Logs API @@ -181,7 +178,8 @@

Module slack_sdk.audit_logs.v1.async_client

action: Optional[str] = None, actor: Optional[str] = None, entity: Optional[str] = None, - additional_query_params: Optional[Dict[str, any]] = None, + cursor: Optional[str] = None, + additional_query_params: Optional[Dict[str, Any]] = None, headers: Optional[Dict[str, str]] = None, ) -> AuditLogsResponse: """This is the primary endpoint for retrieving actual audit events from your organization. @@ -201,6 +199,7 @@

Module slack_sdk.audit_logs.v1.async_client

action: Name of the action. actor: User ID who initiated the action. entity: ID of the target entity of the action (such as a channel, workspace, organization, file). + cursor: The next page cursor of pagination additional_query_params: Add anything else if you need to use the ones this library does not support headers: Additional request headers @@ -214,6 +213,7 @@

Module slack_sdk.audit_logs.v1.async_client

"action": action, "actor": actor, "entity": entity, + "cursor": cursor, } if additional_query_params is not None: query_params.update(additional_query_params) @@ -229,8 +229,8 @@

Module slack_sdk.audit_logs.v1.async_client

*, http_verb: str = "GET", path: str, - query_params: Optional[Dict[str, any]] = None, - body_params: Optional[Dict[str, any]] = None, + query_params: Optional[Dict[str, Any]] = None, + body_params: Optional[Dict[str, Any]] = None, headers: Optional[Dict[str, str]] = None, ) -> AuditLogsResponse: url = f"{self.base_url}{path}" @@ -256,7 +256,7 @@

Module slack_sdk.audit_logs.v1.async_client

headers: Dict[str, str], ) -> AuditLogsResponse: if body_params is not None: - body_params = json.dumps(body_params) + body_params = json.dumps(body_params) # type: ignore[assignment] headers["Content-Type"] = "application/json;charset=utf-8" session: Optional[ClientSession] = None @@ -283,7 +283,7 @@

Module slack_sdk.audit_logs.v1.async_client

retry_request = RetryHttpRequest( method=http_verb, url=url, - headers=headers, + headers=headers, # type: ignore[arg-type] body_params=body_params, ) @@ -298,8 +298,7 @@

Module slack_sdk.audit_logs.v1.async_client

if self.logger.level <= logging.DEBUG: headers_for_logging = { - k: "(redacted)" if k.lower() == "authorization" else v - for k, v in headers.items() + k: "(redacted)" if k.lower() == "authorization" else v for k, v in headers.items() } self.logger.debug( f"Sending a request - " @@ -310,19 +309,19 @@

Module slack_sdk.audit_logs.v1.async_client

) try: - async with session.request(http_verb, url, **request_kwargs) as res: + async with session.request(http_verb, url, **request_kwargs) as res: # type: ignore[arg-type, union-attr] # noqa: E501 try: response_body = await res.text() retry_response = RetryHttpResponse( status_code=res.status, - headers=res.headers, - data=response_body.encode("utf-8") - if response_body is not None - else None, + headers=res.headers, # type: ignore[arg-type] + data=response_body.encode("utf-8") if response_body is not None else None, ) except aiohttp.ContentTypeError: - self.logger.debug( - f"No response data returned from the following API call: {url}." + self.logger.debug(f"No response data returned from the following API call: {url}.") + retry_response = RetryHttpResponse( + status_code=res.status, + headers=res.headers, # type: ignore[arg-type] ) except json.decoder.JSONDecodeError as e: message = f"Failed to parse the response body: {str(e)}" @@ -352,7 +351,7 @@

Module slack_sdk.audit_logs.v1.async_client

url=url, status_code=res.status, raw_body=response_body, - headers=res.headers, + headers=res.headers, # type: ignore[arg-type] ) _debug_log_response(self.logger, resp) return resp @@ -368,8 +367,7 @@

Module slack_sdk.audit_logs.v1.async_client

): if self.logger.level <= logging.DEBUG: self.logger.info( - f"A retry handler found: {type(handler).__name__} " - f"for {http_verb} {url} - {e}" + f"A retry handler found: {type(handler).__name__} " f"for {http_verb} {url} - {e}" ) await handler.prepare_for_next_attempt_async( state=retry_state, @@ -384,31 +382,16 @@

Module slack_sdk.audit_logs.v1.async_client

if resp is not None: return resp - raise last_error + raise last_error # type: ignore[misc] finally: if not use_running_session: - await session.close() + await session.close() # type: ignore[union-attr] return resp
-
-
-
-
-
-
-
-
-

Classes

-
-
-class AsyncAuditLogsClient -(token:ย str, timeout:ย intย =ย 30, ssl:ย Optional[ssl.SSLContext]ย =ย None, proxy:ย Optional[str]ย =ย None, base_url:ย strย =ย 'https://api.slack.com/audit/v1/', session:ย Optional[aiohttp.client.ClientSession]ย =ย None, trust_env_in_session:ย boolย =ย False, auth:ย Optional[aiohttp.helpers.BasicAuth]ย =ย None, default_headers:ย Optional[Dict[str,ย str]]ย =ย None, user_agent_prefix:ย Optional[str]ย =ย None, user_agent_suffix:ย Optional[str]ย =ย None, logger:ย Optional[logging.Logger]ย =ย None, retry_handlers:ย Optional[List[AsyncRetryHandler]]ย =ย None) -
-

API client for Audit Logs API -See https://api.slack.com/admins/audit-logs for more details

+See https://docs.slack.dev/admins/audit-logs-api/ for more details

Args

token
@@ -438,415 +421,63 @@

Args

retry_handlers
Retry handlers
-
- -Expand source code - -
class AsyncAuditLogsClient:
-    BASE_URL = "https://api.slack.com/audit/v1/"
-
-    token: str
-    timeout: int
-    ssl: Optional[SSLContext]
-    proxy: Optional[str]
-    base_url: str
-    session: Optional[ClientSession]
-    trust_env_in_session: bool
-    auth: Optional[BasicAuth]
-    default_headers: Dict[str, str]
-    logger: logging.Logger
-    retry_handlers: List[AsyncRetryHandler]
-
-    def __init__(
-        self,
-        token: str,
-        timeout: int = 30,
-        ssl: Optional[SSLContext] = None,
-        proxy: Optional[str] = None,
-        base_url: str = BASE_URL,
-        session: Optional[ClientSession] = None,
-        trust_env_in_session: bool = False,
-        auth: Optional[BasicAuth] = None,
-        default_headers: Optional[Dict[str, str]] = None,
-        user_agent_prefix: Optional[str] = None,
-        user_agent_suffix: Optional[str] = None,
-        logger: Optional[logging.Logger] = None,
-        retry_handlers: Optional[List[AsyncRetryHandler]] = None,
-    ):
-        """API client for Audit Logs API
-        See https://api.slack.com/admins/audit-logs for more details
-
-        Args:
-            token: An admin user's token, which starts with `xoxp-`
-            timeout: Request timeout (in seconds)
-            ssl: `ssl.SSLContext` to use for requests
-            proxy: Proxy URL (e.g., `localhost:9000`, `http://localhost:9000`)
-            base_url: The base URL for API calls
-            session: `aiohttp.ClientSession` instance
-            trust_env_in_session: True/False for `aiohttp.ClientSession`
-            auth: Basic auth info for `aiohttp.ClientSession`
-            default_headers: Request headers to add to all requests
-            user_agent_prefix: Prefix for User-Agent header value
-            user_agent_suffix: Suffix for User-Agent header value
-            logger: Custom logger
-            retry_handlers: Retry handlers
-        """
-        self.token = token
-        self.timeout = timeout
-        self.ssl = ssl
-        self.proxy = proxy
-        self.base_url = base_url
-        self.session = session
-        self.trust_env_in_session = trust_env_in_session
-        self.auth = auth
-        self.default_headers = default_headers if default_headers else {}
-        self.default_headers["User-Agent"] = get_user_agent(
-            user_agent_prefix, user_agent_suffix
-        )
-        self.logger = logger if logger is not None else logging.getLogger(__name__)
-        self.retry_handlers = (
-            retry_handlers if retry_handlers is not None else async_default_handlers()
-        )
-
-        if self.proxy is None or len(self.proxy.strip()) == 0:
-            env_variable = load_http_proxy_from_env(self.logger)
-            if env_variable is not None:
-                self.proxy = env_variable
-
-    async def schemas(
-        self,
-        *,
-        query_params: Optional[Dict[str, any]] = None,
-        headers: Optional[Dict[str, str]] = None,
-    ) -> AuditLogsResponse:
-        """Returns information about the kind of objects which the Audit Logs API
-        returns as a list of all objects and a short description.
-        Authentication not required.
-
-        Args:
-            query_params: Set any values if you want to add query params
-            headers: Additional request headers
-        Returns:
-            API response
-        """
-        return await self.api_call(
-            path="schemas",
-            query_params=query_params,
-            headers=headers,
-        )
-
-    async def actions(
-        self,
-        *,
-        query_params: Optional[Dict[str, any]] = None,
-        headers: Optional[Dict[str, str]] = None,
-    ) -> AuditLogsResponse:
-        """Returns information about the kind of actions that the Audit Logs API
-        returns as a list of all actions and a short description of each.
-        Authentication not required.
-
-        Args:
-            query_params: Set any values if you want to add query params
-            headers: Additional request headers
-
-        Returns:
-            API response
-        """
-        return await self.api_call(
-            path="actions",
-            query_params=query_params,
-            headers=headers,
-        )
-
-    async def logs(
-        self,
-        *,
-        latest: Optional[int] = None,
-        oldest: Optional[int] = None,
-        limit: Optional[int] = None,
-        action: Optional[str] = None,
-        actor: Optional[str] = None,
-        entity: Optional[str] = None,
-        additional_query_params: Optional[Dict[str, any]] = None,
-        headers: Optional[Dict[str, str]] = None,
-    ) -> AuditLogsResponse:
-        """This is the primary endpoint for retrieving actual audit events from your organization.
-        It will return a list of actions that have occurred on the installed workspace or grid organization.
-        Authentication required.
-
-        The following filters can be applied in order to narrow the range of actions returned.
-        Filters are added as query string parameters and can be combined together.
-        Multiple filter parameters are additive (a boolean AND) and are separated
-        with an ampersand (&) in the query string. Filtering is entirely optional.
-
-        Args:
-            latest: Unix timestamp of the most recent audit event to include (inclusive).
-            oldest: Unix timestamp of the least recent audit event to include (inclusive).
-                Data is not available prior to March 2018.
-            limit: Number of results to optimistically return, maximum 9999.
-            action: Name of the action.
-            actor: User ID who initiated the action.
-            entity: ID of the target entity of the action (such as a channel, workspace, organization, file).
-            additional_query_params: Add anything else if you need to use the ones this library does not support
-            headers: Additional request headers
-
-        Returns:
-            API response
-        """
-        query_params = {
-            "latest": latest,
-            "oldest": oldest,
-            "limit": limit,
-            "action": action,
-            "actor": actor,
-            "entity": entity,
-        }
-        if additional_query_params is not None:
-            query_params.update(additional_query_params)
-        query_params = {k: v for k, v in query_params.items() if v is not None}
-        return await self.api_call(
-            path="logs",
-            query_params=query_params,
-            headers=headers,
-        )
-
-    async def api_call(
-        self,
-        *,
-        http_verb: str = "GET",
-        path: str,
-        query_params: Optional[Dict[str, any]] = None,
-        body_params: Optional[Dict[str, any]] = None,
-        headers: Optional[Dict[str, str]] = None,
-    ) -> AuditLogsResponse:
-        url = f"{self.base_url}{path}"
-        return await self._perform_http_request(
-            http_verb=http_verb,
-            url=url,
-            query_params=query_params,
-            body_params=body_params,
-            headers=_build_request_headers(
-                token=self.token,
-                default_headers=self.default_headers,
-                additional_headers=headers,
-            ),
-        )
-
-    async def _perform_http_request(
-        self,
-        *,
-        http_verb: str,
-        url: str,
-        query_params: Optional[Dict[str, Any]],
-        body_params: Optional[Dict[str, Any]],
-        headers: Dict[str, str],
-    ) -> AuditLogsResponse:
-        if body_params is not None:
-            body_params = json.dumps(body_params)
-        headers["Content-Type"] = "application/json;charset=utf-8"
-
-        session: Optional[ClientSession] = None
-        use_running_session = self.session and not self.session.closed
-        if use_running_session:
-            session = self.session
-        else:
-            session = aiohttp.ClientSession(
-                timeout=aiohttp.ClientTimeout(total=self.timeout),
-                auth=self.auth,
-                trust_env=self.trust_env_in_session,
-            )
-
-        last_error = None
-        resp: Optional[AuditLogsResponse] = None
-        try:
-            request_kwargs = {
-                "headers": headers,
-                "params": query_params,
-                "data": body_params,
-                "ssl": self.ssl,
-                "proxy": self.proxy,
-            }
-            retry_request = RetryHttpRequest(
-                method=http_verb,
-                url=url,
-                headers=headers,
-                body_params=body_params,
-            )
-
-            retry_state = RetryState()
-            counter_for_safety = 0
-            while counter_for_safety < 100:
-                counter_for_safety += 1
-                # If this is a retry, the next try started here. We can reset the flag.
-                retry_state.next_attempt_requested = False
-                retry_response: Optional[RetryHttpResponse] = None
-                response_body = ""
-
-                if self.logger.level <= logging.DEBUG:
-                    headers_for_logging = {
-                        k: "(redacted)" if k.lower() == "authorization" else v
-                        for k, v in headers.items()
-                    }
-                    self.logger.debug(
-                        f"Sending a request - "
-                        f"url: {url}, "
-                        f"params: {query_params}, "
-                        f"body: {body_params}, "
-                        f"headers: {headers_for_logging}"
-                    )
-
-                try:
-                    async with session.request(http_verb, url, **request_kwargs) as res:
-                        try:
-                            response_body = await res.text()
-                            retry_response = RetryHttpResponse(
-                                status_code=res.status,
-                                headers=res.headers,
-                                data=response_body.encode("utf-8")
-                                if response_body is not None
-                                else None,
-                            )
-                        except aiohttp.ContentTypeError:
-                            self.logger.debug(
-                                f"No response data returned from the following API call: {url}."
-                            )
-                        except json.decoder.JSONDecodeError as e:
-                            message = f"Failed to parse the response body: {str(e)}"
-                            raise SlackApiError(message, res)
-
-                        if res.status == 429:
-                            for handler in self.retry_handlers:
-                                if await handler.can_retry_async(
-                                    state=retry_state,
-                                    request=retry_request,
-                                    response=retry_response,
-                                ):
-                                    if self.logger.level <= logging.DEBUG:
-                                        self.logger.info(
-                                            f"A retry handler found: {type(handler).__name__} "
-                                            f"for {http_verb} {url} - rate_limited"
-                                        )
-                                    await handler.prepare_for_next_attempt_async(
-                                        state=retry_state,
-                                        request=retry_request,
-                                        response=retry_response,
-                                    )
-                                    break
-
-                        if retry_state.next_attempt_requested is False:
-                            resp = AuditLogsResponse(
-                                url=url,
-                                status_code=res.status,
-                                raw_body=response_body,
-                                headers=res.headers,
-                            )
-                            _debug_log_response(self.logger, resp)
-                            return resp
-
-                except Exception as e:
-                    last_error = e
-                    for handler in self.retry_handlers:
-                        if await handler.can_retry_async(
-                            state=retry_state,
-                            request=retry_request,
-                            response=retry_response,
-                            error=e,
-                        ):
-                            if self.logger.level <= logging.DEBUG:
-                                self.logger.info(
-                                    f"A retry handler found: {type(handler).__name__} "
-                                    f"for {http_verb} {url} - {e}"
-                                )
-                            await handler.prepare_for_next_attempt_async(
-                                state=retry_state,
-                                request=retry_request,
-                                response=retry_response,
-                                error=e,
-                            )
-                            break
-
-                    if retry_state.next_attempt_requested is False:
-                        raise last_error
-
-            if resp is not None:
-                return resp
-            raise last_error
-
-        finally:
-            if not use_running_session:
-                await session.close()
-
-        return resp
-

Class variables

var BASE_URL
-
+

The type of the None singleton.

-
var auth :ย Optional[aiohttp.helpers.BasicAuth]
+
var auth :ย aiohttp.helpers.BasicAuthย |ย None
-
+

The type of the None singleton.

var base_url :ย str
-
+

The type of the None singleton.

var default_headers :ย Dict[str,ย str]
-
+

The type of the None singleton.

var logger :ย logging.Logger
-
+

The type of the None singleton.

-
var proxy :ย Optional[str]
+
var proxy :ย strย |ย None
-
+

The type of the None singleton.

-
var retry_handlers :ย List[AsyncRetryHandler]
+
var retry_handlers :ย List[slack_sdk.http_retry.async_handler.AsyncRetryHandler]
-
+

The type of the None singleton.

-
var session :ย Optional[aiohttp.client.ClientSession]
+
var session :ย aiohttp.client.ClientSessionย |ย None
-
+

The type of the None singleton.

-
var ssl :ย Optional[ssl.SSLContext]
+
var ssl :ย ssl.SSLContextย |ย None
-
+

The type of the None singleton.

var timeout :ย int
-
+

The type of the None singleton.

var token :ย str
-
+

The type of the None singleton.

var trust_env_in_session :ย bool
-
+

The type of the None singleton.

Methods

-async def actions(self, *, query_params:ย Optional[Dict[str,ย ]]ย =ย None, headers:ย Optional[Dict[str,ย str]]ย =ย None) โ€‘>ย AuditLogsResponse +async def actions(self,
*,
query_params:ย Dict[str,ย Any]ย |ย Noneย =ย None,
headers:ย Dict[str,ย str]ย |ย Noneย =ย None) โ€‘>ย AuditLogsResponse
-

Returns information about the kind of actions that the Audit Logs API -returns as a list of all actions and a short description of each. -Authentication not required.

-

Args

-
-
query_params
-
Set any values if you want to add query params
-
headers
-
Additional request headers
-
-

Returns

-

API response

Expand source code @@ -854,7 +485,7 @@

Returns

async def actions(
     self,
     *,
-    query_params: Optional[Dict[str, any]] = None,
+    query_params: Optional[Dict[str, Any]] = None,
     headers: Optional[Dict[str, str]] = None,
 ) -> AuditLogsResponse:
     """Returns information about the kind of actions that the Audit Logs API
@@ -874,12 +505,23 @@ 

Returns

headers=headers, )
+

Returns information about the kind of actions that the Audit Logs API +returns as a list of all actions and a short description of each. +Authentication not required.

+

Args

+
+
query_params
+
Set any values if you want to add query params
+
headers
+
Additional request headers
+
+

Returns

+

API response

-async def api_call(self, *, http_verb:ย strย =ย 'GET', path:ย str, query_params:ย Optional[Dict[str,ย ]]ย =ย None, body_params:ย Optional[Dict[str,ย ]]ย =ย None, headers:ย Optional[Dict[str,ย str]]ย =ย None) โ€‘>ย AuditLogsResponse +async def api_call(self,
*,
http_verb:ย strย =ย 'GET',
path:ย str,
query_params:ย Dict[str,ย Any]ย |ย Noneย =ย None,
body_params:ย Dict[str,ย Any]ย |ย Noneย =ย None,
headers:ย Dict[str,ย str]ย |ย Noneย =ย None) โ€‘>ย AuditLogsResponse
-
Expand source code @@ -889,8 +531,8 @@

Returns

*, http_verb: str = "GET", path: str, - query_params: Optional[Dict[str, any]] = None, - body_params: Optional[Dict[str, any]] = None, + query_params: Optional[Dict[str, Any]] = None, + body_params: Optional[Dict[str, Any]] = None, headers: Optional[Dict[str, str]] = None, ) -> AuditLogsResponse: url = f"{self.base_url}{path}" @@ -906,40 +548,12 @@

Returns

), )
+
-async def logs(self, *, latest:ย Optional[int]ย =ย None, oldest:ย Optional[int]ย =ย None, limit:ย Optional[int]ย =ย None, action:ย Optional[str]ย =ย None, actor:ย Optional[str]ย =ย None, entity:ย Optional[str]ย =ย None, additional_query_params:ย Optional[Dict[str,ย ]]ย =ย None, headers:ย Optional[Dict[str,ย str]]ย =ย None) โ€‘>ย AuditLogsResponse +async def logs(self,
*,
latest:ย intย |ย Noneย =ย None,
oldest:ย intย |ย Noneย =ย None,
limit:ย intย |ย Noneย =ย None,
action:ย strย |ย Noneย =ย None,
actor:ย strย |ย Noneย =ย None,
entity:ย strย |ย Noneย =ย None,
cursor:ย strย |ย Noneย =ย None,
additional_query_params:ย Dict[str,ย Any]ย |ย Noneย =ย None,
headers:ย Dict[str,ย str]ย |ย Noneย =ย None) โ€‘>ย AuditLogsResponse
-

This is the primary endpoint for retrieving actual audit events from your organization. -It will return a list of actions that have occurred on the installed workspace or grid organization. -Authentication required.

-

The following filters can be applied in order to narrow the range of actions returned. -Filters are added as query string parameters and can be combined together. -Multiple filter parameters are additive (a boolean AND) and are separated -with an ampersand (&) in the query string. Filtering is entirely optional.

-

Args

-
-
latest
-
Unix timestamp of the most recent audit event to include (inclusive).
-
oldest
-
Unix timestamp of the least recent audit event to include (inclusive). -Data is not available prior to March 2018.
-
limit
-
Number of results to optimistically return, maximum 9999.
-
action
-
Name of the action.
-
actor
-
User ID who initiated the action.
-
entity
-
ID of the target entity of the action (such as a channel, workspace, organization, file).
-
additional_query_params
-
Add anything else if you need to use the ones this library does not support
-
headers
-
Additional request headers
-
-

Returns

-

API response

Expand source code @@ -953,7 +567,8 @@

Returns

action: Optional[str] = None, actor: Optional[str] = None, entity: Optional[str] = None, - additional_query_params: Optional[Dict[str, any]] = None, + cursor: Optional[str] = None, + additional_query_params: Optional[Dict[str, Any]] = None, headers: Optional[Dict[str, str]] = None, ) -> AuditLogsResponse: """This is the primary endpoint for retrieving actual audit events from your organization. @@ -973,6 +588,7 @@

Returns

action: Name of the action. actor: User ID who initiated the action. entity: ID of the target entity of the action (such as a channel, workspace, organization, file). + cursor: The next page cursor of pagination additional_query_params: Add anything else if you need to use the ones this library does not support headers: Additional request headers @@ -986,6 +602,7 @@

Returns

"action": action, "actor": actor, "entity": entity, + "cursor": cursor, } if additional_query_params is not None: query_params.update(additional_query_params) @@ -996,23 +613,42 @@

Returns

headers=headers, )
- -
-async def schemas(self, *, query_params:ย Optional[Dict[str,ย ]]ย =ย None, headers:ย Optional[Dict[str,ย str]]ย =ย None) โ€‘>ย AuditLogsResponse -
-
-

Returns information about the kind of objects which the Audit Logs API -returns as a list of all objects and a short description. -Authentication not required.

+

This is the primary endpoint for retrieving actual audit events from your organization. +It will return a list of actions that have occurred on the installed workspace or grid organization. +Authentication required.

+

The following filters can be applied in order to narrow the range of actions returned. +Filters are added as query string parameters and can be combined together. +Multiple filter parameters are additive (a boolean AND) and are separated +with an ampersand (&) in the query string. Filtering is entirely optional.

Args

-
query_params
-
Set any values if you want to add query params
+
latest
+
Unix timestamp of the most recent audit event to include (inclusive).
+
oldest
+
Unix timestamp of the least recent audit event to include (inclusive). +Data is not available prior to March 2018.
+
limit
+
Number of results to optimistically return, maximum 9999.
+
action
+
Name of the action.
+
actor
+
User ID who initiated the action.
+
entity
+
ID of the target entity of the action (such as a channel, workspace, organization, file).
+
cursor
+
The next page cursor of pagination
+
additional_query_params
+
Add anything else if you need to use the ones this library does not support
headers
Additional request headers

Returns

API response

+
+
+async def schemas(self,
*,
query_params:ย Dict[str,ย Any]ย |ย Noneย =ย None,
headers:ย Dict[str,ย str]ย |ย Noneย =ย None) โ€‘>ย AuditLogsResponse
+
+
Expand source code @@ -1020,7 +656,7 @@

Returns

async def schemas(
     self,
     *,
-    query_params: Optional[Dict[str, any]] = None,
+    query_params: Optional[Dict[str, Any]] = None,
     headers: Optional[Dict[str, str]] = None,
 ) -> AuditLogsResponse:
     """Returns information about the kind of objects which the Audit Logs API
@@ -1039,6 +675,18 @@ 

Returns

headers=headers, )
+

Returns information about the kind of objects which the Audit Logs API +returns as a list of all objects and a short description. +Authentication not required.

+

Args

+
+
query_params
+
Set any values if you want to add query params
+
headers
+
Additional request headers
+
+

Returns

+

API response

@@ -1046,7 +694,6 @@

Returns

-
- -Expand source code - -
class AsyncCacheableInstallationStore(AsyncInstallationStore):
-    underlying: AsyncInstallationStore
-    cached_bots: Dict[str, Bot]
-    cached_installations: Dict[str, Installation]
-
-    def __init__(self, installation_store: AsyncInstallationStore):
-        """A simple memory cache wrapper for any installation stores.
-
-        Args:
-            installation_store: The installation store to wrap
-        """
-        self.underlying = installation_store
-        self.cached_bots = {}
-        self.cached_installations = {}
-
-    @property
-    def logger(self) -> Logger:
-        return self.underlying.logger
-
-    async def async_save(self, installation: Installation):
-        # Invalidate cache data for update operations
-        key = f"{installation.enterprise_id or ''}-{installation.team_id or ''}"
-        if key in self.cached_bots:
-            self.cached_bots.pop(key)
-        key = f"{installation.enterprise_id or ''}-{installation.team_id or ''}-{installation.user_id or ''}"
-        if key in self.cached_installations:
-            self.cached_installations.pop(key)
-        return await self.underlying.async_save(installation)
-
-    async def async_save_bot(self, bot: Bot):
-        # Invalidate cache data for update operations
-        key = f"{bot.enterprise_id or ''}-{bot.team_id or ''}"
-        if key in self.cached_bots:
-            self.cached_bots.pop(key)
-        return await self.underlying.async_save_bot(bot)
-
-    async def async_find_bot(
-        self,
-        *,
-        enterprise_id: Optional[str],
-        team_id: Optional[str],
-        is_enterprise_install: Optional[bool] = False,
-    ) -> Optional[Bot]:
-        if is_enterprise_install or team_id is None:
-            team_id = ""
-        key = f"{enterprise_id or ''}-{team_id or ''}"
-        if key in self.cached_bots:
-            return self.cached_bots[key]
-        bot = await self.underlying.async_find_bot(
-            enterprise_id=enterprise_id,
-            team_id=team_id,
-            is_enterprise_install=is_enterprise_install,
-        )
-        if bot:
-            self.cached_bots[key] = bot
-        return bot
-
-    async def async_find_installation(
-        self,
-        *,
-        enterprise_id: Optional[str],
-        team_id: Optional[str],
-        user_id: Optional[str] = None,
-        is_enterprise_install: Optional[bool] = False,
-    ) -> Optional[Installation]:
-        if is_enterprise_install or team_id is None:
-            team_id = ""
-        key = f"{enterprise_id or ''}-{team_id or ''}-{user_id or ''}"
-        if key in self.cached_installations:
-            return self.cached_installations[key]
-        installation = await self.underlying.async_find_installation(
-            enterprise_id=enterprise_id,
-            team_id=team_id,
-            user_id=user_id,
-            is_enterprise_install=is_enterprise_install,
-        )
-        if installation:
-            self.cached_installations[key] = installation
-        return installation
-
-    async def async_delete_bot(
-        self,
-        *,
-        enterprise_id: Optional[str],
-        team_id: Optional[str],
-    ) -> None:
-        await self.underlying.async_delete_bot(
-            enterprise_id=enterprise_id,
-            team_id=team_id,
-        )
-        key = f"{enterprise_id or ''}-{team_id or ''}"
-        self.cached_bots.pop(key)
-
-    async def async_delete_installation(
-        self,
-        *,
-        enterprise_id: Optional[str],
-        team_id: Optional[str],
-        user_id: Optional[str] = None,
-    ) -> None:
-        await self.underlying.async_delete_installation(
-            enterprise_id=enterprise_id,
-            team_id=team_id,
-            user_id=user_id,
-        )
-        key_prefix = f"{enterprise_id or ''}-{team_id or ''}"
-        for key in list(self.cached_installations.keys()):
-            if key.startswith(key_prefix):
-                self.cached_installations.pop(key)
-
-    async def async_delete_all(
-        self,
-        *,
-        enterprise_id: Optional[str],
-        team_id: Optional[str],
-    ):
-        await self.underlying.async_delete_all(
-            enterprise_id=enterprise_id,
-            team_id=team_id,
-        )
-        key_prefix = f"{enterprise_id or ''}-{team_id or ''}"
-        for key in list(self.cached_bots.keys()):
-            if key.startswith(key_prefix):
-                self.cached_bots.pop(key)
-        for key in list(self.cached_installations.keys()):
-            if key.startswith(key_prefix):
-                self.cached_installations.pop(key)
-

Ancestors

  • AsyncInstallationStore
  • @@ -343,22 +216,21 @@

    Class variables

    var cached_bots :ย Dict[str,ย Bot]
    -
    +

    The type of the None singleton.

    var cached_installations :ย Dict[str,ย Installation]
    -
    +

    The type of the None singleton.

    var underlying :ย AsyncInstallationStore
    -
    +

    The type of the None singleton.

    Instance variables

    -
    var logger :ย logging.Logger
    +
    prop logger :ย logging.Logger
    -
    Expand source code @@ -367,6 +239,7 @@

    Instance variables

    def logger(self) -> Logger: return self.underlying.logger
    +

    Inherited members

    @@ -388,7 +261,6 @@

    Inherited members

-
- -Expand source code - -
class CacheableInstallationStore(InstallationStore):
-    underlying: InstallationStore
-    cached_bots: Dict[str, Bot]
-    cached_installations: Dict[str, Installation]
-
-    def __init__(self, installation_store: InstallationStore):
-        """A simple memory cache wrapper for any installation stores.
-
-        Args:
-            installation_store: The installation store to wrap
-        """
-        self.underlying = installation_store
-        self.cached_bots = {}
-        self.cached_installations = {}
-
-    @property
-    def logger(self) -> Logger:
-        return self.underlying.logger
-
-    def save(self, installation: Installation):
-        # Invalidate cache data for update operations
-        key = f"{installation.enterprise_id or ''}-{installation.team_id or ''}"
-        if key in self.cached_bots:
-            self.cached_bots.pop(key)
-        key = f"{installation.enterprise_id or ''}-{installation.team_id or ''}-{installation.user_id or ''}"
-        if key in self.cached_installations:
-            self.cached_installations.pop(key)
-
-        return self.underlying.save(installation)
-
-    def save_bot(self, bot: Bot):
-        # Invalidate cache data for update operations
-        key = f"{bot.enterprise_id or ''}-{bot.team_id or ''}"
-        if key in self.cached_bots:
-            self.cached_bots.pop(key)
-        return self.underlying.save_bot(bot)
-
-    def find_bot(
-        self,
-        *,
-        enterprise_id: Optional[str],
-        team_id: Optional[str],
-        is_enterprise_install: Optional[bool] = False,
-    ) -> Optional[Bot]:
-        if is_enterprise_install or team_id is None:
-            team_id = ""
-        key = f"{enterprise_id or ''}-{team_id or ''}"
-        if key in self.cached_bots:
-            return self.cached_bots[key]
-        bot = self.underlying.find_bot(
-            enterprise_id=enterprise_id,
-            team_id=team_id,
-            is_enterprise_install=is_enterprise_install,
-        )
-        if bot:
-            self.cached_bots[key] = bot
-        return bot
-
-    def find_installation(
-        self,
-        *,
-        enterprise_id: Optional[str],
-        team_id: Optional[str],
-        user_id: Optional[str] = None,
-        is_enterprise_install: Optional[bool] = False,
-    ) -> Optional[Installation]:
-        if is_enterprise_install or team_id is None:
-            team_id = ""
-        key = f"{enterprise_id or ''}-{team_id or ''}-{user_id or ''}"
-        if key in self.cached_installations:
-            return self.cached_installations[key]
-        installation = self.underlying.find_installation(
-            enterprise_id=enterprise_id,
-            team_id=team_id,
-            user_id=user_id,
-            is_enterprise_install=is_enterprise_install,
-        )
-        if installation:
-            self.cached_installations[key] = installation
-        return installation
-
-    def delete_bot(
-        self,
-        *,
-        enterprise_id: Optional[str],
-        team_id: Optional[str],
-    ) -> None:
-        self.underlying.delete_bot(
-            enterprise_id=enterprise_id,
-            team_id=team_id,
-        )
-        key = f"{enterprise_id or ''}-{team_id or ''}"
-        if key in self.cached_bots:
-            self.cached_bots.pop(key)
-
-    def delete_installation(
-        self,
-        *,
-        enterprise_id: Optional[str],
-        team_id: Optional[str],
-        user_id: Optional[str] = None,
-    ) -> None:
-        self.underlying.delete_installation(
-            enterprise_id=enterprise_id,
-            team_id=team_id,
-            user_id=user_id,
-        )
-        key_prefix = f"{enterprise_id or ''}-{team_id or ''}"
-        for key in list(self.cached_installations.keys()):
-            if key.startswith(key_prefix):
-                self.cached_installations.pop(key)
-
-    def delete_all(
-        self,
-        *,
-        enterprise_id: Optional[str],
-        team_id: Optional[str],
-    ):
-        self.underlying.delete_all(
-            enterprise_id=enterprise_id,
-            team_id=team_id,
-        )
-        key_prefix = f"{enterprise_id or ''}-{team_id or ''}"
-        for key in list(self.cached_bots.keys()):
-            if key.startswith(key_prefix):
-                self.cached_bots.pop(key)
-        for key in list(self.cached_installations.keys()):
-            if key.startswith(key_prefix):
-                self.cached_installations.pop(key)
-

Ancestors

  • InstallationStore
  • @@ -345,22 +218,21 @@

    Class variables

    var cached_bots :ย Dict[str,ย Bot]
    -
    +

    The type of the None singleton.

    var cached_installations :ย Dict[str,ย Installation]
    -
    +

    The type of the None singleton.

    var underlying :ย InstallationStore
    -
    +

    The type of the None singleton.

    Instance variables

    -
    var logger :ย logging.Logger
    +
    prop logger :ย logging.Logger
    -
    Expand source code @@ -369,6 +241,7 @@

    Instance variables

    def logger(self) -> Logger: return self.underlying.logger
    +

    Inherited members

    @@ -390,7 +263,6 @@

    Inherited members

-
- -Expand source code - -
@classmethod
-def on(cls, *, event: str, callback: Callable):
-    """Stores and links the callback(s) to the event.
-
-    Args:
-        event (str): A string that specifies a Slack or websocket event.
-            e.g. 'channel_joined' or 'open'
-        callback (Callable): Any object or a list of objects that can be called.
-            e.g. <function say_hello at 0x101234567> or
-            [<function say_hello at 0x10123>,<function say_bye at 0x10456>]
-
-    Raises:
-        SlackClientError: The specified callback is not callable.
-        SlackClientError: The callback must accept keyword arguments (**kwargs).
-    """
-    if isinstance(callback, list):
-        for cb in callback:
-            cls._validate_callback(cb)
-        previous_callbacks = cls._callbacks[event]
-        cls._callbacks[event] = list(set(previous_callbacks + callback))
-    else:
-        cls._validate_callback(callback)
-        cls._callbacks[event].append(callback)
-
def run_on(*, event:ย str)
-

A decorator to store and link a callback to an event.

Expand source code @@ -1375,6 +733,7 @@

Raises

return decorator
+

A decorator to store and link a callback to an event.

Methods

@@ -1383,7 +742,6 @@

Methods

async def async_stop(self)
-

Closes the websocket connection and ensures it won't reconnect.

Expand source code @@ -1396,19 +754,12 @@

Methods

await future self._stopped = True
+

Closes the websocket connection and ensures it won't reconnect.

async def ping(self)
-

Sends a ping message over the websocket to Slack.

-

Not all web browsers support the WebSocket ping spec, -so the RTM protocol also supports ping/pong messages.

-

Raises

-
-
SlackClientNotConnectedError
-
Websocket connection is closed.
-
Expand source code @@ -1425,35 +776,19 @@

Raises

payload = {"id": self._next_msg_id(), "type": "ping"} await self._send_json(payload=payload)
-
-
-def send_over_websocket(self, *, payload:ย dict) -
-
-

Sends a message to Slack over the WebSocket connection.

-

Note

-

The RTM API only supports posting simple messages formatted using -our default message formatting mode. It does not support -attachments or other message formatting modes. For this reason -we recommend users send messages via the Web API methods. -e.g. web_client.chat_postMessage()

-

If the message "id" is not specified in the payload, it'll be added.

-

Args

-
-
payload : dict
-
The message to send over the wesocket.
-
-

e.g. -{ -"id": 1, -"type": "typing", -"channel": "C024BE91L" -}

+

Sends a ping message over the websocket to Slack.

+

Not all web browsers support the WebSocket ping spec, +so the RTM protocol also supports ping/pong messages.

Raises

SlackClientNotConnectedError
Websocket connection is closed.
+
+
+def send_over_websocket(self, *, payload:ย dict) +
+
Expand source code @@ -1484,24 +819,35 @@

Raises

""" return asyncio.ensure_future(self._send_json(payload), loop=self._event_loop)
+

Sends a message to Slack over the WebSocket connection.

+

Note

+

The RTM API only supports posting simple messages formatted using +our default message formatting mode. It does not support +attachments or other message formatting modes. For this reason +we recommend users send messages via the Web API methods. +e.g. web_client.chat_postMessage()

+

If the message "id" is not specified in the payload, it'll be added.

+

Args

+
+
payload : dict
+
The message to send over the wesocket.
+
+

e.g. +{ +"id": 1, +"type": "typing", +"channel": "C024BE91L" +}

+

Raises

+
+
SlackClientNotConnectedError
+
Websocket connection is closed.
+
-def start(self) โ€‘>ย Union[_asyncio.Future,ย Any] +def start(self) โ€‘>ย _asyncio.Futureย |ย Any
-

Starts an RTM Session with Slack.

-

Makes an authenticated call to Slack's RTM API to retrieve -a websocket URL and then connects to the message server. -As events stream-in we run any associated callbacks stored -on the client.

-

If 'auto_reconnect' is specified we -retrieve a new url and reconnect any time the connection -is lost unintentionally or an exception is thrown.

-

Raises

-
-
SlackApiError
-
Unable to retrieve RTM URL from Slack.
-
Expand source code @@ -1527,24 +873,30 @@

Raises

for s in signals: self._event_loop.add_signal_handler(s, self.stop) - future: Future[Any] = asyncio.ensure_future( - self._connect_and_read(), loop=self._event_loop - ) + future: Future[Any] = asyncio.ensure_future(self._connect_and_read(), loop=self._event_loop) if self.run_async: return future return self._event_loop.run_until_complete(future)
+

Starts an RTM Session with Slack.

+

Makes an authenticated call to Slack's RTM API to retrieve +a websocket URL and then connects to the message server. +As events stream-in we run any associated callbacks stored +on the client.

+

If 'auto_reconnect' is specified we +retrieve a new url and reconnect any time the connection +is lost unintentionally or an exception is thrown.

+

Raises

+
+
SlackApiError
+
Unable to retrieve RTM URL from Slack.
+
def stop(self)
-

Closes the websocket connection and ensures it won't reconnect.

-

If your application outputs the following errors, -call #async_stop() instead and await for the completion on your application side.

-

asyncio/base_events.py:641: RuntimeWarning: -coroutine 'ClientWebSocketResponse.close' was never awaited self._ready.clear()

Expand source code @@ -1562,24 +914,16 @@

Raises

self._stopped = True self._close_websocket()
+

Closes the websocket connection and ensures it won't reconnect.

+

If your application outputs the following errors, +call #async_stop() instead and await for the completion on your application side.

+

asyncio/base_events.py:641: RuntimeWarning: +coroutine 'ClientWebSocketResponse.close' was never awaited self._ready.clear()

async def typing(self, *, channel:ย str)
-

Sends a typing indicator to the specified channel.

-

This indicates that this app is currently -writing a message to send to a channel.

-

Args

-
-
channel : str
-
The channel id. e.g. 'C024BE91L'
-
-

Raises

-
-
SlackClientNotConnectedError
-
Websocket connection is closed.
-
Expand source code @@ -1599,6 +943,19 @@

Raises

payload = {"id": self._next_msg_id(), "type": "typing", "channel": channel} await self._send_json(payload=payload)
+

Sends a typing indicator to the specified channel.

+

This indicates that this app is currently +writing a message to send to a channel.

+

Args

+
+
channel : str
+
The channel id. e.g. 'C024BE91L'
+
+

Raises

+
+
SlackClientNotConnectedError
+
Websocket connection is closed.
+
@@ -1606,7 +963,6 @@

Raises

- \ No newline at end of file + diff --git a/docs/reference/rtm/v2/index.html b/docs/reference/rtm/v2/index.html new file mode 100644 index 000000000..d0c0591ec --- /dev/null +++ b/docs/reference/rtm/v2/index.html @@ -0,0 +1,990 @@ + + + + + + +slack_sdk.rtm.v2 API documentation + + + + + + + + + + + +
+
+
+

Module slack_sdk.rtm.v2

+
+
+
+
+
+
+
+
+
+
+

Classes

+
+
+class RTMClient +(*,
token:ย strย |ย Noneย =ย None,
web_client:ย WebClientย |ย Noneย =ย None,
auto_reconnect_enabled:ย boolย =ย True,
ssl:ย ssl.SSLContextย |ย Noneย =ย None,
proxy:ย strย |ย Noneย =ย None,
timeout:ย intย =ย 30,
base_url:ย strย =ย 'https://slack.com/api/',
headers:ย dictย |ย Noneย =ย None,
ping_interval:ย intย =ย 5,
concurrency:ย intย =ย 10,
logger:ย logging.Loggerย |ย Noneย =ย None,
on_message_listeners:ย List[Callable[[str],ย None]]ย |ย Noneย =ย None,
on_error_listeners:ย List[Callable[[Exception],ย None]]ย |ย Noneย =ย None,
on_close_listeners:ย List[Callable[[int,ย strย |ย None],ย None]]ย |ย Noneย =ย None,
trace_enabled:ย boolย =ย False,
all_message_trace_enabled:ย boolย =ย False,
ping_pong_trace_enabled:ย boolย =ย False)
+
+
+
+ +Expand source code + +
class RTMClient:
+    token: Optional[str]
+    bot_id: Optional[str]
+    default_auto_reconnect_enabled: bool
+    auto_reconnect_enabled: bool
+    ssl: Optional[SSLContext]
+    proxy: Optional[str]
+    timeout: int
+    base_url: str
+    ping_interval: int
+    logger: Logger
+    web_client: WebClient
+
+    current_session: Optional[Connection]
+    current_session_state: Optional[ConnectionState]
+    wss_uri: Optional[str]
+
+    message_queue: Queue
+    message_listeners: List[Callable[["RTMClient", dict], None]]
+    message_processor: IntervalRunner
+    message_workers: ThreadPoolExecutor
+
+    closed: bool
+    connect_operation_lock: Lock
+
+    on_message_listeners: List[Callable[[str], None]]
+    on_error_listeners: List[Callable[[Exception], None]]
+    on_close_listeners: List[Callable[[int, Optional[str]], None]]
+
+    def __init__(
+        self,
+        *,
+        token: Optional[str] = None,
+        web_client: Optional[WebClient] = None,
+        auto_reconnect_enabled: bool = True,
+        ssl: Optional[SSLContext] = None,
+        proxy: Optional[str] = None,
+        timeout: int = 30,
+        base_url: str = WebClient.BASE_URL,
+        headers: Optional[dict] = None,
+        ping_interval: int = 5,
+        concurrency: int = 10,
+        logger: Optional[logging.Logger] = None,
+        on_message_listeners: Optional[List[Callable[[str], None]]] = None,
+        on_error_listeners: Optional[List[Callable[[Exception], None]]] = None,
+        on_close_listeners: Optional[List[Callable[[int, Optional[str]], None]]] = None,
+        trace_enabled: bool = False,
+        all_message_trace_enabled: bool = False,
+        ping_pong_trace_enabled: bool = False,
+    ):
+        self.token = token.strip() if token is not None else None
+        self.bot_id = None
+        self.default_auto_reconnect_enabled = auto_reconnect_enabled
+        # You may want temporarily turn off the auto_reconnect as necessary
+        self.auto_reconnect_enabled = self.default_auto_reconnect_enabled
+        self.ssl = ssl
+        self.proxy = proxy
+        self.timeout = timeout
+        self.base_url = base_url
+        self.headers = headers
+        self.ping_interval = ping_interval
+        self.logger = logger or logging.getLogger(__name__)
+        if self.proxy is None or len(self.proxy.strip()) == 0:
+            env_variable = load_http_proxy_from_env(self.logger)
+            if env_variable is not None:
+                self.proxy = env_variable
+
+        self.web_client = web_client or WebClient(
+            token=self.token,
+            base_url=self.base_url,
+            timeout=self.timeout,
+            ssl=self.ssl,
+            proxy=self.proxy,
+            headers=self.headers,
+            logger=logger,
+        )
+
+        self.on_message_listeners = on_message_listeners or []
+
+        self.on_error_listeners = on_error_listeners or []
+        self.on_close_listeners = on_close_listeners or []
+
+        self.trace_enabled = trace_enabled
+        self.all_message_trace_enabled = all_message_trace_enabled
+        self.ping_pong_trace_enabled = ping_pong_trace_enabled
+
+        self.message_queue = Queue()
+
+        def goodbye_listener(_self, event: dict):
+            if event.get("type") == "goodbye":
+                message = "Got a goodbye message. Reconnecting to the server ..."
+                self.logger.info(message)
+                self.connect_to_new_endpoint(force=True)
+
+        self.message_listeners = [goodbye_listener]
+        self.socket_mode_request_listeners = []
+
+        self.current_session = None
+        self.current_session_state = ConnectionState()
+        self.current_session_runner = IntervalRunner(self._run_current_session, 0.1).start()
+        self.wss_uri = None
+
+        self.current_app_monitor_started = False
+        self.current_app_monitor = IntervalRunner(
+            self._monitor_current_session,
+            self.ping_interval,
+        )
+
+        self.closed = False
+        self.connect_operation_lock = Lock()
+
+        self.message_processor = IntervalRunner(self.process_messages, 0.001).start()
+        self.message_workers = ThreadPoolExecutor(max_workers=concurrency)
+
+    # --------------------------------------------------------------
+    # Decorator to register listeners
+    # --------------------------------------------------------------
+
+    def on(self, event_type: str) -> Callable:
+        """Registers a new event listener.
+
+        Args:
+            event_type: str representing an event's type (e.g., message, reaction_added)
+        """
+
+        def __call__(*args, **kwargs):
+            func = args[0]
+            if func is not None:
+                if isinstance(func, Callable):
+                    name = (
+                        func.__name__
+                        if hasattr(func, "__name__")
+                        else f"{func.__class__.__module__}.{func.__class__.__name__}"
+                    )
+                    inspect_result: inspect.FullArgSpec = inspect.getfullargspec(func)
+                    if inspect_result is not None and len(inspect_result.args) != 2:
+                        actual_args = ", ".join(inspect_result.args)
+                        error = f"The listener '{name}' must accept two args: client, event (actual: {actual_args})"
+                        raise SlackClientError(error)
+
+                    def new_message_listener(_self, event: dict):
+                        actual_event_type = event.get("type")
+                        if event.get("bot_id") == self.bot_id:
+                            # SKip the events generated by this bot user
+                            return
+                        # https://github.com/slackapi/python-slack-sdk/issues/533
+                        if event_type == "*" or (actual_event_type is not None and actual_event_type == event_type):
+                            func(_self, event)
+
+                    self.message_listeners.append(new_message_listener)
+                else:
+                    error = f"The listener '{func}' is not a Callable (actual: {type(func).__name__})"
+                    raise SlackClientError(error)
+            # Not to cause modification to the decorated method
+            return func
+
+        return __call__
+
+    # --------------------------------------------------------------
+    # Connections
+    # --------------------------------------------------------------
+
+    def is_connected(self) -> bool:
+        """Returns True if this client is connected."""
+        return self.current_session is not None and self.current_session.is_active()
+
+    def issue_new_wss_url(self) -> str:
+        """Acquires a new WSS URL using rtm.connect API method"""
+        try:
+            api_response = self.web_client.rtm_connect()
+            return api_response["url"]
+        except SlackApiError as e:
+            if e.response["error"] == "ratelimited":
+                delay = int(e.response.headers.get("Retry-After", "30"))  # Tier1
+                self.logger.info(f"Rate limited. Retrying in {delay} seconds...")
+                time.sleep(delay)
+                # Retry to issue a new WSS URL
+                return self.issue_new_wss_url()
+            else:
+                # other errors
+                self.logger.error(f"Failed to retrieve WSS URL: {e}")
+                raise e
+
+    def connect_to_new_endpoint(self, force: bool = False):
+        """Acquires a new WSS URL and tries to connect to the endpoint."""
+        with self.connect_operation_lock:
+            if force or not self.is_connected():
+                self.logger.info("Connecting to a new endpoint...")
+                self.wss_uri = self.issue_new_wss_url()
+                self.connect()
+                self.logger.info("Connected to a new endpoint...")
+
+    def connect(self):
+        """Starts talking to the RTM server through a WebSocket connection"""
+        if self.bot_id is None:
+            self.bot_id = self.web_client.auth_test()["bot_id"]
+
+        old_session: Optional[Connection] = self.current_session
+        old_current_session_state: ConnectionState = self.current_session_state
+
+        if self.wss_uri is None:
+            self.wss_uri = self.issue_new_wss_url()
+
+        current_session = Connection(
+            url=self.wss_uri,
+            logger=self.logger,
+            ping_interval=self.ping_interval,
+            trace_enabled=self.trace_enabled,
+            all_message_trace_enabled=self.all_message_trace_enabled,
+            ping_pong_trace_enabled=self.ping_pong_trace_enabled,
+            receive_buffer_size=1024,
+            proxy=self.proxy,
+            on_message_listener=self.run_all_message_listeners,
+            on_error_listener=self.run_all_error_listeners,
+            on_close_listener=self.run_all_close_listeners,
+            connection_type_name="RTM",
+        )
+        current_session.connect()
+
+        if old_current_session_state is not None:
+            old_current_session_state.terminated = True
+        if old_session is not None:
+            old_session.close()
+
+        self.current_session = current_session
+        self.current_session_state = ConnectionState()
+        self.auto_reconnect_enabled = self.default_auto_reconnect_enabled
+
+        if not self.current_app_monitor_started:
+            self.current_app_monitor_started = True
+            self.current_app_monitor.start()
+
+        self.logger.info(f"A new session has been established (session id: {self.session_id()})")
+
+    def disconnect(self):
+        """Disconnects the current session."""
+        self.current_session.disconnect()
+
+    def close(self) -> None:
+        """
+        Closes this instance and cleans up underlying resources.
+        After calling this method, this instance is no longer usable.
+        """
+        self.closed = True
+        self.disconnect()
+        self.current_session.close()
+
+    def start(self) -> None:
+        """Establishes an RTM connection and blocks the current thread."""
+        self.connect()
+        Event().wait()
+
+    def send(self, payload: Union[dict, str]) -> None:
+        if payload is None:
+            return
+        if self.current_session is None or not self.current_session.is_active():
+            raise SlackClientError("The RTM client is not connected to the Slack servers")
+        if isinstance(payload, str):
+            self.current_session.send(payload)
+        else:
+            self.current_session.send(json.dumps(payload))
+
+    # --------------------------------------------------------------
+    # WS Message Processor
+    # --------------------------------------------------------------
+
+    def enqueue_message(self, message: str):
+        self.message_queue.put(message)
+        if self.logger.level <= logging.DEBUG:
+            self.logger.debug(f"A new message enqueued (current queue size: {self.message_queue.qsize()})")
+
+    def process_message(self):
+        try:
+            raw_message = self.message_queue.get(timeout=1)
+            if self.logger.level <= logging.DEBUG:
+                self.logger.debug(f"A message dequeued (current queue size: {self.message_queue.qsize()})")
+
+            if raw_message is not None:
+                message: dict = {}
+                if raw_message.startswith("{"):
+                    message = json.loads(raw_message)
+
+                def _run_message_listeners():
+                    self.run_message_listeners(message)
+
+                self.message_workers.submit(_run_message_listeners)
+        except Empty:
+            pass
+
+    def process_messages(self) -> None:
+        while not self.closed:
+            try:
+                self.process_message()
+            except Exception as e:
+                self.logger.exception(f"Failed to process a message: {e}")
+
+    def run_message_listeners(self, message: dict) -> None:
+        type = message.get("type")
+        if self.logger.level <= logging.DEBUG:
+            self.logger.debug(f"Message processing started (type: {type})")
+        try:
+            for listener in self.message_listeners:
+                try:
+                    listener(self, message)
+                except Exception as e:
+                    self.logger.exception(f"Failed to run a message listener: {e}")
+        except Exception as e:
+            self.logger.exception(f"Failed to run message listeners: {e}")
+        finally:
+            if self.logger.level <= logging.DEBUG:
+                self.logger.debug(f"Message processing completed (type: {type})")
+
+    # --------------------------------------------------------------
+    # Internals
+    # --------------------------------------------------------------
+
+    def session_id(self) -> Optional[str]:
+        if self.current_session is not None:
+            return self.current_session.session_id
+        return None
+
+    def run_all_message_listeners(self, message: str):
+        if self.logger.level <= logging.DEBUG:
+            self.logger.debug(f"on_message invoked: (message: {message})")
+        self.enqueue_message(message)
+        for listener in self.on_message_listeners:
+            listener(message)
+
+    def run_all_error_listeners(self, error: Exception):
+        self.logger.exception(
+            f"on_error invoked (session id: {self.session_id()}, " f"error: {type(error).__name__}, message: {error})"
+        )
+        for listener in self.on_error_listeners:
+            listener(error)
+
+    def run_all_close_listeners(self, code: int, reason: Optional[str] = None):
+        if self.logger.level <= logging.DEBUG:
+            self.logger.debug(f"on_close invoked (session id: {self.session_id()})")
+        if self.auto_reconnect_enabled:
+            self.logger.info("Received CLOSE event. Going to reconnect... " f"(session id: {self.session_id()})")
+            self.connect_to_new_endpoint()
+        for listener in self.on_close_listeners:
+            listener(code, reason)
+
+    def _run_current_session(self):
+        if self.current_session is not None and self.current_session.is_active():
+            session_id = self.session_id()
+            try:
+                self.logger.info("Starting to receive messages from a new connection" f" (session id: {session_id})")
+                self.current_session_state.terminated = False
+                self.current_session.run_until_completion(self.current_session_state)
+                self.logger.info("Stopped receiving messages from a connection" f" (session id: {session_id})")
+            except Exception as e:
+                self.logger.exception(
+                    "Failed to start or stop the current session" f" (session id: {session_id}, error: {e})"
+                )
+
+    def _monitor_current_session(self):
+        if self.current_app_monitor_started:
+            try:
+                self.current_session.check_state()
+
+                if self.auto_reconnect_enabled and (self.current_session is None or not self.current_session.is_active()):
+                    self.logger.info(
+                        "The session seems to be already closed. Going to reconnect... " f"(session id: {self.session_id()})"
+                    )
+                    self.connect_to_new_endpoint()
+            except Exception as e:
+                self.logger.error(
+                    "Failed to check the current session or reconnect to the server "
+                    f"(session id: {self.session_id()}, error: {type(e).__name__}, message: {e})"
+                )
+
+
+

Class variables

+
+
var auto_reconnect_enabled :ย bool
+
+

The type of the None singleton.

+
+
var base_url :ย str
+
+

The type of the None singleton.

+
+
var bot_id :ย strย |ย None
+
+

The type of the None singleton.

+
+
var closed :ย bool
+
+

The type of the None singleton.

+
+
var connect_operation_lock :ย _thread.lock
+
+

The type of the None singleton.

+
+
var current_session :ย Connectionย |ย None
+
+

The type of the None singleton.

+
+
var current_session_state :ย ConnectionStateย |ย None
+
+

The type of the None singleton.

+
+
var default_auto_reconnect_enabled :ย bool
+
+

The type of the None singleton.

+
+
var logger :ย logging.Logger
+
+

The type of the None singleton.

+
+
var message_listeners :ย List[Callable[[RTMClient,ย dict],ย None]]
+
+

The type of the None singleton.

+
+
var message_processor :ย IntervalRunner
+
+

The type of the None singleton.

+
+
var message_queue :ย queue.Queue
+
+

The type of the None singleton.

+
+
var message_workers :ย concurrent.futures.thread.ThreadPoolExecutor
+
+

The type of the None singleton.

+
+
var on_close_listeners :ย List[Callable[[int,ย strย |ย None],ย None]]
+
+

The type of the None singleton.

+
+
var on_error_listeners :ย List[Callable[[Exception],ย None]]
+
+

The type of the None singleton.

+
+
var on_message_listeners :ย List[Callable[[str],ย None]]
+
+

The type of the None singleton.

+
+
var ping_interval :ย int
+
+

The type of the None singleton.

+
+
var proxy :ย strย |ย None
+
+

The type of the None singleton.

+
+
var ssl :ย ssl.SSLContextย |ย None
+
+

The type of the None singleton.

+
+
var timeout :ย int
+
+

The type of the None singleton.

+
+
var token :ย strย |ย None
+
+

The type of the None singleton.

+
+
var web_client :ย WebClient
+
+

The type of the None singleton.

+
+
var wss_uri :ย strย |ย None
+
+

The type of the None singleton.

+
+
+

Methods

+
+
+def close(self) โ€‘>ย None +
+
+
+ +Expand source code + +
def close(self) -> None:
+    """
+    Closes this instance and cleans up underlying resources.
+    After calling this method, this instance is no longer usable.
+    """
+    self.closed = True
+    self.disconnect()
+    self.current_session.close()
+
+

Closes this instance and cleans up underlying resources. +After calling this method, this instance is no longer usable.

+
+
+def connect(self) +
+
+
+ +Expand source code + +
def connect(self):
+    """Starts talking to the RTM server through a WebSocket connection"""
+    if self.bot_id is None:
+        self.bot_id = self.web_client.auth_test()["bot_id"]
+
+    old_session: Optional[Connection] = self.current_session
+    old_current_session_state: ConnectionState = self.current_session_state
+
+    if self.wss_uri is None:
+        self.wss_uri = self.issue_new_wss_url()
+
+    current_session = Connection(
+        url=self.wss_uri,
+        logger=self.logger,
+        ping_interval=self.ping_interval,
+        trace_enabled=self.trace_enabled,
+        all_message_trace_enabled=self.all_message_trace_enabled,
+        ping_pong_trace_enabled=self.ping_pong_trace_enabled,
+        receive_buffer_size=1024,
+        proxy=self.proxy,
+        on_message_listener=self.run_all_message_listeners,
+        on_error_listener=self.run_all_error_listeners,
+        on_close_listener=self.run_all_close_listeners,
+        connection_type_name="RTM",
+    )
+    current_session.connect()
+
+    if old_current_session_state is not None:
+        old_current_session_state.terminated = True
+    if old_session is not None:
+        old_session.close()
+
+    self.current_session = current_session
+    self.current_session_state = ConnectionState()
+    self.auto_reconnect_enabled = self.default_auto_reconnect_enabled
+
+    if not self.current_app_monitor_started:
+        self.current_app_monitor_started = True
+        self.current_app_monitor.start()
+
+    self.logger.info(f"A new session has been established (session id: {self.session_id()})")
+
+

Starts talking to the RTM server through a WebSocket connection

+
+
+def connect_to_new_endpoint(self, force:ย boolย =ย False) +
+
+
+ +Expand source code + +
def connect_to_new_endpoint(self, force: bool = False):
+    """Acquires a new WSS URL and tries to connect to the endpoint."""
+    with self.connect_operation_lock:
+        if force or not self.is_connected():
+            self.logger.info("Connecting to a new endpoint...")
+            self.wss_uri = self.issue_new_wss_url()
+            self.connect()
+            self.logger.info("Connected to a new endpoint...")
+
+

Acquires a new WSS URL and tries to connect to the endpoint.

+
+
+def disconnect(self) +
+
+
+ +Expand source code + +
def disconnect(self):
+    """Disconnects the current session."""
+    self.current_session.disconnect()
+
+

Disconnects the current session.

+
+
+def enqueue_message(self, message:ย str) +
+
+
+ +Expand source code + +
def enqueue_message(self, message: str):
+    self.message_queue.put(message)
+    if self.logger.level <= logging.DEBUG:
+        self.logger.debug(f"A new message enqueued (current queue size: {self.message_queue.qsize()})")
+
+
+
+
+def is_connected(self) โ€‘>ย bool +
+
+
+ +Expand source code + +
def is_connected(self) -> bool:
+    """Returns True if this client is connected."""
+    return self.current_session is not None and self.current_session.is_active()
+
+

Returns True if this client is connected.

+
+
+def issue_new_wss_url(self) โ€‘>ย str +
+
+
+ +Expand source code + +
def issue_new_wss_url(self) -> str:
+    """Acquires a new WSS URL using rtm.connect API method"""
+    try:
+        api_response = self.web_client.rtm_connect()
+        return api_response["url"]
+    except SlackApiError as e:
+        if e.response["error"] == "ratelimited":
+            delay = int(e.response.headers.get("Retry-After", "30"))  # Tier1
+            self.logger.info(f"Rate limited. Retrying in {delay} seconds...")
+            time.sleep(delay)
+            # Retry to issue a new WSS URL
+            return self.issue_new_wss_url()
+        else:
+            # other errors
+            self.logger.error(f"Failed to retrieve WSS URL: {e}")
+            raise e
+
+

Acquires a new WSS URL using rtm.connect API method

+
+
+def on(self, event_type:ย str) โ€‘>ย Callable +
+
+
+ +Expand source code + +
def on(self, event_type: str) -> Callable:
+    """Registers a new event listener.
+
+    Args:
+        event_type: str representing an event's type (e.g., message, reaction_added)
+    """
+
+    def __call__(*args, **kwargs):
+        func = args[0]
+        if func is not None:
+            if isinstance(func, Callable):
+                name = (
+                    func.__name__
+                    if hasattr(func, "__name__")
+                    else f"{func.__class__.__module__}.{func.__class__.__name__}"
+                )
+                inspect_result: inspect.FullArgSpec = inspect.getfullargspec(func)
+                if inspect_result is not None and len(inspect_result.args) != 2:
+                    actual_args = ", ".join(inspect_result.args)
+                    error = f"The listener '{name}' must accept two args: client, event (actual: {actual_args})"
+                    raise SlackClientError(error)
+
+                def new_message_listener(_self, event: dict):
+                    actual_event_type = event.get("type")
+                    if event.get("bot_id") == self.bot_id:
+                        # SKip the events generated by this bot user
+                        return
+                    # https://github.com/slackapi/python-slack-sdk/issues/533
+                    if event_type == "*" or (actual_event_type is not None and actual_event_type == event_type):
+                        func(_self, event)
+
+                self.message_listeners.append(new_message_listener)
+            else:
+                error = f"The listener '{func}' is not a Callable (actual: {type(func).__name__})"
+                raise SlackClientError(error)
+        # Not to cause modification to the decorated method
+        return func
+
+    return __call__
+
+

Registers a new event listener.

+

Args

+
+
event_type
+
str representing an event's type (e.g., message, reaction_added)
+
+
+
+def process_message(self) +
+
+
+ +Expand source code + +
def process_message(self):
+    try:
+        raw_message = self.message_queue.get(timeout=1)
+        if self.logger.level <= logging.DEBUG:
+            self.logger.debug(f"A message dequeued (current queue size: {self.message_queue.qsize()})")
+
+        if raw_message is not None:
+            message: dict = {}
+            if raw_message.startswith("{"):
+                message = json.loads(raw_message)
+
+            def _run_message_listeners():
+                self.run_message_listeners(message)
+
+            self.message_workers.submit(_run_message_listeners)
+    except Empty:
+        pass
+
+
+
+
+def process_messages(self) โ€‘>ย None +
+
+
+ +Expand source code + +
def process_messages(self) -> None:
+    while not self.closed:
+        try:
+            self.process_message()
+        except Exception as e:
+            self.logger.exception(f"Failed to process a message: {e}")
+
+
+
+
+def run_all_close_listeners(self, code:ย int, reason:ย strย |ย Noneย =ย None) +
+
+
+ +Expand source code + +
def run_all_close_listeners(self, code: int, reason: Optional[str] = None):
+    if self.logger.level <= logging.DEBUG:
+        self.logger.debug(f"on_close invoked (session id: {self.session_id()})")
+    if self.auto_reconnect_enabled:
+        self.logger.info("Received CLOSE event. Going to reconnect... " f"(session id: {self.session_id()})")
+        self.connect_to_new_endpoint()
+    for listener in self.on_close_listeners:
+        listener(code, reason)
+
+
+
+
+def run_all_error_listeners(self, error:ย Exception) +
+
+
+ +Expand source code + +
def run_all_error_listeners(self, error: Exception):
+    self.logger.exception(
+        f"on_error invoked (session id: {self.session_id()}, " f"error: {type(error).__name__}, message: {error})"
+    )
+    for listener in self.on_error_listeners:
+        listener(error)
+
+
+
+
+def run_all_message_listeners(self, message:ย str) +
+
+
+ +Expand source code + +
def run_all_message_listeners(self, message: str):
+    if self.logger.level <= logging.DEBUG:
+        self.logger.debug(f"on_message invoked: (message: {message})")
+    self.enqueue_message(message)
+    for listener in self.on_message_listeners:
+        listener(message)
+
+
+
+
+def run_message_listeners(self, message:ย dict) โ€‘>ย None +
+
+
+ +Expand source code + +
def run_message_listeners(self, message: dict) -> None:
+    type = message.get("type")
+    if self.logger.level <= logging.DEBUG:
+        self.logger.debug(f"Message processing started (type: {type})")
+    try:
+        for listener in self.message_listeners:
+            try:
+                listener(self, message)
+            except Exception as e:
+                self.logger.exception(f"Failed to run a message listener: {e}")
+    except Exception as e:
+        self.logger.exception(f"Failed to run message listeners: {e}")
+    finally:
+        if self.logger.level <= logging.DEBUG:
+            self.logger.debug(f"Message processing completed (type: {type})")
+
+
+
+
+def send(self, payload:ย dictย |ย str) โ€‘>ย None +
+
+
+ +Expand source code + +
def send(self, payload: Union[dict, str]) -> None:
+    if payload is None:
+        return
+    if self.current_session is None or not self.current_session.is_active():
+        raise SlackClientError("The RTM client is not connected to the Slack servers")
+    if isinstance(payload, str):
+        self.current_session.send(payload)
+    else:
+        self.current_session.send(json.dumps(payload))
+
+
+
+
+def session_id(self) โ€‘>ย strย |ย None +
+
+
+ +Expand source code + +
def session_id(self) -> Optional[str]:
+    if self.current_session is not None:
+        return self.current_session.session_id
+    return None
+
+
+
+
+def start(self) โ€‘>ย None +
+
+
+ +Expand source code + +
def start(self) -> None:
+    """Establishes an RTM connection and blocks the current thread."""
+    self.connect()
+    Event().wait()
+
+

Establishes an RTM connection and blocks the current thread.

+
+
+
+
+
+
+ +
+ + + diff --git a/docs/api-docs/slack_sdk/rtm_v2/index.html b/docs/reference/rtm_v2/index.html similarity index 60% rename from docs/api-docs/slack_sdk/rtm_v2/index.html rename to docs/reference/rtm_v2/index.html index 9bcf169cb..5d65bdc74 100644 --- a/docs/api-docs/slack_sdk/rtm_v2/index.html +++ b/docs/reference/rtm_v2/index.html @@ -2,18 +2,32 @@ - - + + slack_sdk.rtm_v2 API documentation - - - - - - + + + + + + - - + +
@@ -23,429 +37,6 @@

Module slack_sdk.rtm_v2

A Python module for interacting with Slack's RTM API.

-
- -Expand source code - -
"""A Python module for interacting with Slack's RTM API."""
-import inspect
-import json
-import logging
-import time
-from concurrent.futures.thread import ThreadPoolExecutor
-from logging import Logger
-from queue import Queue, Empty
-from ssl import SSLContext
-from threading import Lock, Event
-from typing import Optional, Callable, List, Union
-
-from slack_sdk.errors import SlackApiError, SlackClientError
-from slack_sdk.proxy_env_variable_loader import load_http_proxy_from_env
-from slack_sdk.socket_mode.builtin.connection import Connection, ConnectionState
-from slack_sdk.socket_mode.interval_runner import IntervalRunner
-from slack_sdk.web import WebClient
-
-
-class RTMClient:
-    token: Optional[str]
-    bot_id: Optional[str]
-    default_auto_reconnect_enabled: bool
-    auto_reconnect_enabled: bool
-    ssl: Optional[SSLContext]
-    proxy: str
-    timeout: int
-    base_url: str
-    ping_interval: int
-    logger: Logger
-    web_client: WebClient
-
-    current_session: Optional[Connection]
-    current_session_state: Optional[ConnectionState]
-    wss_uri: Optional[str]
-
-    message_queue: Queue
-    message_listeners: List[Callable[["RTMClient", dict], None]]
-    message_processor: IntervalRunner
-    message_workers: ThreadPoolExecutor
-
-    closed: bool
-    connect_operation_lock: Lock
-
-    on_message_listeners: List[Callable[[str], None]]
-    on_error_listeners: List[Callable[[Exception], None]]
-    on_close_listeners: List[Callable[[int, Optional[str]], None]]
-
-    def __init__(
-        self,
-        *,
-        token: Optional[str] = None,
-        web_client: Optional[WebClient] = None,
-        auto_reconnect_enabled: bool = True,
-        ssl: Optional[SSLContext] = None,
-        proxy: Optional[str] = None,
-        timeout: int = 30,
-        base_url: str = WebClient.BASE_URL,
-        headers: Optional[dict] = None,
-        ping_interval: int = 5,
-        concurrency: int = 10,
-        logger: Optional[logging.Logger] = None,
-        on_message_listeners: Optional[List[Callable[[str], None]]] = None,
-        on_error_listeners: Optional[List[Callable[[Exception], None]]] = None,
-        on_close_listeners: Optional[List[Callable[[int, Optional[str]], None]]] = None,
-        trace_enabled: bool = False,
-        all_message_trace_enabled: bool = False,
-        ping_pong_trace_enabled: bool = False,
-    ):
-        self.token = token.strip() if token is not None else None
-        self.bot_id = None
-        self.default_auto_reconnect_enabled = auto_reconnect_enabled
-        # You may want temporarily turn off the auto_reconnect as necessary
-        self.auto_reconnect_enabled = self.default_auto_reconnect_enabled
-        self.ssl = ssl
-        self.proxy = proxy
-        self.timeout = timeout
-        self.base_url = base_url
-        self.headers = headers
-        self.ping_interval = ping_interval
-        self.logger = logger or logging.getLogger(__name__)
-        if self.proxy is None or len(self.proxy.strip()) == 0:
-            env_variable = load_http_proxy_from_env(self.logger)
-            if env_variable is not None:
-                self.proxy = env_variable
-
-        self.web_client = web_client or WebClient(
-            token=self.token,
-            base_url=self.base_url,
-            timeout=self.timeout,
-            ssl=self.ssl,
-            proxy=self.proxy,
-            headers=self.headers,
-            logger=logger,
-        )
-
-        self.on_message_listeners = on_message_listeners or []
-
-        self.on_error_listeners = on_error_listeners or []
-        self.on_close_listeners = on_close_listeners or []
-
-        self.trace_enabled = trace_enabled
-        self.all_message_trace_enabled = all_message_trace_enabled
-        self.ping_pong_trace_enabled = ping_pong_trace_enabled
-
-        self.message_queue = Queue()
-
-        def goodbye_listener(_self, event: dict):
-            if event.get("type") == "goodbye":
-                message = "Got a goodbye message. Reconnecting to the server ..."
-                self.logger.info(message)
-                self.connect_to_new_endpoint(force=True)
-
-        self.message_listeners = [goodbye_listener]
-        self.socket_mode_request_listeners = []
-
-        self.current_session = None
-        self.current_session_state = ConnectionState()
-        self.current_session_runner = IntervalRunner(
-            self._run_current_session, 0.1
-        ).start()
-        self.wss_uri = None
-
-        self.current_app_monitor_started = False
-        self.current_app_monitor = IntervalRunner(
-            self._monitor_current_session,
-            self.ping_interval,
-        )
-
-        self.closed = False
-        self.connect_operation_lock = Lock()
-
-        self.message_processor = IntervalRunner(self.process_messages, 0.001).start()
-        self.message_workers = ThreadPoolExecutor(max_workers=concurrency)
-
-    # --------------------------------------------------------------
-    # Decorator to register listeners
-    # --------------------------------------------------------------
-
-    def on(self, event_type: str) -> Callable:
-        """Registers a new event listener.
-
-        Args:
-            event_type: str representing an event's type (e.g., message, reaction_added)
-        """
-
-        def __call__(*args, **kwargs):
-            func = args[0]
-            if func is not None:
-                if isinstance(func, Callable):
-                    name = (
-                        func.__name__
-                        if hasattr(func, "__name__")
-                        else f"{func.__class__.__module__}.{func.__class__.__name__}"
-                    )
-                    inspect_result: inspect.FullArgSpec = inspect.getfullargspec(func)
-                    if inspect_result is not None and len(inspect_result.args) != 2:
-                        actual_args = ", ".join(inspect_result.args)
-                        error = f"The listener '{name}' must accept two args: client, event (actual: {actual_args})"
-                        raise SlackClientError(error)
-
-                    def new_message_listener(_self, event: dict):
-                        actual_event_type = event.get("type")
-                        if event.get("bot_id") == self.bot_id:
-                            # SKip the events generated by this bot user
-                            return
-                        # https://github.com/slackapi/python-slack-sdk/issues/533
-                        if event_type == "*" or (
-                            actual_event_type is not None
-                            and actual_event_type == event_type
-                        ):
-                            func(_self, event)
-
-                    self.message_listeners.append(new_message_listener)
-                else:
-                    error = f"The listener '{func}' is not a Callable (actual: {type(func).__name__})"
-                    raise SlackClientError(error)
-            # Not to cause modification to the decorated method
-            return func
-
-        return __call__
-
-    # --------------------------------------------------------------
-    # Connections
-    # --------------------------------------------------------------
-
-    def is_connected(self) -> bool:
-        """Returns True if this client is connected."""
-        return self.current_session is not None and self.current_session.is_active()
-
-    def issue_new_wss_url(self) -> str:
-        """Acquires a new WSS URL using rtm.connect API method"""
-        try:
-            api_response = self.web_client.rtm_connect()
-            return api_response["url"]
-        except SlackApiError as e:
-            if e.response["error"] == "ratelimited":
-                delay = int(e.response.headers.get("Retry-After", "30"))  # Tier1
-                self.logger.info(f"Rate limited. Retrying in {delay} seconds...")
-                time.sleep(delay)
-                # Retry to issue a new WSS URL
-                return self.issue_new_wss_url()
-            else:
-                # other errors
-                self.logger.error(f"Failed to retrieve WSS URL: {e}")
-                raise e
-
-    def connect_to_new_endpoint(self, force: bool = False):
-        """Acquires a new WSS URL and tries to connect to the endpoint."""
-        with self.connect_operation_lock:
-            if force or not self.is_connected():
-                self.logger.info("Connecting to a new endpoint...")
-                self.wss_uri = self.issue_new_wss_url()
-                self.connect()
-                self.logger.info("Connected to a new endpoint...")
-
-    def connect(self):
-        """Starts talking to the RTM server through a WebSocket connection"""
-        if self.bot_id is None:
-            self.bot_id = self.web_client.auth_test()["bot_id"]
-
-        old_session: Optional[Connection] = self.current_session
-        old_current_session_state: ConnectionState = self.current_session_state
-
-        if self.wss_uri is None:
-            self.wss_uri = self.issue_new_wss_url()
-
-        current_session = Connection(
-            url=self.wss_uri,
-            logger=self.logger,
-            ping_interval=self.ping_interval,
-            trace_enabled=self.trace_enabled,
-            all_message_trace_enabled=self.all_message_trace_enabled,
-            ping_pong_trace_enabled=self.ping_pong_trace_enabled,
-            receive_buffer_size=1024,
-            proxy=self.proxy,
-            on_message_listener=self.run_all_message_listeners,
-            on_error_listener=self.run_all_error_listeners,
-            on_close_listener=self.run_all_close_listeners,
-            connection_type_name="RTM",
-        )
-        current_session.connect()
-
-        if old_current_session_state is not None:
-            old_current_session_state.terminated = True
-        if old_session is not None:
-            old_session.close()
-
-        self.current_session = current_session
-        self.current_session_state = ConnectionState()
-        self.auto_reconnect_enabled = self.default_auto_reconnect_enabled
-
-        if not self.current_app_monitor_started:
-            self.current_app_monitor_started = True
-            self.current_app_monitor.start()
-
-        self.logger.info(
-            f"A new session has been established (session id: {self.session_id()})"
-        )
-
-    def disconnect(self):
-        """Disconnects the current session."""
-        self.current_session.disconnect()
-
-    def close(self) -> None:
-        """
-        Closes this instance and cleans up underlying resources.
-        After calling this method, this instance is no longer usable.
-        """
-        self.closed = True
-        self.disconnect()
-        self.current_session.close()
-
-    def start(self) -> None:
-        """Establishes an RTM connection and blocks the current thread."""
-        self.connect()
-        Event().wait()
-
-    def send(self, payload: Union[dict, str]) -> None:
-        if payload is None:
-            return
-        if self.current_session is None or not self.current_session.is_active():
-            raise SlackClientError(
-                "The RTM client is not connected to the Slack servers"
-            )
-        if isinstance(payload, str):
-            self.current_session.send(payload)
-        else:
-            self.current_session.send(json.dumps(payload))
-
-    # --------------------------------------------------------------
-    # WS Message Processor
-    # --------------------------------------------------------------
-
-    def enqueue_message(self, message: str):
-        self.message_queue.put(message)
-        if self.logger.level <= logging.DEBUG:
-            self.logger.debug(
-                f"A new message enqueued (current queue size: {self.message_queue.qsize()})"
-            )
-
-    def process_message(self):
-        try:
-            raw_message = self.message_queue.get(timeout=1)
-            if self.logger.level <= logging.DEBUG:
-                self.logger.debug(
-                    f"A message dequeued (current queue size: {self.message_queue.qsize()})"
-                )
-
-            if raw_message is not None:
-                message: dict = {}
-                if raw_message.startswith("{"):
-                    message = json.loads(raw_message)
-
-                def _run_message_listeners():
-                    self.run_message_listeners(message)
-
-                self.message_workers.submit(_run_message_listeners)
-        except Empty:
-            pass
-
-    def process_messages(self) -> None:
-        while not self.closed:
-            try:
-                self.process_message()
-            except Exception as e:
-                self.logger.exception(f"Failed to process a message: {e}")
-
-    def run_message_listeners(self, message: dict) -> None:
-        type = message.get("type")
-        if self.logger.level <= logging.DEBUG:
-            self.logger.debug(f"Message processing started (type: {type})")
-        try:
-            for listener in self.message_listeners:
-                try:
-                    listener(self, message)
-                except Exception as e:
-                    self.logger.exception(f"Failed to run a message listener: {e}")
-        except Exception as e:
-            self.logger.exception(f"Failed to run message listeners: {e}")
-        finally:
-            if self.logger.level <= logging.DEBUG:
-                self.logger.debug(f"Message processing completed (type: {type})")
-
-    # --------------------------------------------------------------
-    # Internals
-    # --------------------------------------------------------------
-
-    def session_id(self) -> Optional[str]:
-        if self.current_session is not None:
-            return self.current_session.session_id
-        return None
-
-    def run_all_message_listeners(self, message: str):
-        if self.logger.level <= logging.DEBUG:
-            self.logger.debug(f"on_message invoked: (message: {message})")
-        self.enqueue_message(message)
-        for listener in self.on_message_listeners:
-            listener(message)
-
-    def run_all_error_listeners(self, error: Exception):
-        self.logger.exception(
-            f"on_error invoked (session id: {self.session_id()}, "
-            f"error: {type(error).__name__}, message: {error})"
-        )
-        for listener in self.on_error_listeners:
-            listener(error)
-
-    def run_all_close_listeners(self, code: int, reason: Optional[str] = None):
-        if self.logger.level <= logging.DEBUG:
-            self.logger.debug(f"on_close invoked (session id: {self.session_id()})")
-        if self.auto_reconnect_enabled:
-            self.logger.info(
-                "Received CLOSE event. Going to reconnect... "
-                f"(session id: {self.session_id()})"
-            )
-            self.connect_to_new_endpoint()
-        for listener in self.on_close_listeners:
-            listener(code, reason)
-
-    def _run_current_session(self):
-        if self.current_session is not None and self.current_session.is_active():
-            session_id = self.session_id()
-            try:
-                self.logger.info(
-                    "Starting to receive messages from a new connection"
-                    f" (session id: {session_id})"
-                )
-                self.current_session_state.terminated = False
-                self.current_session.run_until_completion(self.current_session_state)
-                self.logger.info(
-                    "Stopped receiving messages from a connection"
-                    f" (session id: {session_id})"
-                )
-            except Exception as e:
-                self.logger.exception(
-                    "Failed to start or stop the current session"
-                    f" (session id: {session_id}, error: {e})"
-                )
-
-    def _monitor_current_session(self):
-        if self.current_app_monitor_started:
-            try:
-                self.current_session.check_state()
-
-                if self.auto_reconnect_enabled and (
-                    self.current_session is None or not self.current_session.is_active()
-                ):
-                    self.logger.info(
-                        "The session seems to be already closed. Going to reconnect... "
-                        f"(session id: {self.session_id()})"
-                    )
-                    self.connect_to_new_endpoint()
-            except Exception as e:
-                self.logger.error(
-                    "Failed to check the current session or reconnect to the server "
-                    f"(session id: {self.session_id()}, error: {type(e).__name__}, message: {e})"
-                )
-
@@ -458,10 +49,9 @@

Classes

class RTMClient -(*, token:ย Optional[str]ย =ย None, web_client:ย Optional[WebClient]ย =ย None, auto_reconnect_enabled:ย boolย =ย True, ssl:ย Optional[ssl.SSLContext]ย =ย None, proxy:ย Optional[str]ย =ย None, timeout:ย intย =ย 30, base_url:ย strย =ย 'https://www.slack.com/api/', headers:ย Optional[dict]ย =ย None, ping_interval:ย intย =ย 5, concurrency:ย intย =ย 10, logger:ย Optional[logging.Logger]ย =ย None, on_message_listeners:ย Optional[List[Callable[[str],ย None]]]ย =ย None, on_error_listeners:ย Optional[List[Callable[[Exception],ย None]]]ย =ย None, on_close_listeners:ย Optional[List[Callable[[int,ย Optional[str]],ย None]]]ย =ย None, trace_enabled:ย boolย =ย False, all_message_trace_enabled:ย boolย =ย False, ping_pong_trace_enabled:ย boolย =ย False) +(*,
token:ย strย |ย Noneย =ย None,
web_client:ย WebClientย |ย Noneย =ย None,
auto_reconnect_enabled:ย boolย =ย True,
ssl:ย ssl.SSLContextย |ย Noneย =ย None,
proxy:ย strย |ย Noneย =ย None,
timeout:ย intย =ย 30,
base_url:ย strย =ย 'https://slack.com/api/',
headers:ย dictย |ย Noneย =ย None,
ping_interval:ย intย =ย 5,
concurrency:ย intย =ย 10,
logger:ย logging.Loggerย |ย Noneย =ย None,
on_message_listeners:ย List[Callable[[str],ย None]]ย |ย Noneย =ย None,
on_error_listeners:ย List[Callable[[Exception],ย None]]ย |ย Noneย =ย None,
on_close_listeners:ย List[Callable[[int,ย strย |ย None],ย None]]ย |ย Noneย =ย None,
trace_enabled:ย boolย =ย False,
all_message_trace_enabled:ย boolย =ย False,
ping_pong_trace_enabled:ย boolย =ย False)
-
Expand source code @@ -472,7 +62,7 @@

Classes

default_auto_reconnect_enabled: bool auto_reconnect_enabled: bool ssl: Optional[SSLContext] - proxy: str + proxy: Optional[str] timeout: int base_url: str ping_interval: int @@ -565,9 +155,7 @@

Classes

self.current_session = None self.current_session_state = ConnectionState() - self.current_session_runner = IntervalRunner( - self._run_current_session, 0.1 - ).start() + self.current_session_runner = IntervalRunner(self._run_current_session, 0.1).start() self.wss_uri = None self.current_app_monitor_started = False @@ -614,10 +202,7 @@

Classes

# SKip the events generated by this bot user return # https://github.com/slackapi/python-slack-sdk/issues/533 - if event_type == "*" or ( - actual_event_type is not None - and actual_event_type == event_type - ): + if event_type == "*" or (actual_event_type is not None and actual_event_type == event_type): func(_self, event) self.message_listeners.append(new_message_listener) @@ -703,9 +288,7 @@

Classes

self.current_app_monitor_started = True self.current_app_monitor.start() - self.logger.info( - f"A new session has been established (session id: {self.session_id()})" - ) + self.logger.info(f"A new session has been established (session id: {self.session_id()})") def disconnect(self): """Disconnects the current session.""" @@ -729,9 +312,7 @@

Classes

if payload is None: return if self.current_session is None or not self.current_session.is_active(): - raise SlackClientError( - "The RTM client is not connected to the Slack servers" - ) + raise SlackClientError("The RTM client is not connected to the Slack servers") if isinstance(payload, str): self.current_session.send(payload) else: @@ -744,17 +325,13 @@

Classes

def enqueue_message(self, message: str): self.message_queue.put(message) if self.logger.level <= logging.DEBUG: - self.logger.debug( - f"A new message enqueued (current queue size: {self.message_queue.qsize()})" - ) + self.logger.debug(f"A new message enqueued (current queue size: {self.message_queue.qsize()})") def process_message(self): try: raw_message = self.message_queue.get(timeout=1) if self.logger.level <= logging.DEBUG: - self.logger.debug( - f"A message dequeued (current queue size: {self.message_queue.qsize()})" - ) + self.logger.debug(f"A message dequeued (current queue size: {self.message_queue.qsize()})") if raw_message is not None: message: dict = {} @@ -809,8 +386,7 @@

Classes

def run_all_error_listeners(self, error: Exception): self.logger.exception( - f"on_error invoked (session id: {self.session_id()}, " - f"error: {type(error).__name__}, message: {error})" + f"on_error invoked (session id: {self.session_id()}, " f"error: {type(error).__name__}, message: {error})" ) for listener in self.on_error_listeners: listener(error) @@ -819,10 +395,7 @@

Classes

if self.logger.level <= logging.DEBUG: self.logger.debug(f"on_close invoked (session id: {self.session_id()})") if self.auto_reconnect_enabled: - self.logger.info( - "Received CLOSE event. Going to reconnect... " - f"(session id: {self.session_id()})" - ) + self.logger.info("Received CLOSE event. Going to reconnect... " f"(session id: {self.session_id()})") self.connect_to_new_endpoint() for listener in self.on_close_listeners: listener(code, reason) @@ -831,20 +404,13 @@

Classes

if self.current_session is not None and self.current_session.is_active(): session_id = self.session_id() try: - self.logger.info( - "Starting to receive messages from a new connection" - f" (session id: {session_id})" - ) + self.logger.info("Starting to receive messages from a new connection" f" (session id: {session_id})") self.current_session_state.terminated = False self.current_session.run_until_completion(self.current_session_state) - self.logger.info( - "Stopped receiving messages from a connection" - f" (session id: {session_id})" - ) + self.logger.info("Stopped receiving messages from a connection" f" (session id: {session_id})") except Exception as e: self.logger.exception( - "Failed to start or stop the current session" - f" (session id: {session_id}, error: {e})" + "Failed to start or stop the current session" f" (session id: {session_id}, error: {e})" ) def _monitor_current_session(self): @@ -852,12 +418,9 @@

Classes

try: self.current_session.check_state() - if self.auto_reconnect_enabled and ( - self.current_session is None or not self.current_session.is_active() - ): + if self.auto_reconnect_enabled and (self.current_session is None or not self.current_session.is_active()): self.logger.info( - "The session seems to be already closed. Going to reconnect... " - f"(session id: {self.session_id()})" + "The session seems to be already closed. Going to reconnect... " f"(session id: {self.session_id()})" ) self.connect_to_new_endpoint() except Exception as e: @@ -866,99 +429,100 @@

Classes

f"(session id: {self.session_id()}, error: {type(e).__name__}, message: {e})" )
+

Class variables

var auto_reconnect_enabled :ย bool
-
+

The type of the None singleton.

var base_url :ย str
-
+

The type of the None singleton.

-
var bot_id :ย Optional[str]
+
var bot_id :ย strย |ย None
-
+

The type of the None singleton.

var closed :ย bool
-
+

The type of the None singleton.

-
var connect_operation_lock :ย 
+
var connect_operation_lock :ย _thread.lock
-
+

The type of the None singleton.

-
var current_session :ย Optional[Connection]
+
var current_session :ย Connectionย |ย None
-
+

The type of the None singleton.

-
var current_session_state :ย Optional[ConnectionState]
+
var current_session_state :ย ConnectionStateย |ย None
-
+

The type of the None singleton.

var default_auto_reconnect_enabled :ย bool
-
+

The type of the None singleton.

var logger :ย logging.Logger
-
+

The type of the None singleton.

var message_listeners :ย List[Callable[[RTMClient,ย dict],ย None]]
-
+

The type of the None singleton.

var message_processor :ย IntervalRunner
-
+

The type of the None singleton.

var message_queue :ย queue.Queue
-
+

The type of the None singleton.

var message_workers :ย concurrent.futures.thread.ThreadPoolExecutor
-
+

The type of the None singleton.

-
var on_close_listeners :ย List[Callable[[int,ย Optional[str]],ย None]]
+
var on_close_listeners :ย List[Callable[[int,ย strย |ย None],ย None]]
-
+

The type of the None singleton.

var on_error_listeners :ย List[Callable[[Exception],ย None]]
-
+

The type of the None singleton.

var on_message_listeners :ย List[Callable[[str],ย None]]
-
+

The type of the None singleton.

var ping_interval :ย int
-
+

The type of the None singleton.

-
var proxy :ย str
+
var proxy :ย strย |ย None
-
+

The type of the None singleton.

-
var ssl :ย Optional[ssl.SSLContext]
+
var ssl :ย ssl.SSLContextย |ย None
-
+

The type of the None singleton.

var timeout :ย int
-
+

The type of the None singleton.

-
var token :ย Optional[str]
+
var token :ย strย |ย None
-
+

The type of the None singleton.

var web_client :ย WebClient
-
+

The type of the None singleton.

-
var wss_uri :ย Optional[str]
+
var wss_uri :ย strย |ย None
-
+

The type of the None singleton.

Methods

@@ -967,8 +531,6 @@

Methods

def close(self) โ€‘>ย None
-

Closes this instance and cleans up underlying resources. -After calling this method, this instance is no longer usable.

Expand source code @@ -982,12 +544,13 @@

Methods

self.disconnect() self.current_session.close()
+

Closes this instance and cleans up underlying resources. +After calling this method, this instance is no longer usable.

def connect(self)
-

Starts talking to the RTM server through a WebSocket connection

Expand source code @@ -1032,16 +595,14 @@

Methods

self.current_app_monitor_started = True self.current_app_monitor.start() - self.logger.info( - f"A new session has been established (session id: {self.session_id()})" - ) + self.logger.info(f"A new session has been established (session id: {self.session_id()})")
+

Starts talking to the RTM server through a WebSocket connection

def connect_to_new_endpoint(self, force:ย boolย =ย False)
-

Acquires a new WSS URL and tries to connect to the endpoint.

Expand source code @@ -1055,12 +616,12 @@

Methods

self.connect() self.logger.info("Connected to a new endpoint...")
+

Acquires a new WSS URL and tries to connect to the endpoint.

def disconnect(self)
-

Disconnects the current session.

Expand source code @@ -1069,12 +630,12 @@

Methods

"""Disconnects the current session.""" self.current_session.disconnect()
+

Disconnects the current session.

def enqueue_message(self, message:ย str)
-
Expand source code @@ -1082,16 +643,14 @@

Methods

def enqueue_message(self, message: str):
     self.message_queue.put(message)
     if self.logger.level <= logging.DEBUG:
-        self.logger.debug(
-            f"A new message enqueued (current queue size: {self.message_queue.qsize()})"
-        )
+ self.logger.debug(f"A new message enqueued (current queue size: {self.message_queue.qsize()})")
+
def is_connected(self) โ€‘>ย bool
-

Returns True if this client is connected.

Expand source code @@ -1100,12 +659,12 @@

Methods

"""Returns True if this client is connected.""" return self.current_session is not None and self.current_session.is_active()
+

Returns True if this client is connected.

def issue_new_wss_url(self) โ€‘>ย str
-

Acquires a new WSS URL using rtm.connect API method

Expand source code @@ -1127,17 +686,12 @@

Methods

self.logger.error(f"Failed to retrieve WSS URL: {e}") raise e
+

Acquires a new WSS URL using rtm.connect API method

def on(self, event_type:ย str) โ€‘>ย Callable
-

Registers a new event listener.

-

Args

-
-
event_type
-
str representing an event's type (e.g., message, reaction_added)
-
Expand source code @@ -1170,10 +724,7 @@

Args

# SKip the events generated by this bot user return # https://github.com/slackapi/python-slack-sdk/issues/533 - if event_type == "*" or ( - actual_event_type is not None - and actual_event_type == event_type - ): + if event_type == "*" or (actual_event_type is not None and actual_event_type == event_type): func(_self, event) self.message_listeners.append(new_message_listener) @@ -1185,12 +736,17 @@

Args

return __call__
+

Registers a new event listener.

+

Args

+
+
event_type
+
str representing an event's type (e.g., message, reaction_added)
+
def process_message(self)
-
Expand source code @@ -1199,9 +755,7 @@

Args

try: raw_message = self.message_queue.get(timeout=1) if self.logger.level <= logging.DEBUG: - self.logger.debug( - f"A message dequeued (current queue size: {self.message_queue.qsize()})" - ) + self.logger.debug(f"A message dequeued (current queue size: {self.message_queue.qsize()})") if raw_message is not None: message: dict = {} @@ -1215,12 +769,12 @@

Args

except Empty: pass
+
def process_messages(self) โ€‘>ย None
-
Expand source code @@ -1232,12 +786,12 @@

Args

except Exception as e: self.logger.exception(f"Failed to process a message: {e}")
+
-def run_all_close_listeners(self, code:ย int, reason:ย Optional[str]ย =ย None) +def run_all_close_listeners(self, code:ย int, reason:ย strย |ย Noneย =ย None)
-
Expand source code @@ -1246,38 +800,34 @@

Args

if self.logger.level <= logging.DEBUG: self.logger.debug(f"on_close invoked (session id: {self.session_id()})") if self.auto_reconnect_enabled: - self.logger.info( - "Received CLOSE event. Going to reconnect... " - f"(session id: {self.session_id()})" - ) + self.logger.info("Received CLOSE event. Going to reconnect... " f"(session id: {self.session_id()})") self.connect_to_new_endpoint() for listener in self.on_close_listeners: listener(code, reason)
+
def run_all_error_listeners(self, error:ย Exception)
-
Expand source code
def run_all_error_listeners(self, error: Exception):
     self.logger.exception(
-        f"on_error invoked (session id: {self.session_id()}, "
-        f"error: {type(error).__name__}, message: {error})"
+        f"on_error invoked (session id: {self.session_id()}, " f"error: {type(error).__name__}, message: {error})"
     )
     for listener in self.on_error_listeners:
         listener(error)
+
def run_all_message_listeners(self, message:ย str)
-
Expand source code @@ -1289,12 +839,12 @@

Args

for listener in self.on_message_listeners: listener(message)
+
def run_message_listeners(self, message:ย dict) โ€‘>ย None
-
Expand source code @@ -1315,12 +865,12 @@

Args

if self.logger.level <= logging.DEBUG: self.logger.debug(f"Message processing completed (type: {type})")
+
-def send(self, payload:ย Union[dict,ย str]) โ€‘>ย None +def send(self, payload:ย dictย |ย str) โ€‘>ย None
-
Expand source code @@ -1329,20 +879,18 @@

Args

if payload is None: return if self.current_session is None or not self.current_session.is_active(): - raise SlackClientError( - "The RTM client is not connected to the Slack servers" - ) + raise SlackClientError("The RTM client is not connected to the Slack servers") if isinstance(payload, str): self.current_session.send(payload) else: self.current_session.send(json.dumps(payload))
+
-def session_id(self) โ€‘>ย Optional[str] +def session_id(self) โ€‘>ย strย |ย None
-
Expand source code @@ -1352,12 +900,12 @@

Args

return self.current_session.session_id return None
+
def start(self) โ€‘>ย None
-

Establishes an RTM connection and blocks the current thread.

Expand source code @@ -1367,6 +915,7 @@

Args

self.connect() Event().wait()
+

Establishes an RTM connection and blocks the current thread.

@@ -1374,7 +923,6 @@

Args